4.3 用grep在文件中搜索文本

在文件中进行搜索是文本处理中一项重要的用例。我们也许需要根据某些规格在上千行的文件中查找所需要的数据。这则攻略将教你如何从大量数据中定位符合给定规格的数据。

4.3.1 预备知识

grep命令是UNIX中用于文本搜索的大师级工具。它能够接受正则表达式和通配符。我们可以用多个grep命令选项来生成各种格式的输出。让我们看看它具体的使用方法。

4.3.2 实战演练

在文件中搜索一个单词:

  1. $ grep match_pattern filename
  2. this is the line containing match_pattern

或者

  1. $ grep "match_pattern" filename
  2. this is the line containing match_pattern

命令会返回包含给定match_pattern的文本行。

也可以像下面这样从stdin中读取:

  1. $ echo -e "this is a word\nnext line" | grep word
  2. this is a word

一个grep命令也可以对多个文件进行搜索:

  1. $ grep "match_text" file1 file2 file3 ...

--color选项可以在输出行中重点标记出匹配到的单词:

  1. $ grep word filename --color=auto
  2. this is the line containing word

grep命令通常将match_pattern视为通配符。如果要使用正则表达式,需要添加-E选项——这意味着使用扩展(extended)正则表达式,也可以使用默认允许正则表达式的grep命令——egrep。例如:

  1. $ grep -E "[a-z]+"

或者

  1. $ egrep "[a-z]+"

为了只输出文件中匹配到的文本部分,可以使用选项 -o

  1. $ echo this is a line. | grep -o -E "[a-z]+\."
  2. line.

或者

  1. $ echo this is a line. | egrep -o "[a-z]+\."
  2. line.

要想打印除包含match_pattern的行之外的所有行,可使用:

  1. $ grep -v match_pattern file

选项-v可以将匹配结果进行反转(invert)。

统计文件或文本中包含匹配字符串的行数:

  1. $ grep -c "text" filename
  2. 10

需要注意的是-c只是统计匹配行的数量,并不是匹配的次数。例如:

  1. $ echo -e "1 2 3 4\nhello\n5 6" | egrep -c "[0-9]"
  2. 2

尽管有6个匹配项,但命令只打印出2,这是因为只有两个匹配行。在单行中出现的多次匹配只统计为一次。

为了文件中统计匹配项的数量,可以使用下面的技巧:

  1. $ echo -e "1 2 3 4\nhello\n5 6" | egrep -o "[0-9]" | wc -l
  2. 6

打印出包含匹配字符串的行数:

  1. $ cat sample1.txt
  2. gnu is not unix
  3. linux is fun
  4. bash is art
  5. $ cat sample2.txt
  6.  
  7. planetlinux
  8.  
  9. $ grep linux -n sample1.txt
  10. 2:linux is fun

或者

  1. $ cat sample1.txt | grep linux -n

如果使用了多个文件,它也会随着输出结果打印出文件名:

  1. $ grep linux -n sample1.txt sample2.txt
  2. sample1.txt:2:linux is fun
  3. sample2.txt:2:planetlinux

打印样式匹配所位于的字符或字节偏移:

  1. $ echo gnu is not unix | grep -b -o "not"
  2. 7:not

一行中字符串的字符偏移是从该行的第一个字符开始计算,起始值是0。在上面的例子中,“not”的偏移值是7[也就是说,not是从该行(即“gnu is not unix”这一行)的第7个字符开始的]。

选项 -b总是和 -o配合使用。

搜索多个文件并找出匹配文本位于哪一个文件中:

  1. $ grep -l linux sample1.txt sample2.txt
  2. sample1.txt
  3. sample2.txt

-l相反的选项是-L,它会返回一个不匹配的文件列表。

4.3.3 补充内容

我们已经看过grep命令的一些基本用法。不过grep的本事可不止如此,它具有各种丰富的特性。让我们接着看看grep的其他选项。

  • 递归搜索文件

如果需要在多级目录中对文本进行递归搜索,可以使用:

  1. $ grep "text" . -R -n

命令中的“.”指定了当前目录。

例如:

  1. $ cd src_dir
  2. $ grep "test_function()" . -R -n
  3. ./miscutils/test.c:16:test_function();

test_function()位于miscutils/test.c的第16行。

4.3 用grep在文件中搜索文本 - 图1 这是开发人员使用最多的命令之一。它用于查找某些文本位于哪些源码文件中。

  • 忽略样式中的大小写

选项 -i可以使匹配样式不考虑字符的大小写,例如:

  1. $ echo hello world | grep -i "HELLO"
  2. hello
  • grep匹配多个样式

在进行匹配的时候通常只指定一个样式。然而,我们可以用选项 -e来指定多个匹配样式:

  1. $ grep -e "pattern1" -e "pattern"

