在Linux中,一切皆文件,所以在Linux中避免不了和文件打交道,Linux有不少和文本处理相关的工具,其中最有名的当为三剑客:grep、awk、sed。三个工具配合正则表达式,基本上可以解决处理文本的绝大多数问题。
如果你忘记了自己的钥匙在哪里,就得自己去找;如果你忘记了文件中的内容,那么 grep
命令可以帮你查找。
grep
命令多配合管道符 |
以及正则来查找匹配文本。
$ 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
awk
命令可以处理数据流,它支持关联数组、条件语句等等功能
基本结构如下
awk 'BEGIN{ print "start" } pattern { commands } END{ print "end" } file
这一串命令由三部分组成
这三部分都是可选的,不是必需
awk 以逐行的形式处理文件,BEGIN 之后的命令会先于公共语句块执行。对于匹配 PATTERN 的行,awk 会对其执行 PATTERN 之后的命令。最后,在处理完整个文件之后,awk 会执行 END 之后的命令。
$ awk "BEGIN { i=0 } { i++ } END { print i }" test.txt
2
解读这条命令
如果 PATTERN 没有提供,那么将会打印读取到的每一行文本,当 print
没有带参数的时候,默认打印当前行。
$ echo -e 'line1\nline2' | awk 'BEGIN { print "start"} { print } END { print "end" }'
start
line1
line2
end
awk 中有一些特殊变量
$ 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
我们借助 -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
$ 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
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