Ctrl+a Ctrl+e Ctrl+u Ctrl+r
Ctrl+n Ctrl+p 等等
更多的快捷键和 bash 的内容可以参考这篇
绝大多数情况下会在终端来管理 git 项目,对 git 项目最好能够有一个直观的显示,包括当前的分支,修改的内容,命令行空间比较小,但是也能够显示分支名和是否有修改,推荐使用
oh-my-zsh
把每天要使用 5 次以上的命令都制作别名保存到 bashrc 或者 zshrc 中
alias vi='vim'
如果经常切换不同的终端窗口执行不同的任务,那么就需要考虑使用顺手的多窗口或者支持分屏 (panel) 的终端,推荐使用 Tmux,配合快捷键无比顺畅。
Tmux 的内容可以参考这个
除却常用的查找文件、浏览文件命令等等之外,善用命令行中的 Tab 自动补全,通配符等等。
cat, grep
通过文件名查找
sudo find -name <filename> path_to_search
滚动查看大文件
less path_to_file
当前路径下打开文件管理器
nemo .
树形结构展开当前目录结构,包括子目录和文件
tree
流式读取一个文件,实时日志文件
tail -f filename
Unix 哲学中,每个程序都足够小,只做一件事情,并将其做到最好。Bash 提供的管道机制 (|) 可以将命令的输出作为另一个命令的输入,结合两个或者多个命令,比如最简单的例子,ls 是将目录下文件列出, grep 命令是搜索包含指定正则的行,结合两者
ls ~ | grep word
就可以过滤 HOME 目录下,包含 word 的文件
*
星号字符匹配任意长度,比如删除文件夹下,指定文件
rm morning*.jpg
这样就删除了当前目录下所有 morning 开头的 jpg 文件,使用 rm 命令时一定要注意确认
>
字符可以将一个命令的输出重定向到一个文件或者另一个命令的输入,一般情况下命令会有一些输出结果
tree . > file.txt
可以将当前文件目录结构输出到文件 file.txt 中。
>
会覆盖输出的文件 >>
用来追加到文件末尾。
Bash 默认情况下会立即执行当前键入的每一条命令,通常我们就是这样要求终端的,但是如果想要某一些应用在后台长时间执行,可以使用 &
操作符,当然更加推荐 screen
或者 tmux
这类的工具。
./long_time_task.sh &
可以在后台执行一个长时间任务。
使用 htop 来查看系统资源,以及对进程进行管理,当然如果熟悉 ps 也可以使用 ps 来查看
大型 Java 项目可以考虑使用 JetBrains 系列产品,对于 Python, Bash 等脚本语言可以考虑使用 vim
之前一篇文章 已经分享过 [[Tmux]] 的基本使用。这一篇就来总结一下 Tmux 下常用的插件。
Vim 有自己的插件管理系统,zsh 也有插件管理,那当然 Tmux 肯定有插件管理,其实学习 Tmux 的过程中,和 Vim 当时一样,所有的拷贝,粘贴的内容都是在 Tmux 和 Vim 的内部,和外部操作系统的粘贴板完全隔离了,我就是为了解决这个问题,才接触到了 Tmux Plugin Manager。
我个人的 Tmux 配置文件,可以参考我的 GitHub。
Tmux Plugin Manager 是一个 Tmux 插件管理器,用这个插件可以很方便的管理相关的插件。下文中可能把 Tmux Plugin Manager 简写成 tpm。
tpm 的安装的方法,在 GitHub 的页面非常清楚,git clone https://github.com/tmux-plugins/tpm ~/.tmux/plugins/tpm
项目,在 .tmux.conf
文件中加入配置,重新加载配置即可。
# List of plugins
set -g @plugin 'tmux-plugins/tpm'
set -g @plugin 'tmux-plugins/tmux-sensible'
# Other examples:
# set -g @plugin 'github_username/plugin_name'
# set -g @plugin '[email protected]/user/plugin'
# set -g @plugin '[email protected]/user/plugin'
# Initialize TMUX plugin manager (keep this line at the very bottom of tmux.conf)
run '~/.tmux/plugins/tpm/tpm'
新添加插件只需要,在配置文件中增加一行
set -g @plugin '...'
三个操作:
<prefix>
+ I 大写的 I (Install) 来安装新插件<prefix>
+ alt + u (uninstall) 来卸载。<prefix>
+ U,记住是大写的 U
.<prefix> + Alt + u
tmux-yank 是一款将 panel 或会话中的文本复制到剪贴板的插件。tmux-yank 插件可以让用户完全通过键盘来完成复制文本的操作。
在 .tmux.conf
中加入
set -g @plugin 'tmux-plugins/tmux-yank'
然后使用 <prefix>
+ I 来安装 tmux-yank
Linux 平台下需要安装依赖 xsel 或者 xclip
sudo apt-get install xsel # or xclip
一些常用的快捷键
在普通模式下
<prefix>
+ y 来将命令行内容拷贝到 clipboard 系统粘贴板。<prefix>
+ Y 将当前 panel 的 working directory 拷贝到粘贴板在拷贝模式下复制到系统粘贴板
从 Tmux copy mode 直接打开选中内容,这个插件可以在 Tmux 的 copy 模式下,直接打开高亮选中的部分。
比较常见的比如选中一段网址,然后按下 o
,即可打开。
或者选中一个关键字,然后使用 Shift+s 来在搜索引擎中搜索。如果想要更换其他搜索引擎可以参考官方配置
tmux-resurrect 是一款可以在电脑重启或者断电之后恢复会话的插件。
Tmux 的 session 在机器关机再启动后就丢失了,使用 tmux-resurrect
可以将 session 保存到磁盘,再次启动机器的时候可以立即恢复。
安装:
set -g @plugin 'tmux-plugins/tmux-resurrect'
然后按下 prefix+I
安装。tmux-resurrect
只有两个简单的 key-binding,分别是保存和恢复:
prefix + Ctrl-s save
prefix + Ctrl-r restore
tmux-continuum 插件配合 tmux-resurrect 可以实现连续的保存 Tmux 的状态,如果自己的笔记本关机,那么 Tmux 的会话也会被销毁,下次开机需要重新创建,当然配合 fzf 之后倒也是没有那么麻烦,但如果开了 Pane,新建了 Windows,要恢复还是需要花费一段时间的,continuum 能够实时保存,重启后也能快速恢复。
安装:
set -g @plugin 'tmux-plugins/tmux-resurrect'
set -g @plugin 'tmux-plugins/tmux-continuum'
然后启用:
set -g @continuum-restore 'on'
Tmux Sidebar 可以在 Tmux 中展示当前路径的目录树。非常适合和 Vim 一起使用。
Tmux Battery 在 Tmux 中显示电量和状态。
tmux-prefix-highlight 是一款可以在状态栏显示当前激活的 prefix 按键的插件。
Log4j 是一个可靠的、高效的、快速可扩展的日志框架,Log4j 使用 Java 开发,已经被移植到了很多主流语言,比如 C, C++, Perl, Python, Ruby 等等。
这一切都可以通过一个配置文件来灵活地进行配置,而不需要修改应用代码。Log4j 是 Apache 的一个开放源代码项目。
在应用程序中添加日志记录总的来说基于三个目的:
Log4j 主要有三个组件:
log.info()
log.debug()
的地方,作用就是用来记录日志综合使用这三个组件可以轻松的记录信息的类型和级别,并可以在运行时控制日志输出的样式和位置。
Log4j 的执行顺序
Log4j 的特性:
通常,我们都提供一个名为 log4j.properties
的文件,该文件以 key-value 的方式进行配置。默认情况下,LogManager 会在 CLASSPATH 目录下寻找 log4j.properties
这个文件名。一些老的项目也会用 log4j.xml 格式来配置。
简单例子
# Define the root logger with appender X
log4j.rootLogger = DEBUG, X
# Set the appender named X to be a File appender
log4j.appender.X=org.apache.log4j.FileAppender
log4j.appender.X.File=${log}/log.out
# Define the layout for X appender
log4j.appender.X.layout=org.apache.log4j.PatternLayout
log4j.appender.X.layout.conversionPattern=%d{yyyy-MM-dd HH:mm:ss} %p [%t] %C{1}.%M(%F:%L) - %m%n
这个例子定义了
在 maven 的 pom.xml dependency 下添加:
<!-- sl4j -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.21</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.21</version>
</dependency>
<!-- log4j -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
slf4j(Simple Logging Facade for Java) 不是一个真正的日志实现,而是抽象层,允许在后台使用任意一个日志类库。slf4j 使得代码能够独立于任意一个特定的日志 API。
SLF4J API 的特性占位符 (place holder),在代码中表示为“{}”的特性。占位符是一个非常类似于在 String 的 format() 方法中的 %s,因为它会在运行时被某个提供的实际字符串所替换。这不仅降低了你代码中字符串连接次数,而且还节省了新建的 String 对象。
然后在 CLASSPATH 下添加 log4j.properties
文件。
#config root logger
log4j.rootLogger = INFO,system.out
log4j.appender.system.out=org.apache.log4j.ConsoleAppender
log4j.appender.system.out.layout=org.apache.log4j.PatternLayout
log4j.appender.system.out.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %p [%t] %C{1}.%M(%F:%L) - %m%n
#config this Project.file logger
log4j.logger.APPENDER_NAME.file=INFO,APPENDER_NAME.file.out
log4j.appender.APPENDER_NAME.file.out=org.apache.log4j.DailyRollingFileAppender
log4j.appender.APPENDER_NAME.file.out.File=logContentFile.log
log4j.appender.APPENDER_NAME.file.out.layout=org.apache.log4j.PatternLayout
在 Java 代码中使用 slf4j
private static final Logger logger = LoggerFactory.getLogger(Server.class);
logger.info("now {}" , "starting server");
Log4j 支持两种配置文件格式,一种是 XML 格式的文件,一种是 Java properties(key=value)【Java 特性文件(键 = 值)】。先介绍使用 Java 特性文件做为配置文件的方法
配置详细介绍
Loggers 组件在此系统中被分为五个级别:DEBUG、INFO、WARN、ERROR 和 FATAL。这五个级别是有顺序的
DEBUG < INFO < WARN < ERROR < FATAL
分别用来指定这条日志信息的重要程度,这里 Log4j 有一个规则:假设 Loggers 级别为 P,如果在 Loggers 中发生了一个级别 Q 比 P 高,则记录,否则就不记录。
比如,你定义的级别是 info,那么 error 和 warn 的日志可以显示而比他低的 debug 信息就不显示了。
配置 root Logger
其语法为:
log4j.rootLogger = [ level ] , appenderName1, appenderName2, …
# level : 是日志记录的优先级,分为 OFF、FATAL、ERROR、WARN、INFO、DEBUG、ALL 或者您定义的级别。Log4j 建议只使用四个级别,优先级从高到低分别是 ERROR、WARN、INFO、DEBUG。通过在这里定义的级别,您可以控制到应用程序中相应级别的日志信息的开关。比如在这里定义了 INFO 级别,则应用程序中所有 DEBUG 级别的日志信息将不被打印出来。
appenderName: 就是指定日志信息输出到哪个地方。可以同时指定多个输出目的地。
例如:log4j.rootLogger=info,A1,B2,C3
Log4j 日志系统允许把日志输出到不同的地方,如控制台(Console)、文件(Files)、根据日期或者文件大小产生新的文件、以流的形式发送到其它地方等等。
其语法为:
log4j.appender.appenderName = fully.qualified.name.of.appender.class
log4j.appender.appenderName.option1 = value1
log4j.appender.appenderName.optionN = valueN
其中, Log4j 提供的 appender 有以下几种:
例:
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
定义一个名为 stdout 的输出目的地, 输出到控制台。
其语法为:
log4j.appender.appenderName = fully.qualified.name.of.appender.class
“fully.qualified.name.of.appender.class” 可以指定下面五个目的地中的一个:
输出目的地的选项,可以通过如下语法指定
log4j.appender.appenderName.option = valueN
Java web 项目里面的日志的位置配置支持变量
如果是要指定日志文件的位置为 D 盘下的 log.txt 文件。
log4j.appender.APPENDER_NAME.file.out.File=d:\\log.txt
如果指定日志文件的位置为当前的 tomcat 的工作目录下的某个文件
log4j.appender.APPENDER_NAME.file.out.File=${catalina.home}/logs/logs_tomcat.log
DatePattern='.'yyyy-ww
: 每周滚动一次文件,即每周产生一个新的文件。当然也可以指定按月、周、天、时和分。即对应的格式如下:
按照日志大小滚动日志文件
实际应用:
log4j.appender.A1=org.apache.log4j.ConsoleAppender // 这里指定了日志输出的第一个位置 A1 是控制台 ConsoleAppender
如果希望格式化自己的日志输出,Log4j 可以在 Appenders 的后面附加 Layouts 来完成这个功能。Layouts 提供了四种日志输出样式,如根据 HTML 样式、自由指定样式、包含日志级别与信息的样式和包含日志时间、线程、类别等信息的样式等等。
其语法表示为:
org.apache.log4j.HTMLLayout 以 HTML 表格形式布局 org.apache.log4j.PatternLayout 可以灵活地指定布局模式 org.apache.log4j.SimpleLayout 包含日志信息的级别和信息字符串 org.apache.log4j.TTCCLayout 包含日志产生的时间、线程、类别等等信息
配置时使用方式为:
log4j.appender.appenderName.layout = fully.qualified.name.of.layout.class log4j.appender.appenderName.layout.option1 = value1 log4j.appender.appenderName.layout.option = valueN
“fully.qualified.name.of.layout.class” 可以指定下面 4 个格式中的一个:
Log4J 采用类似 C 语言中的 printf 函数的打印格式格式化日志信息,打印参数如下:
%m 输出代码中指定的消息
%p 输出优先级,即 DEBUG,INFO,WARN,ERROR,FATAL
%r 输出自应用启动到输出该 log 信息耗费的毫秒数
%c 输出所属的类目,通常就是所在类的全名
%t 输出产生该日志事件的线程名
%n 输出一个回车换行符,Windows 平台为“\r\n”,Unix 平台为“\n”
%d 输出日志时间点的日期或时间,默认格式为 ISO8601,也可以在其后指定格式,比如: %d{yyyy MMM dd HH:mm:ss,SSS} ,输出类似: 2002 年 10 月 18 日 22 : 10 : 28 , 921
%l 输出日志事件的发生位置,包括类目名、发生的线程,以及在代码中的行数。
格式化例子:
log4j.appender.APPENDER_NAME.file.out.layout.ConversionPattern=%d{yyyy MMM dd HH:mm:ss,SSS}%5p{ \%F\:\%L }-%m%n
注意:
参数中间可能会有一些数字,比如:%5p 它的意思就是在输出此参数之前加入多少个空格,还有就是里面的“\”的作用是转义字符
LocationInfo=true: 默认值是 false, 输出 java 文件和行号
实际应用:
log4j.appender.A1.layout=org.apache.log4j.PatternLayout
log4j.appender.A1.layout.ConversionPattern=%-4r %-5p %d{yyyy-MM-dd HH:mm:ssS} %c %m%n
这里需要说明的就是日志信息格式中几个符号所代表的含义:
%p: 输出日志信息优先级,即 DEBUG,INFO,WARN,ERROR,FATAL,
%d: 输出日志时间点的日期或时间,默认格式为 ISO8601,也可以在其后指定格式,比如:%d{yyy MMM dd HH:mm:ss,SSS},输出类似:2002 年 10 月 18 日 22:10:28,921
%r: 输出自应用启动到输出该 log 信息耗费的毫秒数
%c: 输出日志信息所属的类目,通常就是所在类的全名
%t: 输出产生该日志事件的线程名
%l: 输出日志事件的发生位置,相当于 %C.%M(%F:%L) 的组合,包括类目名、发生的线程,以及在代码中的行数。举例:Testlog4.main(TestLog4.java:10)
%x: 输出和当前线程相关联的 NDC(嵌套诊断环境), 尤其用到像 java servlets 这样的多客户多线程的应用中。
%%: 输出一个"%"字符
%F: 输出日志消息产生时所在的文件名称
%L: 输出代码中的行号
%m: 输出代码中指定的消息,产生的日志具体信息
%n: 输出一个回车换行符,Windows 平台为"\r\n",Unix 平台为"\n"输出日志信息换行
可以在 % 与模式字符之间加上修饰符来控制其最小宽度、最大宽度、和文本的对齐方式。如:
这里上面三个步骤是对前面 Log4j 组件说明的一个简化;下面给出一个具体配置例子,在程序中可以参照执行:
log4j.rootLogger=INFO,A1,B2 log4j.appender.A1=org.apache.log4j.ConsoleAppender log4j.appender.A1.layout=org.apache.log4j.PatternLayout log4j.appender.A1.layout.ConversionPattern=%-4r %-5p %d{yyyy-MM-dd HH:mm:ssS} %c %m%n
根据上面的日志格式,某一个程序的输出结果如下:
0 INFO 2003-06-13 13:23:46968 ClientWithLog4j Client socket: Socket[addr=localhost/127.0.0.1,port=8002,localport=2014]
DEBUG 2003-06-13 13:23:46984 ClientWithLog4j Server says: 'Java server with log4j, Fri Jun 13 13:23:46 CST 2003'
16 DEBUG 2003-06-13 13:23:46984 ClientWithLog4j GOOD
16 DEBUG 2003-06-13 13:23:46984 ClientWithLog4j Server responds: 'Command 'HELLO' not understood.'
16 DEBUG 2003-06-13 13:23:46984 ClientWithLog4j HELP
16 DEBUG 2003-06-13 13:23:46984 ClientWithLog4j Server responds: 'Vocabulary: HELP QUIT'
16 DEBUG 2003-06-13 13:23:46984 ClientWithLog4j QUIT
当输出信息于回滚文件时
log4j.appender.ROLLING_FILE=org.apache.log4j.RollingFileAppender // 指定以文件的方式输出日志 log4j.appender.ROLLING_FILE.Threshold=ERROR log4j.appender.ROLLING_FILE.File=rolling.log // 文件位置,也可以用变量 ${java.home}、rolling.log log4j.appender.ROLLING_FILE.Append=true log4j.appender.ROLLING_FILE.MaxFileSize=10KB // 文件最大尺寸 log4j.appender.ROLLING_FILE.MaxBackupIndex=1 // 备份数 log4j.appender.ROLLING_FILE.layout=org.apache.log4j.PatternLayout log4j.appender.ROLLING_FILE.layout.ConversionPattern=[framework] %d - %c -%-4r [%t] %-5p %c %x - %m%n
建议使用 lombok 的 @Slf4j 注解。
LOG4J 的配置之简单使它遍及于越来越多的应用中:Log4J 配置文件实现了输出到控制台、文件、回滚文件、发送日志邮件、输出到数据库日志表、自定义标签等全套功能。
log4j.rootLogger=DEBUG,CONSOLE,FILE,ROLLING_FILE,SOCKET,LF5_APPENDER,MAIL,DATABASE,A1,im
log4j.addivity.org.apache=true
# 应用于控制台
log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
log4j.appender.Threshold=DEBUG
log4j.appender.CONSOLE.Target=System.out
log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
log4j.appender.CONSOLE.layout.ConversionPattern=[framework] %d - %c -%-4r [%t] %-5p %c %x - %m%n
#log4j.appender.CONSOLE.layout.ConversionPattern=[start]%d{DATE}[DATE]%n%p[PRIORITY]%n%x[NDC]%n%t[thread] n%c[CATEGORY]%n%m[MESSAGE]%n%n
#应用于文件
log4j.appender.FILE=org.apache.log4j.FileAppender
log4j.appender.FILE.File=file.log
log4j.appender.FILE.Append=false
log4j.appender.FILE.layout=org.apache.log4j.PatternLayout
log4j.appender.FILE.layout.ConversionPattern=[framework] %d - %c -%-4r [%t] %-5p %c %x - %m%n
# Use this layout for LogFactor 5 analysis
# 应用于文件回滚
log4j.appender.ROLLING_FILE=org.apache.log4j.RollingFileAppender
log4j.appender.ROLLING_FILE.Threshold=ERROR
log4j.appender.ROLLING_FILE.File=rolling.log // 文件位置,也可以用变量 ${java.home}、rolling.log
log4j.appender.ROLLING_FILE.Append=true //true: 添加 false: 覆盖
log4j.appender.ROLLING_FILE.MaxFileSize=10KB // 文件最大尺寸
log4j.appender.ROLLING_FILE.MaxBackupIndex=1 // 备份数
log4j.appender.ROLLING_FILE.layout=org.apache.log4j.PatternLayout
log4j.appender.ROLLING_FILE.layout.ConversionPattern=[framework] %d - %c -%-4r [%t] %-5p %c %x - %m%n
#应用于 socket
log4j.appender.SOCKET=org.apache.log4j.RollingFileAppender
log4j.appender.SOCKET.RemoteHost=localhost
log4j.appender.SOCKET.Port=5001
log4j.appender.SOCKET.LocationInfo=true
# Set up for Log Facter 5
log4j.appender.SOCKET.layout=org.apache.log4j.PatternLayout
log4j.appender.SOCET.layout.ConversionPattern=[start]%d{DATE}[DATE]%n%p[PRIORITY]%n%x[NDC]%n%t[thread]%n%c[CATEGORY]%n%m[MESSAGE]%n%n
# Log Factor 5 Appender
log4j.appender.LF5_APPENDER=org.apache.log4j.lf5.LF5Appender
log4j.appender.LF5_APPENDER.MaxNumberOfRecords=2000
# 发送日志给邮件
log4j.appender.MAIL=org.apache.log4j.net.SMTPAppender
log4j.appender.MAIL.Threshold=FATAL
log4j.appender.MAIL.BufferSize=10
[email protected]
log4j.appender.MAIL.SMTPHost=www.gmail.com
log4j.appender.MAIL.Subject=Log4J Message
[email protected]
log4j.appender.MAIL.layout=org.apache.log4j.PatternLayout
log4j.appender.MAIL.layout.ConversionPattern=[framework] %d - %c -%-4r [%t] %-5p %c %x - %m%n
# 用于数据库
log4j.appender.DATABASE=org.apache.log4j.jdbc.JDBCAppender
log4j.appender.DATABASE.URL=jdbc:mysql://localhost:3306/test
log4j.appender.DATABASE.driver=com.mysql.jdbc.Driver
log4j.appender.DATABASE.user=root
log4j.appender.DATABASE.password=
log4j.appender.DATABASE.sql=INSERT INTO LOG4J (Message) VALUES ('[framework] %d - %c -%-4r [%t] %-5p %c %x - %m%n')
log4j.appender.DATABASE.layout=org.apache.log4j.PatternLayout
log4j.appender.DATABASE.layout.ConversionPattern=[framework] %d - %c -%-4r [%t] %-5p %c %x - %m%n
log4j.appender.A1=org.apache.log4j.DailyRollingFileAppender
log4j.appender.A1.File=SampleMessages.log4j
log4j.appender.A1.DatePattern=yyyyMMdd-HH'.log4j'
log4j.appender.A1.layout=org.apache.log4j.xml.XMLLayout
#自定义 Appender
log4j.appender.im = net.cybercorlin.util.logger.appender.IMAppender
log4j.appender.im.host = mail.cybercorlin.net
log4j.appender.im.username = username
log4j.appender.im.password = password
log4j.appender.im.recipient = [email protected]
log4j.appender.im.layout=org.apache.log4j.PatternLayout
log4j.appender.im.layout.ConversionPattern =[framework] %d - %c -%-4r [%t] %-5p %c %x - %m%n
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
<!-- ========================== 自定义输出格式说明 ================================ -->
<!-- %p 输出优先级,即 DEBUG,INFO,WARN,ERROR,FATAL -->
<!-- #%r 输出自应用启动到输出该 log 信息耗费的毫秒数 -->
<!-- #%c 输出所属的类目,通常就是所在类的全名 -->
<!-- #%t 输出产生该日志事件的线程名 -->
<!-- #%n 输出一个回车换行符,Windows 平台为“\r\n”,Unix 平台为“\n” -->
<!-- #%d 输出日志时间点的日期或时间,默认格式为 ISO8601,也可以在其后指定格式,比如:%d{yyy MMM dd HH:mm:ss,SSS},输出类似:2002 年 10 月 18 日 22:10:28,921 -->
<!-- #%l 输出日志事件的发生位置,包括类目名、发生的线程,以及在代码中的行数。举例:Testlog4.main(TestLog4.java:10) -->
<!-- ========================================================================== -->
<!-- ========================== 输出方式说明 ================================ -->
<!-- Log4j 提供的 appender 有以下几种:-->
<!-- org.apache.log4j.ConsoleAppender(控制台), -->
<!-- org.apache.log4j.FileAppender(文件), -->
<!-- org.apache.log4j.DailyRollingFileAppender(每天产生一个日志文件), -->
<!-- org.apache.log4j.RollingFileAppender(文件大小到达指定尺寸的时候产生一个新的文件), -->
<!-- org.apache.log4j.WriterAppender(将日志信息以流格式发送到任意指定的地方) -->
<!-- ========================================================================== -->
<!-- 输出到日志文件 -->
<appender name="filelog_appender"
class="org.apache.log4j.RollingFileAppender">
<!-- 设置 File 参数:日志输出文件名 -->
<param name="File" value="log/testlog4jxml_all.log" />
<!-- 设置是否在重新启动服务时,在原有日志的基础添加新日志 -->
<param name="Append" value="true" />
<!-- 设置文件大小 -->
<param name="MaxFileSize" value="1MB" />
<!-- 设置文件备份 -->
<param name="MaxBackupIndex" value="10000" />
<!-- 设置输出文件项目和格式 -->
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="%d{yyyy-MM-dd HH:mm:ss} %-5p (%c:%L)- %m%n" />
</layout>
</appender>
<!-- 输出到日志文件 每天一个日志 -->
<appender name="filelog_daily" class="org.apache.log4j.DailyRollingFileAppender">
<param name="File" value="log/daily.log" />
<param name="DatePattern" value="'daily.'yyyy-MM-dd'.log'" />
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="[%d{yyyy-MM-dd HH:mm:ss\} %-5p] [%t] (%c:%L) - %m%n" />
</layout>
</appender>
<!-- 输出到控制台中 -->
<appender name="console" class="org.apache.log4j.ConsoleAppender">
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern"
value="%d{yyyy-MM-dd HH:mm:ss} %-5p: %m%n" />
<!-- "%-5p: [%t] [%c{3}.%M(%L)] | %m%n" -->
</layout>
</appender>
<appender name="EMAIL_QQ" class="org.apache.log4j.net.SMTPAppender">
<param name="Threshold" value="INFO"/>
<param name="BufferSize" value="128" />
<param name="SMTPHost" value="smtp.qq.com" />
<param name="SMTPUsername" value="" />
<param name="SMTPPassword" value="" />
<param name="From" value="" />
<param name="To" value="" />
<param name="Subject" value="测试邮件发送" />
<param name="LocationInfo" value="true" />
<param name="SMTPDebug" value="true" />
<layout class="org.cjj.log4j.extend.PatternLayout_zh">
<param name="ConversionPattern" value="[%d{ISO8601}] %-5p %c %m%n"/>
</layout>
</appender>
<!--- 异步测试,当日志达到缓存区大小时候执行所包的 appender -->
<appender name="ASYNC_test" class="org.apache.log4j.AsyncAppender">
<param name="BufferSize" value="10"/>
<appender-ref ref="EMAIL_QQ"/>
</appender>
<!-- 设置包限制输出的通道 -->
<category name="com.package.name" additivity="false"><!-- 日志输出级别,起码可以有 5 个级别,可以扩展自己的级别,邮件发送必须是 ERROR 级别不好用,所以最后自己扩展一个邮件发送级别 -->
<level value="ERROR" />
<appender-ref ref="filelog_daily" />
<appender-ref ref="daily_appender" />
<appender-ref ref="console" />
<appender-ref ref="ASYNC_test" />
</category>
</log4j:configuration>
Web 配置 log4j, 需求增加以下内容到 WEB-INF/web.xml
${smilecargo.root}
是 web 工程相对路径
配置时出现如下问题:
log4j:ERROR setFile(null,true) call failed.
java.io.FileNotFoundException: /home/work/log/web.log (No such file or directory)
at java.io.FileOutputStream.open0(Native Method)
at java.io.FileOutputStream.open(FileOutputStream.java:270)
at java.io.FileOutputStream.<init>(FileOutputStream.java:213)
at java.io.FileOutputStream.<init>(FileOutputStream.java:133)
at org.apache.log4j.FileAppender.setFile(FileAppender.java:294)
at org.apache.log4j.FileAppender.activateOptions(FileAppender.java:165)
at org.apache.log4j.DailyRollingFileAppender.activateOptions(DailyRollingFileAppender.java:223)
at org.apache.log4j.config.PropertySetter.activate(PropertySetter.java:307)
at org.apache.log4j.xml.DOMConfigurator.parseAppender(DOMConfigurator.java:295)
at org.apache.log4j.xml.DOMConfigurator.findAppenderByName(DOMConfigurator.java:176)
at org.apache.log4j.xml.DOMConfigurator.findAppenderByReference(DOMConfigurator.java:191)
at org.apache.log4j.xml.DOMConfigurator.parseChildrenOfLoggerElement(DOMConfigurator.java:523)
at org.apache.log4j.xml.DOMConfigurator.parseCategory(DOMConfigurator.java:436)
at org.apache.log4j.xml.DOMConfigurator.parse(DOMConfigurator.java:1004)
at org.apache.log4j.xml.DOMConfigurator.doConfigure(DOMConfigurator.java:872)
at org.apache.log4j.xml.DOMConfigurator.doConfigure(DOMConfigurator.java:778)
at org.apache.log4j.helpers.OptionConverter.selectAndConfigure(OptionConverter.java:526)
at org.apache.log4j.LogManager.<clinit>(LogManager.java:127)
at org.apache.log4j.Logger.getLogger(Logger.java:104)
at org.apache.commons.logging.impl.Log4JLogger.getLogger(Log4JLogger.java:262)
at org.apache.commons.logging.impl.Log4JLogger.<init>(Log4JLogger.java:108)
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
at org.apache.commons.logging.impl.LogFactoryImpl.createLogFromClass(LogFactoryImpl.java:1025)
at org.apache.commons.logging.impl.LogFactoryImpl.discoverLogImplementation(LogFactoryImpl.java:844)
at org.apache.commons.logging.impl.LogFactoryImpl.newInstance(LogFactoryImpl.java:541)
at org.apache.commons.logging.impl.LogFactoryImpl.getInstance(LogFactoryImpl.java:292)
at org.apache.commons.logging.impl.LogFactoryImpl.getInstance(LogFactoryImpl.java:269)
at org.apache.commons.logging.LogFactory.getLog(LogFactory.java:655)
at org.springframework.util.PropertyPlaceholderHelper.<clinit>(PropertyPlaceholderHelper.java:40)
at org.springframework.web.util.ServletContextPropertyUtils.<clinit>(ServletContextPropertyUtils.java:38)
at org.springframework.web.util.Log4jWebConfigurer.initLogging(Log4jWebConfigurer.java:128)
at org.springframework.web.util.Log4jConfigListener.contextInitialized(Log4jConfigListener.java:49)
at org.eclipse.jetty.server.handler.ContextHandler.callContextInitialized(ContextHandler.java:890)
at org.eclipse.jetty.servlet.ServletContextHandler.callContextInitialized(ServletContextHandler.java:532)
at org.eclipse.jetty.server.handler.ContextHandler.startContext(ContextHandler.java:853)
at org.eclipse.jetty.servlet.ServletContextHandler.startContext(ServletContextHandler.java:344)
at org.eclipse.jetty.webapp.WebAppContext.startWebapp(WebAppContext.java:1514)
at org.eclipse.jetty.maven.plugin.JettyWebAppContext.startWebapp(JettyWebAppContext.java:359)
at org.eclipse.jetty.webapp.WebAppContext.startContext(WebAppContext.java:1476)
at org.eclipse.jetty.server.handler.ContextHandler.doStart(ContextHandler.java:785)
at org.eclipse.jetty.servlet.ServletContextHandler.doStart(ServletContextHandler.java:261)
at org.eclipse.jetty.webapp.WebAppContext.doStart(WebAppContext.java:545)
at org.eclipse.jetty.maven.plugin.JettyWebAppContext.doStart(JettyWebAppContext.java:434)
at org.eclipse.jetty.util.component.AbstractLifeCycle.start(AbstractLifeCycle.java:68)
at org.eclipse.jetty.util.component.ContainerLifeCycle.start(ContainerLifeCycle.java:131)
at org.eclipse.jetty.util.component.ContainerLifeCycle.doStart(ContainerLifeCycle.java:113)
at org.eclipse.jetty.server.handler.AbstractHandler.doStart(AbstractHandler.java:113)
at org.eclipse.jetty.server.handler.ContextHandlerCollection.doStart(ContextHandlerCollection.java:167)
at org.eclipse.jetty.util.component.AbstractLifeCycle.start(AbstractLifeCycle.java:68)
at org.eclipse.jetty.util.component.ContainerLifeCycle.start(ContainerLifeCycle.java:131)
at org.eclipse.jetty.util.component.ContainerLifeCycle.doStart(ContainerLifeCycle.java:113)
at org.eclipse.jetty.server.handler.AbstractHandler.doStart(AbstractHandler.java:113)
at org.eclipse.jetty.util.component.AbstractLifeCycle.start(AbstractLifeCycle.java:68)
at org.eclipse.jetty.util.component.ContainerLifeCycle.start(ContainerLifeCycle.java:131)
at org.eclipse.jetty.server.Server.start(Server.java:449)
at org.eclipse.jetty.util.component.ContainerLifeCycle.doStart(ContainerLifeCycle.java:105)
at org.eclipse.jetty.server.handler.AbstractHandler.doStart(AbstractHandler.java:113)
at org.eclipse.jetty.server.Server.doStart(Server.java:416)
at org.eclipse.jetty.util.component.AbstractLifeCycle.start(AbstractLifeCycle.java:68)
at org.eclipse.jetty.maven.plugin.AbstractJettyMojo.startJetty(AbstractJettyMojo.java:467)
at org.eclipse.jetty.maven.plugin.AbstractJettyMojo.execute(AbstractJettyMojo.java:333)
at org.eclipse.jetty.maven.plugin.JettyRunMojo.execute(JettyRunMojo.java:180)
at org.apache.maven.plugin.DefaultBuildPluginManager.executeMojo(DefaultBuildPluginManager.java:134)
at org.apache.maven.lifecycle.internal.MojoExecutor.execute(MojoExecutor.java:207)
at org.apache.maven.lifecycle.internal.MojoExecutor.execute(MojoExecutor.java:153)
at org.apache.maven.lifecycle.internal.MojoExecutor.execute(MojoExecutor.java:145)
at org.apache.maven.lifecycle.internal.LifecycleModuleBuilder.buildProject(LifecycleModuleBuilder.java:116)
at org.apache.maven.lifecycle.internal.LifecycleModuleBuilder.buildProject(LifecycleModuleBuilder.java:80)
at org.apache.maven.lifecycle.internal.builder.singlethreaded.SingleThreadedBuilder.build(SingleThreadedBuilder.java:51)
at org.apache.maven.lifecycle.internal.LifecycleStarter.execute(LifecycleStarter.java:128)
at org.apache.maven.DefaultMaven.doExecute(DefaultMaven.java:307)
at org.apache.maven.DefaultMaven.doExecute(DefaultMaven.java:193)
at org.apache.maven.DefaultMaven.execute(DefaultMaven.java:106)
at org.apache.maven.cli.MavenCli.execute(MavenCli.java:863)
at org.apache.maven.cli.MavenCli.doMain(MavenCli.java:288)
at org.apache.maven.cli.MavenCli.main(MavenCli.java:199)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.codehaus.plexus.classworlds.launcher.Launcher.launchEnhanced(Launcher.java:289)
at org.codehaus.plexus.classworlds.launcher.Launcher.launch(Launcher.java:229)
at org.codehaus.plexus.classworlds.launcher.Launcher.mainWithExitCode(Launcher.java:415)
at org.codehaus.plexus.classworlds.launcher.Launcher.main(Launcher.java:356)
at org.codehaus.classworlds.Launcher.main(Launcher.java:47)
log4j:ERROR Either File or DatePattern options are not set for appender [file].
解决方案:
这种情况一般是 log 文件的路径不对,要不是文件路径不存在,要不就是无权限写入。
因为平时使用 SmartGit 这样一个 Git client,所以也没有太大注意 Git 中不同阶段撤回的方式,虽然平时接触过 git reset
的 --soft
和 --hard
来撤销已提交的 commit,但没有形成一个系统的知识体系。大家都知道 Git 是一个分布式版本控制,所以 Git 会有一个本地库,和一个远端库,而平时提交代码的时候,一般也都是先从本地工作区提交代码
git add .
git commit -s
git push
这几个步骤,虽然平淡无奇,但是展开说,就体现了 Git 的重要的环节,一段代码的提交顺序:
工作区 -> git add . -> 暂存区 -> git commit -> 本地仓库 -> git push -> 远程仓库
这里就要提到 Git 中的四个区:
被追踪的文件,在未进入和进入上述四个区之后分别有一个状态,所以一共有五个状态:
在了解这几个基本概念之后,如何检查本地的修改,以及如何查看不同状态之间的修改,这就要用到 git diff
命令。
直接使用 git diff
命令,能够查看已修改,未暂存的内容
使用 git diff --cache
来查看已暂存,未提交的内容
使用 git diff origin/master master
来查看已提交,未推送的差异。
工作区 暂存区 本地仓库 远程仓库
\ / \ / \ /
\ / \ / \ /
git diff git diff --cache git diff origin/master master
图解
在知道如何查看四个不同区之间的差异后,如何使用 git reset
来撤销呢?
如果只是在编辑器中修改了文件的内容,还未使用 git add
将修改提交到暂存区,那么可以使用 git checkout .
或者 git checkout -- <file>
来丢弃本地全部修改或者丢弃某文件的修改。
可以将 git add .
和 git checkout .
看做一对反义词,修改完成后,如果想 Git 往前进一步,让修改进入暂存区,执行 git add .
如果向后退则执行 git checkout .
如果已经执行了 git add
,意味着暂存区中已经有了修改,但是需要丢弃暂存区的修改,那么可以执行 git reset
对于已经被 Git 追踪的文件,可以使用
git reset <file>
来单独将文件从暂存区中丢弃,将修改放到工作区。
对于从来没有被 Git 追踪过,是 new file 的文件,则需要使用:
git reset HEAD <file>
来将新文件从暂存区中取出放到工作区。
如果确定暂存区中的修改完全不需要,则可以使用
git reset --hard
直接将修改抛弃,谨慎使用 –hard 命令, 暂存区中所有修改都会被丢弃。修改内容也不会被重新放到工作区。
对于已经本地的提交,也就是使用 git add
并且执行了 git commit
的修改,这时候本地的修改已经进入了本地仓库,而这是需要撤销这一次提交,或者本地的多次提交,怎么办?
git reset --hard origin/master
同样还是 git reset
命令,但是多了 origin/master
,origin
表示远端仓库的名字,默认为 origin,可能也有其他自己的名字,origin/master
表示远程仓库,既然本地的修改已经不再需要,那么从远端将代码拉回来就行。
不过不建议直接使用 git reset --hard origin/master
这样太强的命令,如果想要撤销本地最近的一次提交,可以使用
git reset --soft HEAD~1
这行命令表示,将最近一次提交 HEAD~1
从本地仓库回退到暂存区,--soft
不会丢弃修改,而是将修改放到暂存区,后续继续修改,或者丢弃暂存区的修改就可以随意了。如果要撤销本地两次修改,则改成 HEAD~2
即可,其他同类。
不过要注意的是,已经提交到远端的提交,不要使用 git reset
来修改,对于多人协作项目会给其他人带来很多不必要的麻烦。
对于已经推送的修改,原则上是不要撤销的,不过 Git 给了使用者充分的自由,在明确自己在做什么的情况下,可以使用 git push -f
使用 force 选项来将本地库 force 覆盖远端仓库,强制 push 到远端。
对于个人,一个人使用的项目使用这样的方式,并没有太大问题,但是如果对于多人项目,如果你强行改变了远端仓库,别人再使用的时候就会出现很多问题,所以使用 git push -f
时一定要想清楚自己在做什么事情。
MyBatis 是 Java 系的 ORM(Object Relational Mapping) 框架,提供了非常简洁的编程接口。用简单的话来说就是可以将数据库表映射到 Object 中 MyBatis 就是中间辅助处理的框架。
类似于 Python 中的 [[SQLAlchemy]]。
分为三层
基础支持层包含了如下模块
核心处理层包括
mybatis-config.xml
配置文件、映射配置文件以及 Mapper 接口中的注解信息,解析后的配置信息会形成相应的对象并保存到 Configuration 对象中接口层相对较简单,核心是 SqlSession
接口,接口定义了 MyBatis 暴露给应用程序的 API。
所以如果要使用 MyBatis 基本有如下几个步骤:
SqlSession 是 MyBatis 关键对象,持久化操作的对象,类似 JDBC 中 Connection。SqlSession 对象完全包含以数据库为背景的所有执行 SQL 操作的方法,底层封装了 JDBC 连接。每个线程都应该有自己的 SqlSession 实例,SqlSession 实例线程不安全,不能共享,绝对不要将 SqlSession 实例引用放到类静态字段或者实例字段中。使用完 SqlSession 一定关闭。
Mapper 文件针对 SQL 文件构建。
select 语句用来映射查询语句。
<select id="selectUser" parameterType="int" resultType="hashmap">
SELECT * FROM USER WHERE ID = #{id}
</select>
这个语句被称为 selectUser,接受 int 参数,返回 HashMap 类型。
比如
<insert id="insertUser">
insert into USER (id, username, password, email, address)
values (#{id},#{username},#{password},#{email},#{address})
</insert>
sql 元素用来定义可重用的 SQL 代码。
如果 parameterType 传入一个对象,那么 #{id}
在查询时会去对象属性查询。
<insert id="insertUser" parameterType="User">
insert into USER (id, username, password, email, address)
values (#{id},#{username},#{password},#{email},#{address})
</insert>
ResultMaps 元素是 MyBatis 中最重要最强大的元素,告诉 MyBatis 从结果集中取出数据转换成 Java Object。
MyBatis 是一个比较大的项目,下面包含了很多子项目,如果看这个项目列表就能够清晰的看到一些
Ant 则直接在 classpath 引入 jar 包,Maven 则
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>x.x.x</version>
</dependency>
使用 mybatis-spring 将 MyBatis 无缝嵌入到 Spring 中。
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>x.x.x</version>
</dependency>
mybatis generator 作为插件引入:
<project ...>
...
<build>
...
<plugins>
...
<plugin>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-maven-plugin</artifactId>
<version>1.3.7</version>
</plugin>
...
</plugins>
...
</build>
...
</project>
更多关于 MyBatis Generator 的内容可以参考这里
pandas 基于 numpy 构建,可以提供强大的数据处理分析能力。
两种数据类型,series 和 dataframe
数据集
series 是一种一维数据类型,每个元素都有各自的标签。可以当成带标签元素的 numpy 数组,标签可以是数字或者字符。Series 可以用元组、列表或者字典生成,如果没有为数据指定标签,那么会自动生成 0 到 N-1 的标签。
obj = Series([4, 7, -5, 3])
obj2 = Series([4, 7, -5, 3], index=['d', 'b', 'a', 'c'])
sdata = {'Ohio': 35000, 'Texas': 71000, 'Oregon': 16000, 'Utah': 5000}
值和标签分别可以通过 .values
和 .index
来访问
dataframe 是一个二维、表格型的数据结构,每个轴都有标签。
data = {'state': ['Ohio', 'Ohio', 'Ohio', 'Nevada', 'Nevada'],
'year': [2000, 2001, 2002, 2001, 2002],
'pop': [1.5, 1.7, 3.6, 2.4, 2.9]}
frame = DataFrame(data)
读 csv 文件
pandas.read_csv('path.csv')
写 csv 文件
data.to_csv('filepath.csv')
读 csv 这个函数有非常多的参数,比如读很大的文件,只想要读前 500 条,那么可以使用
pandas.read_csv('path.csv', nrows=500)
读 \t
分割的表格可以使用
pandas.read_table()
同理这个方法也有非常多的参数,可以具体参考文档。
还有读取等宽数据
pandas.read_fwf()
读取 Excel
pandas.read_excel()
读取 JSON
pandas.read_json()
读取网页
pandas.read_html()
读取 SQL 有
pandas.read_sql_table()
pandas.read_sql_query()
pandas.read_sql()
还有其他包括 Google BigQuery,HDFS,SAS 等等数据来源的,可以参考官方文档的 API reference。
nltk 是 Python 下一个自然语言处理相关的库,可以方便的实现分词,词性标注等等。
pip install nltk
然后在终端执行 python, 进入交互式编辑环境
>> import nltk
>> nltk.download()
下载相关模块
nltk.sent_tokenize(text)
#对文本按照句子进行分割
nltk.word_tokenize(sent)
#对句子进行分词
nltk.pos_tag()
from nltk.stem import WordNetLemmatizer
lemmatizer = WordNetLemmatizer()
lemmatizer.lemmatize(word)
frp 是 fatedier 的开源项目,frp 是一个高性能的反向代理应用,可以轻松地进行内网穿透,对外网提供服务,支持 TCP, UDP, http, https 等协议类型,并且 web 服务支持根据域名进行路由转发。
frp 用法和 ngrok 相似,但是 frp 比 ngrok 更加优秀。 配置过程很简单,但是也遇到一些问题,所以把过程记录下来。
frps.ini
服务端配置:
[common]
bind_port = 7000
frpc.ini
客户端配置
[common]
server_addr = x.x.x.x # 填写公网服务器 IP 地址
server_port = 7000
[ssh-computer-name]
type = tcp
local_ip = 127.0.0.1
local_port = 22
remote_port = 6000 #配置服务器端口
然后使用 ssh [email protected] -p 6000
或者 ssh -o Port=6000 [email protected]
来连接内网的机器。
再配置完之后可以使用 supervisor 来管理,实现进程死掉后自动重启。
Git 作为分布式版本控制系统,所有修改操作都是基于本地的,在团队协作过程中,假设你和你的同伴在本地中分别有各自的新提交,而你的同伴先于你 push 了代码到远程分支上,所以你必须先执行 git pull 来获取同伴的提交,然后才能 push 自己的提交到远程分支。而按照 Git 的默认策略,如果远程分支和本地分支之间的提交线图有分叉的话(即不是 fast-forwarded),Git 会执行一次 merge 操作,因此产生一次没意义的提交记录。
在 pull 操作的时候,使用 git pull --rebase
选项即可很好地解决上述问题,使用 -r
或者 --rebase
的好处是,Git 会使用 rebase 来代替 merge 的策略。
使用 man git-merge
中的示例图说明:
A---B---C remotes/origin/master
/
D---E---F---G master
如果执行 git pull 之后,提交线是:
A---B---C remotes/origin/master
/ \
D---E---F---G---H master
结果是多出了 H 这个 无意义的提交。如果执行 git pull -r
的话,提交就是:
remotes/origin/master
|
D---E---A---B---C---F'---G' master
本地的两次提交就使用 rebase 重新添加到了远端的提交之后,多余的 merge 无意义提交消失。
在了解 git pull -r
的前提下,来看一下如何使用 rebase 命令来将本地的多个提交合并为一次提交。
假设本地 Git 仓库中因为临时提交产生了一些 commits
commit 8b465db3672a24710207d91af74d61cee975b208
Author: Ein Verne
Date: Thu Nov 30 20:25:52 2017 +0800
Third commit
commit 821476d2b043e85d131483279e23778aa3fd1241
Author: Ein Verne
Date: Thu Nov 30 14:07:08 2017 +0800
Second commit
commit 51912266c1634dd2f0848071cc311975b6aad730
Author: Ein Verne
Date: Thu Nov 23 20:39:42 2017 +0800
Init commit
假设我们需要将第二次提交 821476d2b043e85d131483279e23778aa3fd1241
和 第三次提交 8b465db3672a24710207d91af74d61cee975b208
合并为一次提交,可以先使用
git rebase -i 5191226
最后一次不需要修改的 commit id,然后进入 vi 的提交信息的编辑模式。
pick 821476d Second commit
pick 8b465db Third commit
# Rebase 5191226..8b465db onto 5191226 (2 command(s))
#
# Commands:
# p, pick = use commit
# r, reword = use commit, but edit the commit message
# e, edit = use commit, but stop for amending
# s, squash = use commit, but meld into previous commit
# f, fixup = like "squash", but discard this commit's log message
# x, exec = run command (the rest of the line) using shell
# d, drop = remove commit
#
# These lines can be re-ordered; they are executed from top to bottom.
#
# If you remove a line here THAT COMMIT WILL BE LOST.
#
# However, if you remove everything, the rebase will be aborted.
#
# Note that empty commits are commented out
这里可以看到,上方未注释部分填写要执行的命令,下方注释部分为支持的指令说明。指令部分由命令,commit hash 和 commit message 组成。
这里
这里只要将第三次提交前的 pick
修改为 squash
,就可以将该 commit 合并到第二次提交。修改之后保存 :wq
退出。
pick 821476d Second commit
squash 8b465db Third commit
然后会进入 commit message 界面,在该界面中修改合适的提交信息,将两次的 commit 合并为一次,保存退出即可完成合并。
注意:git rebase
是一个比较危险的命令,如果一旦中途出现错误,可以使用 git rebase --abort
来终止 rebase,回到没有合并之前的状态。
如果想要合并最近的多次提交,在 rebase 进入交互模式时,可以指定范围比如
git rebase -i HEAD~8
选取最近的 8 次提交。
在进入 rebase -i
交互模式时,更换提交信息的顺序,保存即可修改本地提交的 commit 顺序。比如
pick 821476d Second commit
pick 8b465db Third commit
修改为
pick 8b465db Third commit
pick 821476d Second commit
可以更换次序。
git rebase
操作应该只用于本地尚未提交到远程仓库的 commit,一旦 push 到远端仓库,则不再允许修改 commit,否则可能会给其他开发者带来很多麻烦。尤其是多人协作时,千万要注意。
Linux 下按照正则过滤文本的命令 grep 非常强大,grep 能够把正则匹配的行打印出来。而 zgrep 则能够对压缩包内容进行正则匹配。zgrep 全称是 search compressed files for a regular expression
grep 的命令格式是
grep [option] pattern files
他的工作方式是,在一个或者多个文件中根据正则搜索匹配内容,将搜索的结果输出到标准输出,不更改源文件内容。
-i 忽略字符大小写区别
-v 显示不包含正则的所有行
关于更多的 grep 的内容可以参考另外一篇文章,zgrep 和 grep 用法类似,不过操作的对象是压缩的内容。支持 bzip2,gzip,lzip, xz 等等。
但如果想要过滤 Nginx 的 access_log.gz 的压缩文件的内容,如果先解压,然后过滤出有用的文本,再把文件压缩回去,这就变的非常不方便。
gunzip access_log.gz
grep "/api" access_log
gzip access_log
需要使用三个命令来实现文件的过滤,其实 Linux 下可以使用 zgrep
来一步完成
zgrep "/api" access_log.gz
和 grep 类似, zgrep
也可以指定多个文件同时进行搜索过滤
zgrep "/api" access_log.gz access_log_1.gz
既然提到了不解压搜索压缩包内容,.gz
的文件可以使用 zgrep
,而对于 .tar.gz
文件
zcat access.tar.gz | grep -a '/api'
zgrep -a "/api" access.tar.gz
其实这些带 z
的命令都包含在 Zutils 这个工具包中,这个工具包还提供了
zcat 解压文件并将内容输出到标准输出
zcmp 解压文件并且 byte by byte 比较两个文件
zdiff 解压文件并且 line by line 比较两个文件
zgrep 解压文件并且根据正则搜索文件内容
ztest - Tests integrity of compressed files.
zupdate - Recompresses files to lzip format.
这些命令支持 bzip2, gzip, lzip and xz 格式。