Shell 使用grep在文件中搜索文本,如果你忘记把钥匙放在了哪里,就得自己去找;如果你忘记了文件中的内容,grep
命令可以帮助你查找。本章将教你如何定位包含特定文本模式的文件。
实战演练
grep
命令作为Unix中用于文本搜索的神奇工具,能够接受正则表达式,生成各种格式的输出。
(1) 在stdin
中搜索匹配特定模式的文本行:
$ echo -e "this is a word\nnext line" | grep word
this is a word
(2) 在文件中搜索匹配特定模式的文本行:
$ grep pattern filename
this is the line containing pattern
或者
$ grep "pattern" filename
this is the line containing pattern
(3) 在多个文件中搜索匹配特定模式的文本行:
$ grep "match_text" file1 file2 file3 ...
(4) 选项--color
可以在输出行中着重标记出匹配到的模式。尽管该选项在命令行中的放置位置没有强制要求,不过惯常作为第一个选项出现:
$ grep --color=auto word filename
this is the line containing word
(5) grep
命令默认使用基础正则表达式。这是先前描述的正则表达式的一个子集。选项-E
可以使grep
使用扩展正则表达式。也可以使用默认启用扩展正则表达式的egrep
命令:
$ grep -E "[a-z]+" filename
或者
$ egrep "[a-z]+" filename
(6) 选项-o
可以只输出匹配到的文本:
$ echo this is a line. | egrep -o "[a-z]+\."
line
(7) 选项-v
可以打印出不匹配match_pattern
的所有行:
$ grep -v match_pattern file
选项-v
能够反转(invert)匹配结果。
(8) 选项-c
能够统计出匹配模式的文本行数:
$ grep -c "text" filename
10
需要注意的是-c
只是统计匹配行的数量,并不是匹配的次数。例如:
$ echo -e "1 2 3 4\nhello\n5 6" | egrep -c "[0-9]"
2
尽管有6个匹配项,但egrep
命令只输出2,这是因为只有两个匹配行。在单行中出现的多次匹配只被计为一次。
(9) 要统计文件中匹配项的数量,可以使用下面的技巧:
$ echo -e "1 2 3 4\nhello\n5 6" | egrep -o "[0-9]" | wc -l
6
(10) 选项-n
可以打印出匹配字符串所在行的行号:
$ cat sample1.txt
gnu is not unix
linux is fun
bash is art
$ cat sample2.txt
planetlinux
$ grep linux -n sample1.txt
2:linux is fun
或者
$ cat sample1.txt | grep linux -n
如果涉及多个文件,该选项也会随输出结果打印出文件名:
$ grep linux -n sample1.txt sample2.txt
sample1.txt:2:linux is fun
sample2.txt:2:planetlinux
(11) 选项-b
可以打印出匹配出现在行中的偏移。配合选项-o
可以打印出匹配所在的字符或字节偏移:
$ echo gnu is not unix | grep -b -o "not"
7:not
字符在行中的偏移是从0开始计数,不是1。
(12) 选项-l
可以列出匹配模式所在的文件:
$ grep -l linux sample1.txt sample2.txt
sample1.txt
sample2.txt
和-l
效果相反的选项是-L
,它会返回一个不匹配的文件列表。
补充内容
grep
命令是Linux/Unix系统中最为全能的命令之一。它还包括其他一些选项,可用于搜索目录、选择待搜索的文件等。
- 递归搜索多个文件
如果需要在多级目录中对文本进行递归搜索,可以使用下列命令:
$ grep "text" . -R -n
命令中的.
指定了当前目录。例如:
$ cd src_dir
$ grep "test_function()" . -R -n
./miscutils/test.c:16:test_function();
grep
的选项-R
和-r
功能一样。
test_function()
位于miscutils/test.c
的第16行。如果你要在网站或源代码树中展开搜索,选项-R
尤其有用。它等价于下列命令:
$ find . -type f | xargs grep "test_function()"
- 忽略模式中的大小写
选项-i
可以在匹配模式时不考虑字符的大小写:
$ echo hello world | grep -i "HELLO"
hello
- 使用
grep
匹配多个模式
选项-e
可以指定多个匹配模式:
$ grep -e "pattern1" -e "pattern2"
上述命令会打印出匹配任意一种模式的行,每个匹配对应一行输出。例如:
$ echo this is a line of text | grep -o -e "this" -e "line"
this
line
可以将多个模式定义在文件中。选项-f
可以读取文件并使用其中的模式(一个模式一行):
$ grep -f pattern_filesource_filename
例如:
$ cat pat_file
hello
cool
$ echo hello this is cool | grep -f pat_file
hello this is cool
- 在
grep
搜索中指定或排除文件
grep
可以在搜索过程中使用通配符指定(include)或排除(exclude)某些文件。
使用--include
选项在目录中递归搜索所有的 .c和 .cpp文件:
$ grep "main()" . -r --include *.{c,cpp}
注意,some{string1,string2,string3}
会被扩展成somestring1 somestring2 somestring3
。
使用选项--exclude
在搜索过程中排除所有的README
文件:
$ grep "main()" . -r --exclude "README"
选项--exclude-dir
可以排除目录:
$ grep main . -r -exclude-dir CVS
如果需要从文件中读取排除文件列表,使用--exclude-from FILE
。
- 使用0值字节后缀的
xargs
与grep
xargs
命令可以为其他命令提供命令行参数列表。当文件名作为命令行参数时,建议用0值字节作为文件名终结符,而非空格。因为一些文件名中会包含空格字符,一旦它被误解为终结符,那么单个文件名就会被视为两个(例如,New file.txt被解析成New和file.txt两个文件名)。这个问题可以利用0值字节后缀来避免。我们使用xargs
从命令(如grep
和find
)中接收stdin
文本。这些命令可以生成带有0值字节后缀的输出。为了指明输入中的文件名是以0值字节作为终结,需要在xargs
中使用选项-0
。
创建测试文件:
$ echo "test" > file1
$ echo "cool" > file2
$ echo "test" > file3
选项-l
告诉grep
只输出有匹配出现的文件名。选项-Z
使得grep
使用0值字节(\0
)作为文件名的终结符。这两个选项通常都是配合使用的。xargs
的-0
选项会使用0值字节作为输入的分隔符:
$ grep "test" file* -lZ | xargs -0 rm
grep
的静默输出
有时候,我们并不打算查看匹配的字符串,而只是想知道是否能够成功匹配。这可以通过设置grep
的静默选项(-q
)来实现。在静默模式中,grep
命令不会输出任何内容。它仅是运行命令,然后根据命令执行成功与否返回退出状态。0表示匹配成功,非0表示匹配失败。
下面这个脚本利用grep
的静默模式来测试文件中是否有匹配文本:
#!/bin/bash
# 文件名: silent_grep.sh
# 用途: 测试文件是否包含特定的文本内容
if [ # -ne 2 ]; then
echo "Usage:0 match_text filename"
exit 1
fi
match_text=1
filename=2
grep -q "match_text"filename
if [ $? -eq 0 ]; then
echo "The text exists in the file"
else
echo "Text does not exist in the file"
fi
这个silent_grep.sh脚本接受两个命令行参数:一个是需要匹配的单词(Student),另一个是文件名(student_data.txt):
$ ./silent_grep.sh Student student_data.txt
The text exists in the file
- 打印出匹配文本之前或之后的行
基于上下文的打印是grep
的一个挺不错的特性。当grep
找到了匹配模式的行时,它只会打印出这一行。但我们也许需要匹配行之前或之后的n行。这可以通过控制选项-B
和-A
来实现。
选项-A
可以打印匹配结果之后的行:
$ seq 10 | grep 5 -A 3
5
6
7
8
选项-B
可以打印匹配结果之前的行:
$ seq 10 | grep 5 -B 3
2
3
4
5
选项-A
和-B
可以结合使用,或者也可以使用选项-C
,它可以分别打印出匹配结果之前及之后的n行:
$ seq 10 | grep 5 -C 3
2
3
4
5
6
7
8
如果有多个匹配,那么使用--
作为各部分之间的分隔:
$ echo -e "a\nb\nc\na\nb\nc" | grep a -A 1
a
b
--
a
b