例如:

  1. $ echo this is a line of text | grep -e "this" -e "line" -o
  2. this
  3. line

还有另一种方法也可以指定多个样式。我们可以提供一个样式文件用于读取样式。在样式文件中逐行写下需要匹配的样式,然后用选项 -f执行grep

  1. $ grep -f pattern_file source_filename

例如:

  1. $ cat pat_file
  2. hello
  3. cool
  4.  
  5. $ echo hello this is cool | grep -f pat_file
  6. hello this is cool
  • grep搜索中包括或排除文件

grep可以在搜索过程中包括或排除某些文件。我们可以使用通配符来指定所需要包括或排除的文件。

只在目录中递归搜索所有的 .c和 .cpp文件:

  1. $ grep "main()" . -r --include *.{c,cpp}

注意,some{string1,string2,string3}会扩展成somestring1 somestring2 somestring3

在搜索中排除所有的README文件:

  1. $ grep "main()" . -r --exclude "README"

如果需要排除目录,可以使用 --exclude-dir选项。

如果需要从文件中读取所需排除的文件列表,使用--exclude-from FILE

  • 使用0值字节后缀的grepxargs

xargs命令通常用于将文件名列表作为命令行参数提供给其他命令。当文件名用作命令行参数时,建议用0值字节作为文件名终止符,而不是用空格。因为一些文件名中会包含空格字符,一旦它被误解为终结符,那么单个文件名就会被认为是两个文件名(例如,New file.txt被解析成New和file.txt两个文件名)。而这个问题完全可以利用0值字节后缀来避免。我们使用xargs以便于从诸如grepfind等命令中接受stdin文本。这些命令可以将带有0值字节后缀的文本输出到stdout。为了指明输入的文件名是以0值字节(\0)作为终止符,我们应该在xargs中使用-0

创建测试文件:

  1. $ echo "test" > file1
  2. $ echo "cool" > file2
  3. $ echo "test" > file3

在下面的命令序列中,grep输出以0值字节作为终结符的文件名(\0)。这可以用grep-Z选项来指定。xargs -0读取输入并用0值字节终结符分隔文件名:

  1. $ grep "test" file* -lZ | xargs -0 rm

-Z通常和 -l结合使用。

  • grep的静默输出

之前提到grep能够返回不同格式的输出。在某些情况下,我们需要知道一个文件是否包含指定的文本。对此,我们要执行一个可以返回真假的条件测试。这可以通过设置grep的静默条件(-q)来实现。在静默模式(quiet mode)中,grep命令不会向标准输出打印任何输出。它仅是运行命令,然后根据命令执行成功与否返回退出状态。

我们都知道如果命令运行成功会返回0,如果失败则返回非0值。

让我们来看一个脚本,这个脚本利用静默模式的grep来测试文本匹配是否存在于某个文件中。

  1. #!/bin/bash
  2. #文件名: silent_grep.sh
  3. #用途: 测试文件是否包含特定的文本内容
  4.  
  5. if [ $# -ne 2 ];
  6. then
  7. echo "$0 match_text filename"
  8. fi
  9.  
  10. match_text=$1
  11. filename=$2
  12.  
  13. grep -q $match_text $filename
  14.  
  15. if [ $? -eq 0 ];
  16. then
  17. echo "The text exists in the file"
  18. else
  19. echo "Text does not exist in the file"
  20. fi

这个silentgrep.sh脚本可以提供一个用于匹配的单词(Student)和一个文件名(student data.txt)作为命令参数:

  1. $ ./silent_grep.sh Student student_data.txt
  2. The text exists in the file
  • 打印出匹配文本之前或之后的行

基于上下文的打印是grep的特色之一。假设已经找到了给定文本的匹配行,通常情况下grep只会打印出这一行。但我们也许需要匹配行之前或之后的n行,也可能两者皆要。这可以在grep中用前后行控制来实现。让我们看看具体的做法。

要打印匹配某个结果之后的3行,使用 -A选项:

  1. $ seq 10 | grep 5 -A 3
  2. 5
  3. 6
  4. 7
  5. 8

要打印匹配某个结果之前的3行,使用 -B选项:

  1. $ seq 10 | grep 5 -B 3
  2. 2
  3. 3
  4. 4
  5. 5

要打印匹配某个结果之前以及之后的3行,使用-C选项:

  1. $ seq 10 | grep 5 -C 3
  2. 2
  3. 3
  4. 4
  5. 5
  6. 6
  7. 7
  8. 8

如果有多个匹配,那么以一行“--”作为各匹配之间的定界符:

  1. $ echo -e "a\nb\nc\na\nb\nc" | grep a -A 1
  2. a
  3. b
  4. --
  5. a
  6. b