Linux下文本处理的三剑客–grep/awk/sed!
概述
Grep/awk/Sed是Linux下处理文本信息的最强的三个命令.使用它们几乎可以完成所有的文本处理, 具体说来是:
1 | grep : 过滤.查找 |
使用它们不可避免的要使用正则.
因此回顾一下正则的基础:
简单的说, 我们把基础正则分为这样三个部分.
单个字符–表示内容
特定的字符(直接写), 字符范围(使用中括号[]
), 任意字符(用一个点.
来表示).
使用|
来进行逻辑或运算.
重复字符–表示数量
表示从零到任意:使用星号*
表示要么0个要么1个:使用问号?
表示从1到任意个:使用加号+
表示特定的范围:使用大括号{}
边界字符
首字符使用尖角号^
尾字符使用美元符$
元字符
任何字类字符使用\w
任何非字类字符使用\W
划定单词边界使用\b
– (boundary)
另外,在使用一些字符时, 千万不要忘记使用转义字符.
基础的正则就是这样了.
grep
这个使用率应该是最高的了, 因为他是三个命令中最简单的一个.
grep的基本命令格式是:
1 | grep [选项] 匹配字符串 文件 |
我们经常使用管道符流动的数据来替代文件, grep常用的选项是:
1 | -i (ignore-case) 忽略大小写 |
这个没什么好说的了..因为大家都用的很666666. :)
sed
sed相当于是一个自动的vim,常用来分析日志,修改配置,自动处理文件.
简单先说一下Sed的原理, Sed接受一个输入, 接着(A)将读到的一行数据写入到模式空间(其实就是一个临时缓冲区), 接着进行命令处理(B),最后输出到屏幕, (A-B)循环执行直到读取结束.
Sed实际上是行处理! 并且, Sed不改变源文件, 除非指定选项(这个后面再说),
命令格式:
1 | sed [选项] '[命令]' 文件名 |
现将常用的选项参数列出来, 到了下面再慢慢说:
1 | -e 对输入数据进行多条sed命令(;号分割) |
那我们从文本处理开始!
Sed的命令有很多和Vim很像, 列举一些常用的:
1 | - a : (append) 追加 |
下面来动动手吧~
1 | [root@WWW ~]$ sed 'p' passwd |
挨!!??输出了两次?想想看这是为什么?
之前说过, sed的原理是读取一行, 而在没有加上-n参数的时候, 又会将全文件输出, 所以就造成了行行重复的情况.
同Vim一样,也可以直接使用行号或者使用斜杠进行搜索.
比如:
1 | [root@WWW ~]$ sed -n '10p' passwd |
也可以选择一段距离, 比如:
1 | [root@WWW ~]$ sed -n '10,20p' passwd |
将数字中的逗号变为波浪线即可进行步进式的跳跃.
也可以进行取反:
1 | [root@WWW ~]$ sed -n '10!p' passwd |
接着来看一下-i
和-a
两个插入命令:
1 | [root@WWW ~]$ sed '2a =========' passwd |
也就是说, i是向前插入, a是向后插入.
很简单对吧? 删除命令也是很简单的:
1 | [root@WWW ~]$ nl passwd | sed '2d' |
删除同样也支持跳行和段选择.
最后就是替换命令了, 替换支持行替换和字串替换.
相对来说, 行替换要更简单一点.
1 | [root@WWW ~]$ sed '2c Hello' passwd | nl |
如果在替换命令中使用了段选择, 不会使每一行都做替换, 而是把他们作为一个整体.
1 | [root@WWW ~]$ sed '1,34c Hello' passwd | nl |
来看一个操作实例吧, 现在我们从ifconfig中读取ip地址:
1 | [root@WWW ~]$ ifconfig | grep inet | sed -e 's/inet //;s/net.*//' |
其中我们使用了多重操作, 也就是添加-e
参数, 并使用分号进行多个sed
命令.
接下来来看一下Sed稍微高级一点的用法:
- 使用&来复制前一个字符串
- 大小写转换
使用&
的一个实例像这样:
1 | [root@WWW ~]$ sed 's/^[a-z]\+/& /' passwd |
接着来看一下这几个大小写转换符:
- u 转大写(以首字母为单位) (U 以词为单位)
- l 转小写(以首字母为单位) (L 以词为单位)
比如:
1 | [root@WWW ~]$ sed 's/^[a-z-]\+/\U&/' passwd |
会将所有的用户名变成大写.
接着,如同我们在Python小技巧中说过的捕获组, 正则拼成的组, 也是可以有编号进行引用的.就像这样:
1 | [root@WWW ~]$ sed 's/\(^[0-9A-Za-z-]\+\):x:\([0-9]\+\):\([0-9]\+\).*$/USER:\1 UID:\2 GID:\3/' passwd |
这样会输出类似:
1 | User:root UID: 0 GID:0 |
这样的语句.
sed也可以进行多文件间的操作, 比如这两个命令:(注意:其中的w
命令是会进行真实写入的!会直接对文件进行改变并且指针在文件头.)
比方说我们先写入一些数据:(和下面的演示有出入,只要分清字母和数字就好)
1 | [root@WWW ~]$ echo -e "13243\n545343\n112324" > 123.txt |
接着演示文件间的读取和写入
1 | [root@WWW ~]$ sed '1r abc.txt' 123.txt # 写入缓存区中,不会生效 |
sed的执行单位是行, 直到读取完整个文件.
但我们可以通过q
命令使其提前退出.
比如:
1 | [root@WWW ~]$ nl passwd | sed '3q' |
至此, sed的命令就差不多了.
awk
awk是一个与Sed类似的文本与数据处理工具, 同样也是一次处理一行内容.
不同的是, awk对每行可进行切片, 并且awk是可以进行编程的, 因此他十分灵活.
awk的格式为:
1 | awk '条件1{动作1}条件2{动作2}...' 文件名 |
awk的内置变量(1)同shell参数很像, 使用$0
表示整行, $1
表示第一个字段, $2
表示第二个字段….以此类推..
awk的分割符用 -F ''
来进行声明
比如:
1 | [root@WWW ~]$ cat passwd | awk -F ':' '{print $1 "----" $7}' |
awk的内置变量(2)是NR,NF,FILENAME,分别表示每行的记录号, 字段的数量, 和正在处理的文件名.
awk是支持逻辑运算的, 比如下面这个:
1 | [root@WWW ~]$ awk -F ":" '{if ($3>100) print "Deny: ",$1} {if ($3<100) print "Access: ",$1}' passwd |
awk有两种输出函数, 一个是printf
, 一个是print
, 其中printf
更加古老, 需要手动加上最后的换行符, print
会自动加上.
printf
基本上和编程语言的同名函数用法相同,都是格式化输出.
作为awk的重要功能, 我们来看一下他的逻辑判断式:
, !: 匹配正则表达式- ==, !=, <, > : 判断逻辑表达式
匹配正则以~
开头, 后面加上有斜线规定的正则, 后面的指令就只对匹配到的内容有效果了.
比如这样:
1 | $ awk -F ':' '$1~/^r.*$/{print $1}' passwd |
当然也可以反选择, 只要把~
进行取反就可以啦, 也就是!~
.
以上是awk的基础格式.
什么?还有扩展的? 是的, awk的command2拓展就像是这样:
1 | [root@WWW ~]$ awk [options] 'BEGIN{print 'start'} pattern{commands} END{ print "end"}' |
使用awk扩展格式可以手动制表, 仍然用passwd
来举例:
1 | [root@WWW ~]$ awk -F ':' 'BEGIN{print "Line\tCol\tUser"}{print NR"\t"NF"\t"$1}END{print "-------"FILENAME"-------"}' passwd |
现在我们利用awk的逻辑控制运算功能, 做一个小实例:(请使用du取代这个愚蠢的实例.)
1 | [root@WWW ~]$ ls -la | awk 'BEGIN{size=0}{size+=$5}END{print "The total size of the directory is "size/1024/1024"M"}' |
在BEGIN段中, 我们初始化了size,接着让awk一行一行读入大小的值并累加到size上,最后输出.
同样, 我们也可以统计passwd文件的行数, 从而得知有多少用户(请使用wc命令,取代这个愚蠢的实例)
1 | [root@WWW ~]$ awk -F ":" 'BEGIN{count=0}$1!~/^$/{count++}END{print "There are "count" users"}' passwd |
awk也可以使用for循环和数组, 没想到吧?
1 | [root@WWW ~]$ netstat -tanp | awk '$6~/LISTEN|ESTABLISHED/{sum[$6]++}END{for (i in sum)print i, sum[i]}' |
到这里awk的使用就算是差不多了, 简单小结一下吧:
awk的内置参数要知道, $n(数字) 代表那一列的值, NR, NF 分别代表Record每行记录数, Field字段数.
awk支持逻辑符, 判断, 数组, 循环等等, 还有BEGIN
和END
.
其他管道命令
接着记录一下Linux的管道命令, 这些在进行文本处理的时候也是超级常用的.
cut
这个命令其实也是很常用的,听名字就知道是干什么的了–做字符串的裁剪以剪出我们需要的信息.
事实上,这个命令的历史很久的.简单的使用方法就像是这样:
1 | [root@WWW ~]$ cut -d "分割字符" -f fields # d其实就是delimiter嘛 fields就是要裁剪的数据域 |
举个例子就明白了:
1 | [root@WWW ~]$ echo $PATH |
简单吧,另外cut还能精确的定位到一节字符串的某个位置:
1 | [root@WWW ~]$ echo $PATH | cut -c 1,5 |
上面就是在使用cut命令的-c
进行的精准截取, ,
表示就选择这两个位置的字符, -
表示选择这两个字符以及他们中间的所有字符, 其实就类似Python的切片, cut
也可以不写两端来进行直到头尾的操作.
当然你也发现了, cut在处理多个空格的情况下是比较吃力的, 这个时候就要选择awk
了.
sort
sort是个很有趣的指令,他可以根据不同的数据类型来进行排序.这里要注意一下,排序的依据还取决于当前系统的语系.使用LANG=C来使得语系统一,优化排序
常用的选项和参数如下:
1 | [root@WWW ~]$ sort [options] [file or stdin] |
使用sort,我们可以很轻易的得到按照用户名排序的passwd文件:
1 | [root@WWW ~]$ cat /etc/passwd | sort |
假如说系统的passwd文件不是按照uid排的序的话, 使用sort同样可以达到同样的目的, 这个时候就要使用到-k
和-t
了.
1 | [root@WWW ~]$ cat /etc/passwd | sort -t ":" -k 3 |
稍微解释一下,-t ":"
的意思是说以:
当做分割符, -k 3
的意思是说以第三个的数据域(也就是uid了)当做排序依据.
uniq
这个命令经常用于进行处理重复的行, 一个经典的使用例子就是统计曾经登录的用户的登录次数.
1 | [root@WWW ~]$ last | cut -d ' ' -f 1 | sort | uniq -c |
我的机器上的输出结果是:
1 | 1 |
一开始我省略了sort命令,结果发现输出的结果是这样的:
1 | 107 root |
root
的结果被分开了, 这样就导致uniq
的统计结果出现了错误. 这样我们就能发现了uniq
的处理逻辑: 从上到下的搜索,如果是一样的就不再增加新条目,一旦遇到不同的,则直接将之前的的条目归档.
wc
这个是一个很好用的统计工具哦, wc
可以很方便的统计行数,字数,字符数.
尤其对于一些成行的文件,这个命令可以很灵活的获取一些你想要的值.( 比如/etc/passwd
这样的文件,行数即为用户数 )
1 | [root@WWW ~]$ echo `cat /etc/passwd | wc -l`"个用户" |
直接使用wc
,会按照次序输出行数,字数,字节数.
wc
对于单词的界定是被空格分割的非零长度的字符序列.
使用一下选项来限制输出的信息:
1 | [root@WWW ~]$ wc -l (lines) -w (words) -m (chars) |
tee
像上面的图那样, tee会将输出流复制一份到文件,另外一份到标准输出流(也就是屏幕了)
例如,当你想要将处理的信息输出到屏幕外,还想存一份日志或者什么.那么tee
就是一个十分适合的工具
1 | [root@WWW ~]$ last | tee last.list | cut -d " " -f 1 |
这样不仅会新建一个叫last.list
的文件,还会将信息同步输出到屏幕上.
如果文件是已经存在的,那么文件指针将会指向文件头.
如果是要进行追加,就要添加-a
参数. 如果文件的参数是-
,那么将会输出标准输出 ( 也就是说会在屏幕上输出两倍的信息 )
tr
tr的用法还是很灵活的, 因为你可以使用正则来进行各种替换和删除.
直接上实例吧:
1 | [root@WWW ~] cat /etc/passwd | tr [:lower:] [:upper:] |
这样便将所有的字母变成了大写.像这样的变量还有:
[:alnum:]
所有的字母和数字[:alpha:]
所有的字母[:blank:]
所有的水平空格[:digit:]
所有的数字[:upper:]
大写字母[:lower:]
小写字母
另外tr可以加上参数来说明模式: -d
是匹配删除, -t
是用来将SET1映射成SET2.
在使用正则的情况下:
1 | [root@WWW ~]$ cat /etc/passwd | tr [a-z] [A-Z] |
和上面的效果是一样的.
删除功能的示例如下:
1 | [root@WWW ~]$ cat /etc/passwd | tr -d ":" |
然后你就会看到一团子东西, 所有的分隔符已经被删去了.
tr还有一个有趣的参数-c
意思就是取补集, 换言之就是除了给定的SET之外的. 如果和-d连续使用就是说仅保留给定的字符集. 这样子的一个应用就是生成随机字符串:
1 | [root@WWW mage_shell_edu]# tr -cd a-zA-Z0-9 < /dev/urandom | head -c 20 | xargs |
最后一个xargs其实就是为了换行而已, 关于xargs的用法, 下面会有.
split
在NT
下, 你应该使用过一些txt
裁剪工具对吧.这个split
就是类似的一个外部命令.
split
可以将大文档分割成一个个小的档案.并且支持单位.
split
支持按行数分割, 按大小两种方式
1 | [root@WWW ~]$ split [OPTION] [INPUT [PREFIX]] |
现在来试一下:
1 | [root@WWW ~]$ split -l 4 /root/test mytest_ |
然后我的机器上就多出来了几个文件, 命名形式是: mytest_a{a,b,c,d...}
如果我是想将屏幕上的信息进行裁剪之后再输出文件怎么办呢?
还记得之前的那个短横线 -
这个横线就可以来解决问题.
1 | [root@WWW ~]$ ls -al /| split -l 4 - lsroot_ |
那如果我要进行文件的合并怎么办呢, 其实这个并不需要什么特定的工具了,直接使用数据流重定向就可以了.
1 | [root@WWW ~]$ cat mytest_* >> mytest |
这样就可以了.
最后再来看一个参数代换的命令:
xargs
这个命令是个很神奇的命令,参数代换并不能理解吧, 其实说白了就是将前面的输出当做命令的参数.所以这个命令的用处是很大的.
比如说
1 | [root@WWW ~]$ cat /etc/passwd \ cut -d ':' -f 1 | xargs -n 1 id |
这样就可以打印出所有用户的UID,GID和所属组名.
这样看来xargs
就像是find
命令后面的那个-exec COMMAND {} \;
一样的感觉.
解释一下上面的参数是什么意思: 如果参数是很多个就一定要指定-n
参数, 这个告诉xargs
一次处理的命令参数数量
另外,如果xargs后面没有指定命令,那么默认就是echo
. 另外一个可能会用到的参数是-e
(EOF)的意思, 在后面可以跟上一个字符串, 当xargs
扫描到了这个字符串就会直接停止工作.