这是Linux命令拾遗系列的第二篇,本篇主要介绍Linux中与文本处理相关的命令,如xargs、grep、sed、awk等。
- # 打印输入内容到标准输出
- $ seq 3 | cat
- # -n 带行号输出
- $ seq 3 | cat -n
- # -A 可以用来查询特殊字符
- $ seq 3 | cat -A
- # tac可以倒序输出
- $ seq 3 | tac
- 3
- 2
- 1
-
less用于查看文件内容,如下:
- $ less app.log
-
同时,less还是一个可交互的命令,交互方式类似于vim,如下:
操作 | 描述 |
---|---|
Ctrl + f | 向后翻页(forward) |
Ctrl + b | 向前翻页(backward) |
g | 跳转到首行 |
G | 跳转到尾行,同时按Shift+g |
63G | 跳转到63行 |
j 或或 | 向下滚动一行 |
k 或 | 向上滚动一行 |
q | 退出less程序 |
/abc | 向后搜索abc 再按n继续搜索下一个abc,再按N搜索上一个abc |
?abc | 向前搜索abc 再按n继续搜索上一个abc,再按N搜索下一个abc |
F | 不断显示文件新内容,同时按Shift + f |
v | 在编辑器中打开当前文件 |
-N | 显示行号 (先按-,再按Shift + n,再按) |
-I | 忽略大小写搜索 (先按-,再按Shift + i,再按) |
-S | 不换行查看 (先按-,再按Shift + s,再按) |
-R | 保留颜色 (先按-,再按Shift + r,再按) |
-F | 一屏可展示,则直接输出 (先按-,再按Shift + f,再按) |
另外,less也经常用来查看命令输出的大量内容,比如ps -ef一般会显示大量内容,这会将之前命令的执行结果从屏幕上往前推很远,使用ps -ef | less就不会有这种烦恼了。
- # 显示前10行
- $ seq 20 | head -n10
- # 显示后10行
- $ seq 20 | tail -n10
- # 显示从第10行开始到末尾的行
- $ seq 20 | tail -n+10
- # 一直查看文件新追加的内容
- $ tail -f temp.txt
- # 生成16个字节的随机hex
- $ cat /dev/urandom | head -c 16 | xxd -ps
-
- # 统计行数,单词数,字节数,之所以有10字节,是因为把换行符也算进去了
- $ seq 5 | wc
- 5 5 10
-
- # 只统计行数
- $ seq 5 | wc -l
- 5
-
- # 排序,-n表示数值排序,-r表示倒序,-k1表示使用第一列排序
- $ seq 5 |sort -nrk1
- 5
- 4
- 3
- 2
- 1
-
- # uniq做分组计数,使用uniq前数据必须排好序,故前面要加sort
- $ (seq 6;seq 3 8) |sort|uniq -c
- 1 1
- 1 2
- 2 3
- 2 4
- 2 5
- 2 6
- 1 7
- 1 8
-
- # 并集
- cat a b | sort | uniq > c
- # 交集
- cat a b | sort | uniq -d > c
- # 差集
- cat a b b | sort | uniq -u > c
-
根据正则搜索内容,它会一行一行的拿出数据中的内容,然后看这一行是否匹配正则,匹配则输出这一行的内容。
- # 使用正则搜索,默认BRE,不支持+?,不支持\d
- $ seq 12|grep '11*'
- 1
- 10
- 11
- 12
- # -F纯字符串搜索,而不是当成正则搜索
- $ seq 12|grep -F '11*'
-
- # -w单词搜索,所以11这种搜不到
- $ seq 12|grep -w '1'
- 1
- # -E使用ERE正则搜索,支持+?,不支持\d
- $ seq 12|grep -E '1+'
- 1
- 10
- 11
- 12
- # -P使用PCRE正则搜索,支持+?,支持\d
- seq 12|grep -P '\d\d+'
- 10
- 11
- 12
-
- # -v反向搜索,显示不包含1的行
- seq 12|grep -v 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- # -o只输出匹配到的数据,而不是整行
- $ echo hello,java|grep -oP '\w+'
- hello
- java
- # -c显示搜索行数
- $ seq 12|grep -P '\d\d+' -c
- 3
- # -m限制搜索行数最多2行
- $ seq 12|grep -P '\d\d+' -m 2
- 10
- 11
-
- # 搜索10并也显示之后的2行(-A2)
- $ seq 12|grep -A2 -w 10
- 10
- 11
- 12
- # 搜索10并也显示之前的2行(-B2)
- $ seq 12|grep -B2 -w 10
- 8
- 9
- 10
- # 搜索10并也显示之前以及之后的2行(-C2)
- $ seq 12|grep -C2 -w 10
- 8
- 9
- 10
- 11
- 12
-
- # -r在当前目录递归的找文件,并在文件中找8080这个词,-n显示8080在文件中的行号
- $ grep -rn -w 8080 .
-
ls一般用来在当前目录的找文件
- # 列出当前目录的文件名
- ls
- # -l列出当前目录的文件,以及文件属性,如创建用户、时间、大小等
- ls -l
- # 在当前目录找txt后缀的文件
- ls *.txt
- # 列出当前目录的文件,按时间倒序显示
- ls -lt
- # 列出当前目录的文件,按大小倒序显示
- ls -lS
-
find一般用来递归的找文件
- # 当前目录递归查找txt后缀的文件,会深入到子目录中,-type f表示查找文件,不然输出结果可能会有目录
- find -name '*.txt' -type f
- # 查找大于800M的文件
- find . -type f -size +800M
- # 1分钟内修改过的文件
- find . -type f -mmin -1
- # 7天内修改过的文件
- find . -type f -mtime -7
-
作用:将标准输入流中的数据,转换为命令参数,并执行命令。
引入这个命令的原因是,有些命令不支持处理标准输入的数据,而只支持命令参数,如杀死进程的kill命令。
当我们想要杀死所有java进程时,可以这样做:
- # 使用pgrep找出java进程
- pgrep java
- 856
- 857
-
- # 再使用kill杀死这两个java进程
- kill 856 857
-
- # 写成一行命令,如下,利用了bash的命令替换语法
- kill `pgrep java`
- kill $(pgrep java)
-
- # 假如kill每次只支持一次传一个参数的话,可以用bash的for与while循环语法
- for pid in `pgrep java`;do kill $pid; done
- pgrep java | while read pid;do kill $pid;done
-
可以看到,对于上面的场景,命令越写越复杂,而xargs就可以很好的解决这个问题,如下:
- # 使用xargs,将输入流按空白分拆成参数,传给kill命令,等效于上面的 kill `pgrep java`
- pgrep java | xargs kill
- # 使用-n选项,将输入流按空白分拆成参数,每次传一个参数给kill命令,类似上面for与while循环实现
- pgrep java | xargs -n1 kill
-
下面体会一下xargs中常用的选项,如下:
- # xargs默认以空白分隔参数,不指定命令时,默认执行echo,并默认将尽可能多的参数传递给命令
- $ seq 20|xargs
- 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
- # 使用-d指定分隔符
- $ seq -s, 20|xargs -d,
- 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
-
注意:没有指定-d时,xargs默认使用空白分隔,这里的空白指的是空格、TAB与换行符,且多个空白符理解为一个空白,Linux中大多数需要分拆文本为列的命令,基本都遵从这个原则,如上面介绍过的sort,以及后面将要介绍的awk。
体会一下有无-d选项上的不同,如下:
- $ seq 20|xargs printf '"%s"\n'|xargs
- 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
-
- $ seq 20|xargs printf '"%s"\n'|xargs -d'\n'
- "1" "2" "3" "4" "5" "6" "7" "8" "9" "10" "11" "12" "13" "14" "15" "16" "17" "18" "19" "20"
-
在没有指定-d选项时,xargs默认会忽略掉',",\,而有-d时则不会忽略。
- # 使用-n或-L指定每次传参的数量
- $ seq 20|xargs -n4
- 1 2 3 4
- 5 6 7 8
- 9 10 11 12
- 13 14 15 16
- 17 18 19 20
-
- $ seq 20|xargs -L4
- 1 2 3 4
- 5 6 7 8
- 9 10 11 12
- 13 14 15 16
- 17 18 19 20
-
体会一下-n与-L的细节上的不同,如下:
- $ seq 20|xargs -n2
- 1 2
- 3 4
- 5 6
- 7 8
- 9 10
- 11 12
- 13 14
- 15 16
- 17 18
- 19 20
-
- $ seq 20|xargs -n2|xargs -n4
- 1 2 3 4
- 5 6 7 8
- 9 10 11 12
- 13 14 15 16
- 17 18 19 20
-
- $ seq 20|xargs -n2|xargs -L4
- 1 2 3 4 5 6 7 8
- 9 10 11 12 13 14 15 16
- 17 18 19 20
-
看起来好像是,在没指定-d时,-n默认使用空白符(包含空格、TAB与换行符)来分拆参数,而-L,默认使用换行符来分拆参数。
- # 使用-i后,可使用{}来作为参数的占位符
- seq 20|xargs -i echo 'id={}'
-
体会一下-i的细节,如下:
- $ seq 20|xargs -n2|xargs -i echo 'id={}'
- id=1 2
- id=3 4
- id=5 6
- id=7 8
- id=9 10
- id=11 12
- id=13 14
- id=15 16
- id=17 18
- id=19 20
-
看起来好像是,在没指定-d时,使用-i后,默认使用换行符来分拆参数。
- # 使用-p来调试xargs传参细节
- $ seq 20|xargs -i -p echo 'id={}'
- echo 'id=1' ?...y
- id=1
- echo 'id=2' ?...
-
- # 使用-P来并发运行命令,没有-P4时需要执行10s,有-P4只需要4s
- $ seq 4|xargs -n1 -P4 sleep
-
有些时候,利用xargs的-P选项,还可以做一些简单的压力测试哩!
xargs常与ls、find、grep配合,用来在指定文件中搜索内容,其中ls、find用来找文件,xargs将找到的文件名变成grep的参数,如下:
- # 在当前目录的所有xml文件中,搜索8080端口配置
- ls *.xml |xargs grep -w 8080
- # 在当前目录及子目录的xml文件中,搜索8080端口配置
- find -name '*.xml'|xargs grep -w 8080
-
一般用于替换修改文本数据,有流文本编辑器(Stream editor)之称,实际上,你也可以将其看做一个极其简化的脚本语言。
基本语法形如pattern action,sed会读取每一行到模式空间,看是否匹配pattern,如果匹配则执行action。
注:模式空间pattern space,后面会详细解释,现在理解为存储当前行数据的变量即可。
如sed '3,5 s/a/c/g'将第3到5行中的a替换为c,其中3,5为pattern部分,s/a/c/g为action部分,只有满足pattern条件的行,action才会执行,pattern部分可以省略,这样每一行都会执行action。
- # yes可以用来不断的重复生成字符串,以此作为我们的测试数据
- $ yes abcde|head -n5
- abcde
- abcde
- abcde
- abcde
- abcde
- # 第3到5行中的a替换为c,其中的g表示替换所有
- $ yes abcde|head -n5|sed '3,5 s/a/c/g'
- abcde
- abcde
- cbcde
- cbcde
- cbcde
-
另外pattern action还可以是如下的形式:
- # 第3行到第5的a替换为c,第2行到第4行的b替换为d
- $ yes abcde|head -n5|sed '3,5 s/a/c/g; 2,4 s/b/d/g'
- abcde
- adcde
- cdcde
- cdcde
- cbcde
- # 第3到5行中,其中3到4行执行a替换为c,第4到5行执行b替换为d
- $ yes abcde|head -n5|sed '3,5{3,4 s/a/c/g; 4,5 s/b/d/g}'
- abcde
- abcde
- cbcde
- cdcde
- adcde
- # 非第3到5行的行,将a替换为c
- $ yes abcde|head -n5|sed '3,5! s/a/c/g'
- cbcde
- cbcde
- abcde
- abcde
- abcde
-
sed默认会把action处理后的每一行打印出来,加上-n选项可以关掉默认打印,如下:
- # 显示1到3行,这里action为p,表示打印,-n用来关闭默认打印,不然1到3行会打印2遍,p一般都和-n配合使用
- $ seq 5|sed -n '1,3 p'
- 1
- 2
- 3
- # pattern部分可以使用正则表达式,注意sed中的正则也不能使用\d,且记得时常搭配-E选项
- # 注意pattern部分和action部分是可以随意组合的,也就是说正则形式的pattern也可以和s搭配使用
- $ seq 5|sed -n '/[2-4]/ p'
- 2
- 3
- 4
- # pattern部分也可以是逗号分隔的两个正则表达式,匹配从找到第一个正则表达式开始的行,到找到第二个正则表达式的行结束
- $ seq 5|sed -n '/[2]/,/[4]/ p'
- 2
- 3
- 4
- # 打印第1行,以及之后第间隔2行的行
- $ seq 5|sed -n '1~2 p'
- 1
- 3
- 5
- # 打印匹配行与之后的2行
- $ seq 5|sed -n '/^1$/,+2 p'
- 1
- 2
- 3
-
除了s(替换)与p(打印)外,还有d(删除)、i(插入)、a(追加)、c(修改)、q(退出)、l(打印特殊字符),如下:
- # 删除包含1和2的行
- $ seq 3|sed '/[1-2]/ d'
- 3
- # 向前插入一行,常用于设置csv标题
- $ seq 3|sed '1 i\id'
- id
- 1
- 2
- 3
- # 在最后一行之后追加一行,其中$表示最后一行
- $ seq 3|sed '$ a\id'
- 1
- 2
- 3
- id
- # 将第一行整行直接修改为id
- $ seq 3|sed '1 c\id'
- id
- 2
- 3
- # 打印前5行,因为sed执行到第5行,q命令让其退出了
- $ seq 9|sed '5q'
- 1
- 2
- 3
- 4
- 5
- # 显示特殊字符
- $ echo -ne '\r\n'|sed -n 'l0'
- \r$
-
另外,s(替换)还有一些细节,这些细节实际上非常有用,体会一下:
- # 替换可以使用正则的捕获组功能
- $ echo 'id=1,name=zs'|sed -E 's/id=(\w+),name=(\w+)/\1 \2/'
- 1 zs
- # g表示将所有的a替换为c
- $ echo 'a,a,a,a'|sed 's/a/c/g'
- c,c,c,c
- # 3g表示将第3次匹配到的a以及后面匹配到的a,都替换为c
- $ echo 'a,a,a,a'|sed 's/a/c/3g'
- a,a,c,c
- # 没有g只能替换第1次匹配
- $ echo 'a,a,a,a'|sed 's/a/c/'
- c,a,a,a
- # 3表示只替换第3次匹配到的a为c
- $ echo 'a,a,a,a'|sed 's/a/c/3'
- a,a,c,a
- # &表示之前匹配到的内容
- $ echo 'a,a,a,a'|sed 's/.,./[&]/g'
- [a,a],[a,a]
- # 大小写转换
- $ echo 'hello'|sed -E 's/.+/\U&/g'
- HELLO
- $ echo 'hello'|sed -E 's/.+/\u&/g'
- Hello
- $ echo 'HELLO'|sed -E 's/.+/\L&/g'
- hello
- $ echo 'HELLO'|sed -E 's/.+/\l&/g'
- hELLO
-
sed中有2个概念,模式空间pattern space与保留空间hold space,简单来说,可以将其看成2个变量,其中模式空间是局部变量,sed读到的当前行数据,会被保存其中,而保留空间是全局变量。
sed运行过程可描述为如下代码:
- hold_space="";
- while read pattern_space; do
- # sed script here
- done
-
理解了这个概念,就可以介绍下面这些action了,如下:
action | 描述 |
---|---|
n | 加载下一行文本到模式空间,覆盖模式空间原数据 |
N | 追加下一行文本到模式空间 |
P | 打印模式空间数据的第一行 |
D | 删除模式空间数据的第一行 |
: label | 标记待跳转位置 |
b label | 跳转到: label标记的位置,可用于实现分支判断与循环 |
- # 打印偶数行
- $ seq 9|sed -n 'n;p'
- 2
- 4
- 6
- 8
-
- $ seq -s, 9
- 1,2,3,4,5,6,7,8,9
- # 每3个一行,使用s切出新行,P打印第一行,D再删掉,如此往复直到D将模式空间数据全删掉
- $ seq -s, 9|sed 's/,/\n/3;P;D'
- 1,2,3
- 4,5,6
- 7,8,9
-
- $ seq 9
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- # 每3个一行,用ba跳转实现一个循环,配合N追加3行到模式空间,再将\n替换为,即可
- $ seq 9|sed ':a;N;0~3!{$!ba};s/\n/,/g'
- 1,2,3
- 4,5,6
- 7,8,9
-
-
-
分段
用sed来获取指定段中的内容,所谓段就是用空行分隔的多个行,如下:
比如需要获取eth0网卡的ip地址,如下:
- $ ifconfig|sed -nE '/\S/{:a;N;/\n$/!{$!ba}}; /eth0/s/.*inet (\S*).*/\1/gp'
- 172.21.117.1
-
运行过程:
保留空间action
action | 描述 |
---|---|
h | 将模式空间数据覆盖到保留空间 |
H | 将模式空间数据追加到保留空间 |
g | 将保留空间数据覆盖到模式空间 |
G | 将保留空间数据追加到模式空间 |
x | 交换模式空间与保留空间的数据 |
- # 倒序输出,运行过程如下:
- # sed处理第1行时会把1保存到保留空间
- # 处理第2行时,会先把保留空间的1追加到模式空间,追加后模式空间就是2 1了,然后又将其保存到保留空间
- # 接下来第3行就是3 2 1,如此往复,到最后一行时再输出内容即可
- $ seq 5|sed -n '1!G; $p; h;'
- 5
- 4
- 3
- 2
- 1
-
- # 过程与上面类似,不过每3行会打印一次并清空模式空间与保留空间罢了
- $ seq 9|sed -n 'G; 0~3{p;s/.*//g};h'
- 3
- 2
- 1
-
- 6
- 5
- 4
-
- 9
- 8
- 7
-
- # 打印匹配行,以及其前面4行,类似seq 9|grep -B4 7
- $ seq 9|sed -n 'H; x; 4,$s/^[^\n]*\n//; x; /^7$/{g;p}'
- 4
- 5
- 6
- 7
-
sed里面掺杂了操作保留空间的命令后,执行过程就变得非常烧脑了,你的大脑需要飞速的运转才行。
- $ echo hello%E7%BC%96%E7%A8%8B|sed 's/%/\\x/g'
- hello\xE7\xBC\x96\xE7\xA8\x8B
-
- $ echo hello%E7%BC%96%E7%A8%8B|sed 's/%/\\x/g'|xargs -d"\n" echo -e
- hello编程
-
awk是一个强大的文本处理工具,本质上可以看做是一门脚本语言了,可以用来对文本进行过滤、替换等操作,还能实现简单的统计以及类似SQL的join功能等。
awk基本语法如下:
- awk 'BEGIN{
- //your code
- }
- pattern1{
- //your code
- }
- pattern2{
- //your code
- }
- END{
- //your code
- }'
-
如下所示,分别求奇数行与偶数行的和:
- $ seq 1 5
- 1
- 2
- 3
- 4
- 5
-
- $ seq 1 5|awk 'BEGIN{print "odd","even"} NR%2==1{odd+=$0} NR%2==0{even+=$0} END{print odd,even}'
- odd even
- 9 6
-
这个程序还可以如下这样写:
- seq 1 5|awk 'BEGIN{print "odd","even"} {if(NR%2==1){odd+=$0}else{even+=$0}} END{print odd,even}'
-
这里使用了if语句,实际上awk的程序语法与C是非常类似的,所以awk也有else,while,for,break,continue,exit等,常见语法如下:
- if (condition) statement [ else statement ]
- while (condition) statement
- do statement while (condition)
- for (expr1; expr2; expr3) statement
- for (var in array) statement
- i++; i--;
- i > 0 ? 1 : 0
-
可以看到,awk程序在处理时,默认是一行一行处理的,注意我这里说的是默认,并不代表awk只能一行一行处理数据,接下来看看awk的分列功能,可通过-F选项提供,如下:
- $ cat temp.txt
- 1,6
- 2,7
- 3,8
- 4,9
- 5,10
-
- $ cat temp.txt |awk -F, '{printf "%s\t%s\n",$1,$2}'
- 1 6
- 2 7
- 3 8
- 4 9
- 5 10
-
这个例子用-F指定了,,这样awk会自动将读取到的每行,使用,分列,拆分后的结果保存在$1,$2...中,另外,你也可以使用$NF, $(NF-1)来引用最后两列的值,不指定-F时,awk默认使用空白字符分列。
注意这里面的printf "%s\t%s\n",$1,$2,printf是一个格式化打印函数,其实也可以写成printf("%s\t%s\n", $1, $2),只不过awk中函数调用可以省略括号。
另外,对于字符串拼接,awk不需要任何连接符号,只需要将两个字符串挨在一起即可,这和C语言中一样,不同于Java中使用+拼接字符串,如下:
- $ awk 'BEGIN{print "a""b"}'
- ab
- # 当然在中间加上空格,也是一样的,awk会忽略它
- $ awk 'BEGIN{print "a" "b"}'
- ab
- # 但如果你用,分隔起来,就不一样了,它相当于传给print两个参数,类似print("a","b")
- $ awk 'BEGIN{print "a","b"}'
- a b
-
awk支持一维数组,使用数组实现上面计算奇偶数和,如下:
- $ seq 1 5|awk 'BEGIN{print "odd","even"} {S[NR%2]+=$0} END{print S[1],S[0]}'
- odd even
- 9 6
-
注意,awk中的数组叫关联数组,即数组key可以是任意值,不一定是数字,概念上类似于java中的Map,如下:
- # 统计各进程的数量,显示数量最多的前4个
- $ ps h -eo comm|awk '{S[$0]++}END{for(k in S){print S[k],k}}'|sort -nr|head -n4
- 9 sshd
- 6 httpd
- 3 systemd
- 3 bash
-
如果要删除数组中的元素,使用delete S[k]即可。
上面已经提到了NR这个内置变量,awk还有如下内置变量
内置变量 | 作用 |
---|---|
$0 | 当前记录(这个变量中存放着整个行的内容) |
$i~$n | 当前记录的第n个字段 |
NF | 当前记录中的字段个数,就是有多少列 |
NR | 已经读出的记录数,就是行号,从1开始,如果有多个文件话,这个值也是不断累加中。 |
FNR | 当前记录数,与NR不同的是,这个值会是各个文件自己的行号 |
FS | 与-F功能类似,用来分列的,不过FS可以是正则表达式,默认是空白字符。 注:如果FS的值是空,代表每个字母拆分为一个 |
OFS | 与FS对应,指定print函数输出时的列分隔符,默认空格 |
RS | 记录分隔符,默认记录分隔符是\n 注:如果RS的值是空,代表按段划分记录 |
ORS | 与RS对应,指定print函数输出时的记录分隔符,默认\n |
FILENAME | 当前输入文件的名字 |
用2个例子体会一下:
- $ echo -n '1,2,3|4,5,6|7,8,9'|awk 'BEGIN{RS="|";FS=","} {print $1,$2,$3}'
- 1 2 3
- 4 5 6
- 7 8 9
- $ echo -n '1,2,3|4,5,6|7,8,9'|awk 'BEGIN{RS="|";FS=",";ORS=",";OFS="|"} {print $1,$2,$3}'
- 1|2|3,4|5|6,7|8|9,
-
总结:awk数据读取模式,总是以RS为记录分隔符,一条一条的读取记录,然后每条记录按FS拆分为字段。
再看看这个例子:
- $ seq 1 5|awk '/^[1-4]/ && !/^[3-4]/'
- 1
- 2
- $ seq 1 5|awk '$0 ~ /^[1-4]/ && $0 !~ /^[3-4]/{print}'
- 1
- 2
- $ seq 1 5|awk '$0 ~ /^[1-4]/ && $0 !~ /^[3-4]/{print $0}'
- 1
- 2
-
可以看到:
如下,看看用awk如何获取eht0网卡的ip地址:
- ifconfig|awk -v RS= '/eth0/{print $6}'
- 172.21.117.1
-
函数名 | 说明 | 示例 |
---|---|---|
sub | 替换一次 | sub(/,/,"|",$0) |
gsub | 替换所有,传入字符串被替换,返回替换次数 | gsub(/,/,"|",$0) |
gensub | 替换,返回替换后的字符串 | $0=gensub(/,/,"|","g",$0) |
match | 匹配,捕获内容在a数组中 | match($0,/id=(\w+)/,a) |
split | 拆分,拆分内容在a数组中 | split($0,a,/,/) |
index | 查找字符串,返回查找到的位置,从1开始 | i=index($0,"hi") |
substr | 截取子串 | substr($0,1,i)或substr($0,i) |
tolower | 转小写 | tolower($0) |
toupper | 转大写 | toupper($0) |
srand,rand | 生成随机数 | BEGIN{srand();printf "%d",rand()*10} |
示例数据如下,也是用awk生成的:
- $ seq 1 10|awk '{printf "id=%s,name=person%s,age=%d,sex=%d\n",$0,$0,$0/3+15,$0/6}'|tee person.txt
- id=1,name=person1,age=15,sex=0
- id=2,name=person2,age=15,sex=0
- id=3,name=person3,age=16,sex=0
- id=4,name=person4,age=16,sex=0
- id=5,name=person5,age=16,sex=0
- id=6,name=person6,age=17,sex=1
- id=7,name=person7,age=17,sex=1
- id=8,name=person8,age=17,sex=1
- id=9,name=person9,age=18,sex=1
- id=10,name=person10,age=18,sex=1
-
然后用awk模拟select id,name,age from person where age > 15 and age < 18 limit 4这样SQL的逻辑,如下:
- $ cat person.txt |awk 'match($0, /^id=(\w+),name=(\w+),age=(\w+)/, a) && a[3]>15 && a[3]<18 { print a[1],a[2],a[3]; if(++limit >= 4) exit 0}'
- 3 person3 16
- 4 person4 16
- 5 person5 16
- 6 person6 17
-
awk可以做一些简单的统计分析任务,还是以SQL为例。
如select age,sex,count(*) num, group_concat(id) ids from person where age > 15 and age < 18 group by age,sex这样的统计SQL,用awk实现如下:
- $ cat person.txt |awk '
- BEGIN{
- printf "age\tsex\tnum\tids\n"
- }
- match($0, /^id=(\w+),name=(\w+),age=(\w+),sex=(\w+)/, a) && a[3]>15 && a[3]<18 {
- s[a[3],a[4]]["num"]++;
- s[a[3],a[4]]["ids"] = (s[a[3],a[4]]["ids"] ? s[a[3],a[4]]["ids"] "," a[1] : a[1])
- }
- END{
- for(key in s){
- split(key, k, SUBSEP);
- age=k[1];
- sex=k[2];
- printf "%s\t%s\t%s\t%s\n",age,sex,s[age,sex]["num"],s[age,sex]["ids"]
- }
- }'
- age sex num ids
- 17 1 3 6,7,8
- 16 0 3 3,4,5
-
awk代码稍微有点长了,但逻辑还是很清晰的。
awk还可以实现类似SQL中的join处理,求交集或差集,如下:
- $ cat user.txt
- 1 zhangsan
- 2 lisi
- 3 wangwu
- 4 pangliu
-
- $ cat score.txt
- 1 86
- 2 57
- 3 92
-
- # 类似 select a.id,a.name,b.score from user a left join score b on a.id=b.id
- # 这里FNR是当前文件中的行号,而NR一直是递增的,所以对于第一个score.txt,NR==FNR成立,第二个user.txt,NR!=FNR成立
- $ awk 'NR==FNR{s[$1]=$2} NR!=FNR{print $1,$2,s[$1]}' score.txt user.txt
- 1 zhangsan 86
- 2 lisi 57
- 3 wangwu 92
- 4 pangliu
-
- # 当然,也可以直接使用FILENAME内置变量,如下
- $ awk 'FILENAME=="score.txt"{s[$1]=$2} FILENAME=="user.txt"{print $1,$2,s[$1]}' score.txt user.txt
-
- # 求差集,打印user.txt不在score.txt中的行
- $ awk 'FILENAME=="score.txt"{s[$1]=$2} FILENAME=="user.txt" && !($1 in s){print $0}' score.txt user.txt
- 4 pangliu
-
- # ip地址转数字
- $ echo 192.168.0.101|awk -F. '{print strtonum("0x"sprintf("%02X",$1)sprintf("%02X",$2)sprintf("%02X",$3)sprintf("%02X",$4))}'
- 3232235621
-
- # 数字转ip地址
- $ echo 3232235621|awk -v ORS=. '{match(sprintf("%08X",$0),/(..)(..)(..)(..)/,a);for(i=1;i<=4;i++){print strtonum("0X"a[i])}}'
- 192.168.0.101.
-
- $ echo -n hello编程|od -An -t u1|xargs -n1|awk -v ORS= '{c=sprintf("%c",$1);print c~/[0-9a-zA-Z.-_]/ ? c : sprintf("%%%02X",$1)}'
- hello%E7%BC%96%E7%A8%8B
-
文本处理中,最常用的就是grep、sed、awk了,因此,这哥仨也常被人合称为Linux三剑客,可见它们的重要性了,下面介绍下它们在处理能力上的异同点。
从处理文本的能力上来看,grep < sed < awk。
从命令学习难度上来看,grep < sed < awk。
以SQL来类比,如下:
grep实现了是行级别的where正则过滤功能。
sed实现了是行级别的where过滤、行号过滤与update、insert、delete等更新功能。
awk实现了是列级别的where过滤、行号过滤与update、insert、delete等更新功能,以及group by统计功能。
功能 | 基础命令 | grep实现 | sed实现 | awk实现 |
---|---|---|---|---|
过滤前10行 | seq 20 | head -n10 | seq 20 | grep -m10 '.*' | seq 20 | sed -n '1,10p' | seq 20 | awk 'NR<=10' |
过滤出包含1的行 | seq 20 | grep 1 | seq 20 | sed -n '/1/p' | seq 20 | awk '/1/' | |
过滤出不包含1的行 | seq 20 | grep -v 1 | seq 20 | sed -n '/1/!p' | seq 20 | awk '!/1/' | |
过滤出大于等于8的行 | seq 20 | grep -E '^([89]|[1-2][0-9])$' | seq 20 | sed -nE '/^([89]|[1-2][0-9])$/p' | seq 20 | awk '$1 >= 8' | |
过滤出10个包含1的行 | seq 20 | grep -m10 1 | seq 20 | awk '/1/ && ++n <= 10' | ||
多条件过滤,过滤出既包含1又包含2,或不包含1与2的行 | seq 50 | grep -P '^(?=.*1)(?=.*2).+|^(?!.*1)(?!.*2).+' | seq 50 | sed -n '/1/{/2/p;d};/1/!{/2/!p;d};/2/!{/1/!p;d}' | seq 50 | awk '$0 ~ /1/ && $0 ~ /2/ || $0 !~ /1/ && $0 !~ /2/' | |
过滤出11以及之后的2行 | seq 20 | grep -A2 11 | seq 20 | sed -n '/11/,+2p' | seq 20 | awk '/11/{n=1} n && n++<=3' | |
步进过滤,过滤出每隔3行的记录,如3,6,9... | seq 10|sed -n '0~3p' | seq 10|awk 'NR%3==0' | ||
区间过滤,过滤出包含2到包含6的行 | seq 20|sed -n '/2/,/6/p' | seq 20|awk '/2/,/6/' | ||
提取部分文本 | echo 'hello,java'|grep -oP 'hello,\K(\w+)' | echo 'hello,java'|sed -nE 's/hello,(\w+)/\1/p' | echo 'hello,java'|awk 'match($0,/hello,(\w+)/,a){print a[1]}' | |
更新,java替换为bash | echo 'hello,java'|sed 's/java/bash/g' | echo 'hello,java'|awk '{gsub(/java/,"bash",$0);print $0}' | ||
插入,首行插入title | echo 'hello,java'|sed '1i\title' | echo 'hello,java'|awk '{if(NR==1){print "title"} print $0}' | ||
删除,删除包含java行 | echo 'hello,java'|sed '/java/d' | echo 'hello,java'|awk '{if(/java/){next} print $0}' | ||
驼峰与下划线互转 | echo "userId"|sed -E 's/([A-Z]+)/_\l\1/g' echo "user_id"|sed -E 's/_(.)/\u\1/g' |
echo "userId"|awk '{print tolower(gensub(/([A-Z]+)/,"_\\1","g",$0))}' echo "user_id"|awk -F_ -v ORS= '{for(i=1;i<=NF;i++){print i==1 ? $i : toupper(substr($i,1,1)) substr($i,2)}}' |
||
倒序输出 | seq 9|tac | seq 9|sed -n 'G;$p;h' | seq 9|awk '{s=$0 "\n" s}END{print s}' | |
统计,总行数 | seq 20 | wc -l | seq 20 | grep . -c | seq 20 | sed -n '$=' | seq 20 | awk 'END{print NR}' |
统计,分组计数 | seq 20|grep -o .|sort|uniq -c | seq 20|grep -o .|awk '{S[$0]++} END{for(k in S){print S[k],k}}' |
- tac app.log |sed '/^\S/a\\'|awk -v RS= '/ERROR/ && ++n<=10{print;if(n>=10){exit}}'|tac
-
- # uniq实现版
- $ time find -name '*.java'|xargs sed -E 's/\b[A-Z]/\l&/g; s/[A-Z]/_\l&/g'|grep -w -oE '\w+'|pv -l|sort|uniq -c|sort -nrk1|head -n5
- 2.16M 0:00:03 [ 584k/s] [ <=> ]
- 56442 public
- 46228 import
- 45940 string
- 42473 order
- 41077 return
-
- real 0m4.434s
- user 0m4.719s
- sys 0m2.911s
-
- # awk实现版,比uniq实现快,理论上内存占用要高于uniq
- $ time find -name '*.java'|xargs sed -E 's/\b[A-Z]/\l&/g; s/[A-Z]/_\l&/g'|grep -w -oE '\w+'|pv -l|awk '{S[$0]++}END{for(k in S){print S[k],k}}'|sort -nrk1|head -n5
- 2.16M 0:00:02 [1.03M/s] [ <=> ]
- 56442 public
- 46228 import
- 45940 string
- 42473 order
- 41077 return
-
- real 0m2.366s
- user 0m2.324s
- sys 0m3.050s
-
熟练掌握文本处理命令十分重要,原因是命令的输入数据,以及命令执行后的输出结果,基本都是纯文本的,因此如果想比较轻松地使用Linux命令解决工作需求,就必须熟练掌握这些常见的文本处理命令。
另外,之前也分享过Linux中的常见文本命令使用技巧,更偏实际应用场景,感兴趣可前往查看:
- (echo 0;echo 1) > num.txt
- tail -n+0 -f num.txt|awk 'NR>1{print pre+$0;fflush()}{pre=$0}' >> num.txt
-
- $ seq 6|xargs -n2|xargs -L1 printf "<%s> "
- <1> <2> <3> <4> <5> <6>
-
- $ seq 6|xargs -n2|xargs -d'\n' -L1 printf "<%s> "
- <1 2> <3 4> <5 6>
-
答案参考:这grep咋还不支持\d呢(BRE,ERE,PCRE)
- while sleep 1;do echo $((i++)); done|sed 's/.\+/&+1/g'|bc
- while sleep 1;do echo $((i++)); done|sed -u 's/.\+/&+1/g'|bc
- while sleep 1;do echo $((i++)); done|stdbuf -oL sed 's/.\+/&+1/g'|bc
-
答案参考:shell管道咋堵住了?
- tr cut paste comm join
-
答案参考:Linux文本命令技巧(上)