0. 前言
在Linux中,一切皆文件,所以在Linux中避免不了和文件打交道,Linux有不少和文本处理相关的工具,其中最有名的当为三剑客:grep、awk、sed。三个工具配合正则表达式,基本上可以解决处理文本的绝大多数问题。
1. gerp
如果你忘记了自己的钥匙在哪里,就得自己去找;如果你忘记了文件中的内容,那么 grep 命令可以帮你查找。grep 命令多配合管道符 | 以及正则来查找匹配文本。
在 stdin 中搜索匹配特定模式的文本行
$ echo -e 'this is a word\nnext line' | grep 'word'this is a word
在文件中搜索匹配特定模式的文本行
$ echo "This is the first line\nsecond line" > test.txt$ grep first test.txt # grep pattern filenameThis is the first line
在多个文件中搜索匹配特定模式的文本行
grep pattern file1 file2 file3 ...
使用
--color=auto着重输出匹配到的特定模式的文本$ grep --color=auto first test.txtThis is the first line # 这里 first 应是带颜色的,代码块里无法上色
使用
-o选项只输出匹配到的文本# 使用 egrep 可以默认使用扩展正则表达式的命令 == grep -E$ echo "This is the first line\nsecond-line." | egrep -o "^T[a-z]+"This$ echo "This is the first line\nsecond-line." | grep -E -o "^T[a-z]+"This
使用
-v可以打印出不匹配的文本行,也就是翻转匹配结果$ "This is the first line\nsecond-line.\nthird line" | egrep -v "^T"second-line.third line
使用
-c可以统计出匹配模式的文本行数$ "This is the first line\nsecond-line.\nthird line" | egrep -v -c "^T"2 # 这里配合了 -v 统计了匹配模式之外的文本行数
这里值得注意的是
-c只是统计了匹配的文本行数,和匹配到的次数并没有关系$ echo '1 2 3 4\nhello\n5 6' | grep -c '[0-9]'2 # 匹配到了两行$ echo '1 2 3 4\nhello\n5 6' | grep -o '[0-9]' # 但是有6个数字被匹配到了123456# 如果想统计匹配到的次数,可以使用 -o 再结合 wc -l 命令$ echo '1 2 3 4\nhello\n5 6' | grep -o '[0-9]' | wc -l6
使用
-n打印出匹配到的字符所在的文本行行号$ echo '1 2 3 4\nhello\n5 6' | grep -n '4'1:1 2 3 4$ echo '1 2 3 4\nhello\n5 6' | grep -n '6'3:5 6
使用
-l可以列出匹配模式所在的文件$ grep -l unix test.txt test1.txttest1.txt
-L命令得出与-l相反的结果使用
-i忽略大小写$ echo HELLO world | grep -i -o helloHELLO
使用
-R | -r进行递归搜索$ grep test . -R -n # . 表示当前目录,匹配到了三个结果,分别打印出了所在文件和所在行./run.py:24: pic_name = 'test.png'./compare_pic.py:47: print('testing pass'.center(30, '-'))./compare_pic.py:49: print('testing fail'.center(30, '-'))
使用
-e可以匹配多个指定模式$ echo this is the first line | grep -o -e this -e linethisline
使用
-f,可以将多个匹配模式定义在文件中并读取使用其中的模式(一个模式一行)$ echo '^this\nhello' > pattern.txt$ echo 'this is the first line\nworld hello\nthird line' | grep -f pattern.txtthis is the first lineworld hello
使用
--include配合通配符指定匹配的文件$ grep 'main' . -r --include *.py./compare_pic.py:if __name__ == '__main__':
与之相反的命令是
--exclude,匹配这些文件之外的文件grep 'main' . -r --exclude README
使用
-exclude-dir可以排除目录$ grep 'main' . -r -exclude-dir imgs
打印匹配到结果之后和之前的行,使用
-A | -B | -C$ seq 10 | grep 2 -A 32345$ seq 10 | grep 5 -B 2345$ seq 10 | grep 5 -C 234567
2. awk
awk命令可以处理数据流,它支持关联数组、条件语句等等功能
基本结构如下awk 'BEGIN{ print "start" } pattern { commands } END{ print "end" } file这一串命令由三部分组成
- BEGIN
- END
- 带模式匹配选项的公共语句块
这三部分都是可选的,不是必需
awk 以逐行的形式处理文件,BEGIN 之后的命令会先于公共语句块执行。对于匹配 PATTERN 的行,awk 会对其执行 PATTERN 之后的命令。最后,在处理完整个文件之后,awk 会执行 END 之后的命令。
- 打印文件的行数
解读这条命令$ awk "BEGIN { i=0 } { i++ } END { print i }" test.txt2
- BEGIN 里面的语句定义了变量 i=0
- 每一行 执行i++
- END 打印变量 i
如果 PATTERN 没有提供,那么将会打印读取到的每一行文本,当 print 没有带参数的时候,默认打印当前行。
$ echo -e 'line1\nline2' | awk 'BEGIN { print "start"} { print } END { print "end" }'startline1line2end
awk 中有一些特殊变量
- NR:表示记录编号,当 awk 将行作为记录时,该变量相当于当前行号
- NF:表示字段数量,在处理当前记录时,相当于字段数量。默认的字段分隔符是空格
- $0:该变量包含当前记录的文本内容
- $1:该变量包含第一个字段的文本内容
- $2:该变量包含第二个字段的文本内容
🌰
$ echo 'line1 named nancy\nline2 named roy\nline3 named lisa' | \awk '{ print "Line no:"NR",num of fields:"NF, "$0="$0, "$2="$2, "$3="$3 }'Line no:1,num of fields:3 $0=line1 named nancy $2=named $3=nancyLine no:2,num of fields:3 $0=line2 named roy $2=named $3=royLine no:3,num of fields:3 $0=line3 named lisa $2=named $3=lisa
注意里面的变量名不要用 “” 包裹上哦
我们可以使用 $NF 来代表最后一个字段,倒数第二个字段用 $(NF-1) 表示,以此类推
$ echo 'line1 named nancy\nline2 named roy\nline3 named lisa' | awk '{ print $NF}'nancyroylisa
- 传递外部变量给 awk
我们借助 -v 参数,将外部变量传递给 awk
$ NAME='nancy'$ echo | awk -v MYNAME=$NAME '{ print MYNAME }'nancy
还有更为灵活的方式
$ NAME='nancy' ; AGE=20$ echo | awk '{ print name,age }' name=$NAME age=$AGEnancy 20
变量以键值对的形式,使用空格进行分隔,作为 awk 的命令行参数紧跟在语句块结束之后
对文本内容进行过滤
$ awk 'NR < 5' # 行号小于5的行$ awk 'NR==1,NR==4' # 行号在1-5之间的行
设置字段分隔符
NF 默认使用空格进行分割,但有时候文本是以特定的分隔符进行分割的,所以需要指定匹配的分隔符-F 参数指定分隔符
$ echo 'a----b----c----d' | awk -F"----" '{print $1 $2 $3 $4}'abcd
我们也可以在 BEGIN 语句块中使用 FS="" 来设置分隔符
$ echo 'a----b----c----d' | awk 'BEGIN { FS="----" } { print NF}'4
在 awk 中关联数组并使用循环
$ seq 5 | awk '{no[$1]=$+1} END { for (i in no) { print i,no[i]}}'2 23 34 45 51 1
内建字符串处理函数
- length(string):返回字符串长度
- index(string, search_str):返回search_str在字符串string中的索引位置
- split(string, array, delimiter):以delimiter作为分隔符,分割字符串string,将生成的字符串组存入数组array中
- substr(string, start, end):返回字符串string中 start起始位置和end结束为止的子字符串
- sub(regex, replace_str, string):匹配到的第一处内容替换为replace_str
- gsub(regex, replace_str, string):替换匹配到的所有
3. sed
sed 是 stream editor 的缩写,字面意思上看就是 流编辑器。这个工具常用来做文本的替换。
举一个最简单的实例
$ echo 'hello,world' | sed 's/hello/hi/' # 将 hello 替换为 hihi,world
sed 基本语法: sed s/pattren/replace_str/ filename
sed 可以读取文件中的文本,也可以从 stdin 中读出输入
sed 语法和 vim | vi 中的替换文本的命令十分类似,默认只打印出被替换的文本
使用
-i修改替换原始文件$ echo 'hello,world' > demo.txt$ sed -i 's/hello/hi/' demo.txt
替换所有
上面只是替换了第一次匹配到的文本,下面替换所有符合匹配模式的文本
$ echo 'this is a demo' | sed 's/is/IS/g'thIS IS a demo
g 标记可以使sed执行全局替换
#g 标记可以使sed执行第n次出现的匹配进行替换
- 分隔符
sed 命令会将 s 之后的字符看做为命令分隔符,我们可以自行修改分隔符sef 's:text:replace_str':g
需要注意的是:如果分隔符作为字符出现在匹配模式中,需要使用 \ 进行转义
- 使用多个表达式进行替换
这里利用管道符 | 对多个匹配模式进行文本替换
$ echo abc | sed 's/a/A/' | sed 's/c/C/'AbC
