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 filename
This is the first line
在多个文件中搜索匹配特定模式的文本行
grep pattern file1 file2 file3 ...
使用
--color=auto
着重输出匹配到的特定模式的文本$ grep --color=auto first test.txt
This 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个数字被匹配到了
1
2
3
4
5
6
# 如果想统计匹配到的次数,可以使用 -o 再结合 wc -l 命令
$ echo '1 2 3 4\nhello\n5 6' | grep -o '[0-9]' | wc -l
6
使用
-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.txt
test1.txt
-L
命令得出与-l
相反的结果使用
-i
忽略大小写$ echo HELLO world | grep -i -o hello
HELLO
使用
-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 line
this
line
使用
-f
,可以将多个匹配模式定义在文件中并读取使用其中的模式(一个模式一行)$ echo '^this\nhello' > pattern.txt
$ echo 'this is the first line\nworld hello\nthird line' | grep -f pattern.txt
this is the first line
world 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 3
2
3
4
5
$ seq 10 | grep 5 -B 2
3
4
5
$ seq 10 | grep 5 -C 2
3
4
5
6
7
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.txt
2
- BEGIN 里面的语句定义了变量 i=0
- 每一行 执行i++
- END 打印变量 i
如果 PATTERN 没有提供,那么将会打印读取到的每一行文本,当 print
没有带参数的时候,默认打印当前行。
$ echo -e 'line1\nline2' | awk 'BEGIN { print "start"} { print } END { print "end" }'
start
line1
line2
end
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=nancy
Line no:2,num of fields:3 $0=line2 named roy $2=named $3=roy
Line 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}'
nancy
roy
lisa
- 传递外部变量给 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=$AGE
nancy 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 2
3 3
4 4
5 5
1 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 替换为 hi
hi,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