记录自己常用的一些 Linux Shell 脚本

常要在 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 中递归查找 print 字符串

输出捕获的分组

打印出匹配的字符串并不需总是要用 awk 分隔再打印,用 grep -oPegrep -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)

然后执行

$ 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 命令可以帮我们对文本行排序,默认以整行字符串为比较单位,主要参数有

  1. -n:  --numberic-sort, 比较数字大小而非字符串来排序
  2. -r:--reverse, 逆序排列
  3. -f: --ignore-case, 忽略大小写排序

这儿我们了解另一种排序需求,即按文件中某一列进行排序,如果有如下文件 planets.txt, 内容为

我们想要按第二列进行序列后输出全部内容,那么命令为

$ sort -t, -k2 planets.txt
Sun,1
Earth,2
Moon,3

解析:

  1. -t: --field-separator, 以逗号为分割符, 默认是以空格分割
  2. -k2: --key, 先取第二列为排序依据

除此之外,sort 还有以下两个有意思的功能

  1. -R: --random-sort, 随机打乱顺序
  2. -u: --unique, 去重功能,从此告别 sort | uniq 这样的管道操作

看一下 -u 去重的功能,修改 planets.txt 内容如下

$ 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

更多功能见文后的链接。

链接:

  1. Install and Use GNU Command Line Tools on macOS/OS X
  2. How to replace Mac OS X utilities with GNU core utilities?
  3. xargs命令

本文链接 https://yanbin.blog/frequently-used-linux-shell-script/, 来自 隔叶黄莺 Yanbin Blog

[版权声明] Creative Commons License 本文采用 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 进行许可。

Subscribe
Notify of
guest

1 Comment
Inline Feedbacks
View all comments
tumars
tumars
4 years ago

很有用==,之前都不知道grep是可以高亮显示匹配字符的。。。