4.3 用grep在文件中搜索文本
在文件中进行搜索是文本处理中一项重要的用例。我们也许需要根据某些规格在上千行的文件中查找所需要的数据。这则攻略将教你如何从大量数据中定位符合给定规格的数据。
4.3.1 预备知识
grep
命令是UNIX中用于文本搜索的大师级工具。它能够接受正则表达式和通配符。我们可以用多个grep
命令选项来生成各种格式的输出。让我们看看它具体的使用方法。
4.3.2 实战演练
在文件中搜索一个单词:
- $ grep match_pattern filename
- this is the line containing match_pattern
或者
- $ grep "match_pattern" filename
- this is the line containing match_pattern
命令会返回包含给定match_pattern
的文本行。
也可以像下面这样从stdin中读取:
- $ echo -e "this is a word\nnext line" | grep word
- this is a word
一个grep
命令也可以对多个文件进行搜索:
- $ grep "match_text" file1 file2 file3 ...
用--color
选项可以在输出行中重点标记出匹配到的单词:
- $ grep word filename --color=auto
- this is the line containing word
grep
命令通常将match_pattern
视为通配符。如果要使用正则表达式,需要添加-E
选项——这意味着使用扩展(extended)正则表达式,也可以使用默认允许正则表达式的grep
命令——egrep
。例如:
- $ grep -E "[a-z]+"
或者
- $ egrep "[a-z]+"
为了只输出文件中匹配到的文本部分,可以使用选项 -o
:
- $ echo this is a line. | grep -o -E "[a-z]+\."
- line.
或者
- $ echo this is a line. | egrep -o "[a-z]+\."
- line.
要想打印除包含match_pattern
的行之外的所有行,可使用:
- $ grep -v match_pattern file
选项-v
可以将匹配结果进行反转(invert)。
统计文件或文本中包含匹配字符串的行数:
- $ grep -c "text" filename
- 10
需要注意的是-c
只是统计匹配行的数量,并不是匹配的次数。例如:
- $ echo -e "1 2 3 4\nhello\n5 6" | egrep -c "[0-9]"
- 2
尽管有6个匹配项,但命令只打印出2,这是因为只有两个匹配行。在单行中出现的多次匹配只统计为一次。
为了文件中统计匹配项的数量,可以使用下面的技巧:
- $ echo -e "1 2 3 4\nhello\n5 6" | egrep -o "[0-9]" | wc -l
- 6
打印出包含匹配字符串的行数:
- $ 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
打印样式匹配所位于的字符或字节偏移:
- $ echo gnu is not unix | grep -b -o "not"
- 7:not
一行中字符串的字符偏移是从该行的第一个字符开始计算,起始值是0。在上面的例子中,“not”的偏移值是7[也就是说,not是从该行(即“gnu is not unix”这一行)的第7个字符开始的]。
选项 -b
总是和 -o
配合使用。
搜索多个文件并找出匹配文本位于哪一个文件中:
- $ grep -l linux sample1.txt sample2.txt
- sample1.txt
- sample2.txt
和 -l
相反的选项是-L
,它会返回一个不匹配的文件列表。
4.3.3 补充内容
我们已经看过grep
命令的一些基本用法。不过grep
的本事可不止如此,它具有各种丰富的特性。让我们接着看看grep
的其他选项。
- 递归搜索文件
如果需要在多级目录中对文本进行递归搜索,可以使用:
- $ grep "text" . -R -n
命令中的“.
”指定了当前目录。
例如:
- $ cd src_dir
- $ grep "test_function()" . -R -n
- ./miscutils/test.c:16:test_function();
test_function()
位于miscutils/test.c的第16行。
这是开发人员使用最多的命令之一。它用于查找某些文本位于哪些源码文件中。
- 忽略样式中的大小写
选项 -i
可以使匹配样式不考虑字符的大小写,例如:
- $ echo hello world | grep -i "HELLO"
- hello
- 用
grep
匹配多个样式
在进行匹配的时候通常只指定一个样式。然而,我们可以用选项 -e
来指定多个匹配样式:
- $ grep -e "pattern1" -e "pattern"
例如:
- $ echo this is a line of text | grep -e "this" -e "line" -o
- this
- line
还有另一种方法也可以指定多个样式。我们可以提供一个样式文件用于读取样式。在样式文件中逐行写下需要匹配的样式,然后用选项 -f
执行grep
:
- $ grep -f pattern_file source_filename
例如:
- $ cat pat_file
- hello
- cool
- $ echo hello this is cool | grep -f pat_file
- hello this is cool
- 在
grep
搜索中包括或排除文件
grep
可以在搜索过程中包括或排除某些文件。我们可以使用通配符来指定所需要包括或排除的文件。
只在目录中递归搜索所有的 .c和 .cpp文件:
- $ grep "main()" . -r --include *.{c,cpp}
注意,some{string1,string2,string3}
会扩展成somestring1 somestring2 somestring3
。
在搜索中排除所有的README文件:
- $ grep "main()" . -r --exclude "README"
如果需要排除目录,可以使用 --exclude-dir
选项。
如果需要从文件中读取所需排除的文件列表,使用--exclude-from FILE
。
- 使用0值字节后缀的
grep
与xargs
xargs
命令通常用于将文件名列表作为命令行参数提供给其他命令。当文件名用作命令行参数时,建议用0值字节作为文件名终止符,而不是用空格。因为一些文件名中会包含空格字符,一旦它被误解为终结符,那么单个文件名就会被认为是两个文件名(例如,New file.txt被解析成New和file.txt两个文件名)。而这个问题完全可以利用0值字节后缀来避免。我们使用xargs
以便于从诸如grep
、find
等命令中接受stdin
文本。这些命令可以将带有0值字节后缀的文本输出到stdout
。为了指明输入的文件名是以0值字节(\0
)作为终止符,我们应该在xargs
中使用-0
。
创建测试文件:
- $ echo "test" > file1
- $ echo "cool" > file2
- $ echo "test" > file3
在下面的命令序列中,grep
输出以0值字节作为终结符的文件名(\0
)。这可以用grep
的 -Z
选项来指定。xargs -0
读取输入并用0值字节终结符分隔文件名:
- $ grep "test" file* -lZ | xargs -0 rm
-Z
通常和 -l
结合使用。
grep
的静默输出
之前提到grep
能够返回不同格式的输出。在某些情况下,我们需要知道一个文件是否包含指定的文本。对此,我们要执行一个可以返回真假的条件测试。这可以通过设置grep
的静默条件(-q
)来实现。在静默模式(quiet mode)中,grep
命令不会向标准输出打印任何输出。它仅是运行命令,然后根据命令执行成功与否返回退出状态。
我们都知道如果命令运行成功会返回0,如果失败则返回非0值。
让我们来看一个脚本,这个脚本利用静默模式的grep
来测试文本匹配是否存在于某个文件中。
#!/bin/bash
#文件名: silent_grep.sh
#用途: 测试文件是否包含特定的文本内容
if [ $# -ne 2 ];
then
echo "$0 match_text filename"
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
这个silentgrep.sh脚本可以提供一个用于匹配的单词(Student)和一个文件名(student data.txt)作为命令参数:
- $ ./silent_grep.sh Student student_data.txt
- The text exists in the file
- 打印出匹配文本之前或之后的行
基于上下文的打印是grep
的特色之一。假设已经找到了给定文本的匹配行,通常情况下grep
只会打印出这一行。但我们也许需要匹配行之前或之后的n
行,也可能两者皆要。这可以在grep
中用前后行控制来实现。让我们看看具体的做法。
要打印匹配某个结果之后的3行,使用 -A
选项:
- $ seq 10 | grep 5 -A 3
- 5
- 6
- 7
- 8
要打印匹配某个结果之前的3行,使用 -B
选项:
- $ seq 10 | grep 5 -B 3
- 2
- 3
- 4
- 5
要打印匹配某个结果之前以及之后的3行,使用-C
选项:
- $ 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