常要在 Linux 下分析日志或其他类型的文件,基本用的命令也就 grep, awk, sed, cut, vim, cat, find, xargs, tail, more 或 less。本人工作平台为 Mac OS X, 而 Mac 下的 grep, sed, awk 的行为与 Linux 下的 GNU 标准的相应命令是有差别的, 所以我总是在 Mac 下安装 GNU 的 grep, sed, awk 等命令来替代系统默认的。
比如安装下面的命令
$ brew install findutils gawk gnu-sed grep
以上会安装 GNU 的 find, awk, sed 和 grep 命令,使用时要加个前缀,如 gfind, gawk, gsed 或 ggrep,也可以设置别名或符号链接来替换掉系统的相应命令。
注: brew install coreutils
会安装众多 Linux 下的基本命令替代品,ls, cat, cut 都在其中,使用它也是要加上前缀 g
, 如 gls, gcut 等。
以下 grep, find, grep, sed 以 GNU 的行为为准。
grep 相关的命令
用了这么久的 grep, 反正它是用来查找字符串的,还能用正则表达式匹配。那么它的全名是什么呢?Global Regular Expression Print (GREP)。
高亮显示匹配的字符串
这个效果很重要,不然显示出了字符串还不清楚哪里匹配了,所以最好用个 alias
grep="grep --color=auto --exclude-dir={.bzr,CVS,.git,.hg,.svn}"
以下各种使用方式
$ cat planets.txt | grep -i e # 忽略大小写匹配
$ cat planets.txt | grep -e "[p|P]" # 使用正则表达式匹配
$ cat planets.txt | grep -P -e "[p|P]{1,3}" # 使用 Perl 风格正则表达式
搜索文件
$ grep -r --include "*.java" "print" src # 从 src 目录中的 *.java 中递归查找
输出捕获的分组
打印出匹配的字符串并不需总是要用 awk
分隔再打印,用 grep -oP
或 egrep -o
会更方便,如
$ echo 'employee_id=1234' | egrep -o '\d+'
1234
$ echo 'employee_id=1234' | grep -oP 'employee_id=\K(\d+)' # Mac OS X 下可用 ggrep, P 为 Perl 正则表达式, \K 是 Perl 用来实现 look-behind, 如从 "=" 开始
1234
$ echo 'employee_id=1234' | grep -oP '(?<==)(\d+)' # 另一种 lookbehind 方式, '(?<==)' 中的?<=
指定从后它后的=
号开始
1234
$ echo 'foo something bar' | grep -oP '(?<=foo )\w+(?= bar)' # 用这个了理解一下 grep -P Perl 的 look-behind 和 look-ahead
something
其他几种输出分组的方式
$ echo 'employee_id=1234' | sed 's/^.*employee_id=\([0-9]*\).*$/\1/'
1234
$ echo employee_id=1234 | sed -E 's/employee_id=([0-9]+)/\1/g' # -E extended regex
1234
$ echo 'employee_id=1234' | awk -F= '{print $2}'
1234
$ foo='employee_id=1234' # 以下是 bash 的方式
$ echo ${foo%%=*}
employee_id
$ echo ${foo#*=}
1234
参考:Can not extract the capture group with neither sed nor grep
控制台的内容直接送到 vim 编辑器中
未打开 vim, 可用管道操作 xxx | vi -
如:
$ ls -l | grep py | vi - # 这样会打开 vim 编辑器,显示
ls -l |grep py
命令的输出内容
如果打开了 vim, 那么在 vim 中命令模式中输入
: read !ls -l | grep py # 就会在当前 vim 编辑器中添加
ls -l | grep py
命令的输出内容
打开 vim 的同时打开语法高亮, 如果 ~/.vimrc 中已打开语法高亮,但 | vi -
没有告知文件类型,所以需要 vim 启动时加上 set syntax=json 或 set filetype=json
$ echo '{"age": 28}' | vi - -c 'set syn=json' # 或者 set syntax=json, set filetype=json 或 set ft=json
$ echo '{"age": 18}' | vi -
也可以进入了 vim 后,在它的命令行下设置 :set syntax=json 或 :set filetype=json, 或简单的用 :set syn=json 或 :set ft=json。
另外,假如用 vim 打开一个大文件,因为加载过多插件的因素滚动慢,可以用 vi -u NONE bigfile.json
来打开文件。
切分字符串
这也用到的命令就是 cut 或 awk,cut 能力比较弱,只能用单字符切割字符串,看如下几个使用方式
$ echo "aa,bb,,cc:dd" | cut -d, -f1
aa # -d 后指定用,
号分隔,-f 输出哪个字段
$ echo "aa,bb,,cc:dd" | cut -d, -f1 -f4 # 或者用 -f1,4
aa,cc:dd # 输出多个字段时又会用 -d 指定的分隔符连接
$ echo "aa,bb,,cc:dd" | cut -d, -f4 | cut -d: -f1
cc #由于不支持模式分隔,所以要进行两次切分
另一种方法就是用 awk 命令,awk 简单是一个编程语言,它名字来源于它的三位作者的名字首字母(Aho, Weinberger and Kernighan), 不是 awkward 的意思。它的功能还是很强,我们且在这里看看怎么分隔字符串
$ echo "aa,bb,,cc:dd" | awk -F "(,*)|:" '{print NF,$1,$2,$3,$4}'
4 aa bb cc dd # 共四个字符,且每个字段的内容,-F 中支持正则表达式
实际使用时应测试好它所支持的正则表达式的语法,以前好像还尝试过 awk 的正则表达式的切割功能,还以为需要用 Python 的实现类似的操作。
曾经想过用 Python 的切分字符串的方式 (cut.py)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
#!/usr/bin/env python import sys from optparse import OptionParser import re parser = OptionParser() parser.add_option('-d', '--delimiter', default='\t') parser.add_option('-f', '--fields') (options, args) = parser.parse_args() indexes = [(int(i) - 1) for i in options.fields.split(',')] regex = re.compile(options.delimiter) for line in sys.stdin: fields = regex.split(line.rstrip()) out = ' '.join([fields[i] for i in indexes]) print(out) |
然后执行
$ echo "aa,bb,,cc:dd" | cut.py -d",+|:" -f1,2,4 # Python 的正则表达式也需多测试
aa bb dd
输出匹配后的下一行
通常我们的需求是用 grep 查找到并输出匹配行,有时我们想输出匹配行的下一行,或下几行,比如说对 ini 文件
[memory]
max=8G
[bandwidth]
max=4G
我们想要找到含有 [memory] 的下一行的 max 值而不是别处的 max
可以用 awk 命令
cat test.ini | awk '/\[memory\]/{getline;print}' #\[ 为转义 [ 符号
max=8G
或者用 sed 命令(sed 是什么?Stream EDitor, 流编程器,也可说是像 awk 一样另成一种编程语言
cat test.ini | sed -n '/[memory]/{n;p;}' # 很像 awk 的 getline;print
max=8G
sed 中的命令非常精简,都用单字母,如 a: append, s: substitue, n: next, p: print。如果要用来处理字符串替换的操作 sed 就是最好的编程工具。
grep 本身也能完成类似的功能
$ cat test.ini | grep -A1 '\[memory\]' # 往下多显示一行 -A, --after-context=NUM 从当前往下多少行,往前是 -B
[memory]
max=8G
前面说过 awk 本身就是个编程语言的,所以还能加入更多代码
$ cat test.ini | awk '/\[memory\]/{for(i=0;i<4;i++)getline;print $0}'
max=4G
//或者
$ cat test.ini | awk -v lines=4 '/\[memory\]/{for(i=lines;i;--i) getline; print $0}'
max=4G
awk 和 sed 是可以直接读取文件的,所以前面的命令可以不用 cat test.ini 到它的管道操作,而可以
$ awk '/\[memory\]/{for(i=0;i<4;i++)getline;print $0}' test.ini
$ sed -n '/[memory]/{n;p;}' test.ini
sort 命令按某列排序
sort 命令可以帮我们对文本行排序,默认以整行字符串为比较单位,主要参数有
- -n: --numberic-sort, 比较数字大小而非字符串来排序
- -r:--reverse, 逆序排列
- -f: --ignore-case, 忽略大小写排序
这儿我们了解另一种排序需求,即按文件中某一列进行排序,如果有如下文件 planets.txt, 内容为
1 2 3 |
Moon,3 Sun,1 Earth,2 |
我们想要按第二列进行序列后输出全部内容,那么命令为
$ sort -t, -k2 planets.txt
Sun,1
Earth,2
Moon,3
解析:
- -t: --field-separator, 以逗号为分割符, 默认是以空格分割
- -k2: --key, 先取第二列为排序依据
除此之外,sort 还有以下两个有意思的功能
- -R: --random-sort, 随机打乱顺序
- -u: --unique, 去重功能,从此告别 sort | uniq 这样的管道操作
看一下 -u 去重的功能,修改 planets.txt 内容如下
1 2 3 4 |
Moon,3 Sun,1 Earth,2 XXX,1 |
$ sort -t, -k2 -u planets.txt
Sun,1
Earth,2
Moon,3
在用 -t, -k2 时还是按照重复列来去重的。
批处理利器 xargs 命令
提起 xargs 命令时让人不禁想起 find 这一绝配,通常是 find 找到某些符合条件的文件后用管道传递给 xargs 去批量操作,有时也会 find -exec 命令来操作。比如
$ find . -type f -size +800M -exec rm {} \; //或
$ find . -type f -size +800M -exec rm {} +
除此之外也会用到 xargs 命令与 find 配合
$ find . | xargs -0 rm -f
$ find . -type f | xargs -I {} cp {} /var/www
上面第一个命令,找到的内容是放到 rm -f
后面,可以不用 {}
的形式; 如果不加 -0
(数字零)参数,rm 删除太多文件时会出现 Argument list too long 的错误。第二个命令因为内容放在 cp
命令中间,所以要用 {}
点位符; 同样的,如果操作的文件太多,在 xargs 命令中也要加上 -0
参数。
除此之外,xargs 还能把单行转成多行
$ cat <<EOF | xargs
> a b c d
> e f
> g
> EOF
a b c d e f g
更多功能见文后的链接。
链接:
- Install and Use GNU Command Line Tools on macOS/OS X
- How to replace Mac OS X utilities with GNU core utilities?
- xargs命令
本文链接 https://yanbin.blog/frequently-used-linux-shell-script/, 来自 隔叶黄莺 Yanbin Blog
[版权声明] 本文采用 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 进行许可。
很有用==,之前都不知道grep是可以高亮显示匹配字符的。。。