Grep
简介
给定一个或多个模式,grep在输入文件中搜索与模式匹配的行。当它在一行中找到匹配项时,它会将该行复制到标准输出(默认),或者根据您的选项产生其他类型的输出。
尽管grep期望在文本上进行匹配,但它对输入行的长度没有限制,除了可用内存之外,它可以在一行中匹配任意字符。如果输入文件的最后一字节不是换行符,grep会默默地提供一个。由于换行符也是模式列表的分隔符,因此无法在文本中匹配换行字符。
调用
grep命令行的一般语法是:
grep [选项...] [模式] [文件...]
可以有零个或多个选项参数,以及零个或多个文件参数。模式参数包含一个或多个由换行符分隔的模式,当通过“-e模式”或“-f文件”选项给出模式时会被省略。通常在shell命令中使用grep时,应该对模式进行引号处理。
通用程序信息
–help
打印一条使用消息,简要总结命令行选项和错误报告地址,然后退出。
-V
–version
将grep的版本号打印到标准输出流。这个版本号应该包含在所有错误报告中。
匹配控制
-e patterns
–regexp=patterns
将模式作为一个或多个模式;模式内的换行符将每个模式与下一个模式分开。如果此选项被多次使用或与-f(–file)选项结合使用,搜索所有给定的模式。通常,当在shell命令中使用grep时,模式应该用引号括起来。(-e由POSIX指定。)
-f file
–file=file
从文件中获取模式,每行一个。如果此选项被多次使用或与-e(–regexp)选项结合使用,搜索所有给定的模式。当文件为“-”时,从标准输入读取模式。空文件包含零个模式,因此不匹配任何内容。(-f由POSIX指定。)
-i
-y
–ignore-case
忽略模式和输入数据中的区分大小写,使得仅大小写不同的字符彼此匹配。尽管仅通过小写字母大写字母对的大小写不同的情况很简单,但在其他情况下行为是未指定的。例如,许多地区的大写字母“S”有一个不常见的小写对应项“ſ”(Unicode字符U+017F,拉丁小写字母长S),即使在将其大写化后得到“S”,也不清楚这个不寻常的字符是否匹配“S”还是“s”。另一个例子:小写的德国字母“ß”(U+00DF,拉丁小写字母锐音S)通常以两个字符字符串“SS”大写,但它不匹配“SS”,并且可能不匹配大写的字母“ẞ”(U+1E9E,拉丁大写字母锐音S),即使将其小写化得到前者。
-y是一个已弃用的同义词,用于兼容。(-i由POSIX指定。)
–no-ignore-case
不要忽略模式和输入数据中的区分大小写。这是默认设置。此选项对于传递给已经使用-i的shell脚本很有用,以便取消其效果,因为这两个选项相互覆盖。
-v
–invert-match
反转匹配的意义,选择不匹配的行。(-v由POSIX指定。)
-w
–word-regexp
只选择包含完全匹配单词的行的匹配项。测试是匹配子字符串必须位于行的开头,或者前一个字符是非单词成分字符。类似地,它必须在行的结尾或后跟非单词成分字符。单词成分字符是字母、数字和下划线。如果同时指定了-x,则此选项无效。 由于-w选项可以匹配不以单词成分字符开头和结尾的子字符串,因此与用’<‘和’>‘包围正则表达式不同。例如,尽管’grep -w @’匹配仅包含’@‘的行,’grep’<@>‘’无法匹配任何行,因为’@’不是单词成分。请参阅特殊反斜杠表达式。
-x
–line-regexp
只选择完全匹配整行的匹配项。对于正则表达式模式,这与在每个模式周围添加括号并用’^‘和’$’包围它们类似。(-x由POSIX指定。)
一般输出控制
-c
–count
禁止正常输出;相反,为每个输入文件打印匹配行的计数。使用-v(–invert-match)选项时,计算非匹配行的数量。(-c由POSIX指定。)
–color[=WHEN]
–colour[=WHEN]
将匹配的非空字符串、匹配行、上下文行、文件名、行号、字节偏移量和分隔符(用于字段和上下文行组)用转义序列包围,以便在终端上以颜色显示它们。颜色由环境变量GREP_COLORS定义,默认为“ms=01;31:mc=01;31:sl=:cx=:fn=35:ln=32:bn=32:se=36”,表示粗红色匹配文本、品红色文件名、绿色行号、绿色字节偏移量、青色分隔符以及默认终端颜色。参见环境变量。
WHEN是“always”以使用颜色,“never”不以使用颜色,或者“auto”如果标准输出与终端设备关联并且TERM环境变量的值建议终端支持颜色。纯–color被视为–color=auto;如果没有给出–color选项,则默认为–color=never。
-L
–files-without-match
禁止正常输出;相反,打印每个输入文件的名称,该文件通常不会打印输出。
-l
–files-with-matches
禁止正常输出;相反,打印每个输入文件的名称,该文件通常会打印输出。扫描每个输入文件在第一次匹配时停止。(-l由POSIX指定。)
-m num
–max-count=num
在选择了第num个选定行后停止。如果num为零,grep立即停止而不读取输入。num为-1被视为无穷大,grep不停止;这是默认值。
如果输入是从常规文件中的标准输入,并且选择了num个选定行,grep确保在退出之前将标准输入定位在最后一个选定行之后,无论是否存在尾随上下文行。这使调用进程可以恢复搜索。例如,以下shell脚本利用了这一点:
while grep -m 1 'PATTERN'
do
echo xxxx
done < FILE
但是,以下可能无法工作,因为管道不是常规文件:
# 这可能无法工作。
cat FILE |
while grep -m 1 'PATTERN'
do
echo xxxx
done
当grep在选择了num个选定行后停止时,它会输出任何尾随上下文行。当使用-c或–count选项时,grep不会输出大于num的计数。当使用-v或–invert-match选项时,grep在输出num个非匹配行后停止。
-o
–only-matching
仅打印匹配行的非空部分的匹配部分,每部分占单独的输出行。输出行使用与输入相同的分隔符,如果同时使用-z(–null-data),则分隔符为空字节(请参阅其他选项)。
-q
–quiet
–silent
安静;不要将任何内容写入标准输出。如果在找到任何匹配项时立即以零状态退出,即使检测到错误。也请参阅-s或–no-messages选项。移植性注意:Solaris 10 grep缺少-q;便携式shell脚本通常可以将标准输出重定向到/dev/null而不是使用-q。(-q由POSIX指定。)
输出行前缀控制
当需要输出多个前缀字段时,顺序始终为文件名、行号和字节偏移量,无论这些选项是按什么顺序指定的。
-b
–byte-offset
在每行输出之前打印输入文件中的0基字节偏移量。如果指定了-o(–only-matching),则打印匹配部分本身的偏移量。
-H
–with-filename
对于每个匹配项,打印文件名。当搜索多个文件时,这是默认设置。
-h
–no-filename
禁止在输出中添加文件名前缀。当只有一个文件(或只有标准输入)进行搜索时,这是默认设置。
–label=LABEL
将实际来自标准输入的输入显示为来自文件LABEL的输入。这对于在搜索之前转换文件内容的命令很有用;例如:
gzip -cd foo.gz | grep --label=foo -H 'some pattern'
-n
–line-number
在前缀输出的每一行中,使用输入文件中的1基行号。(-n由POSIX指定。)
-T
–initial-tab
确保实际行内容的第一个字符位于制表符位上,以便制表符对齐看起来正常。这在使用将输出添加到实际内容之前的选项时很有用:-H、-n和-b。这还可以在输出行号和字节偏移量之前添加空格,以便来自单个文件的所有行都从同一列开始。
-Z
–null
输出零字节(ASCII NUL字符)而不是通常跟随文件名的字符。例如,“grep -lZ”在每个文件名后输出零字节,而不是通常的换行符。此选项使输出不模糊,即使在文件名包含诸如换行符等异常字符的情况下也是如此。此选项可以与命令“find -print0”、“perl -0”、“sort -z”和“xargs -0”一起使用,以处理任意文件名,即使它们包含换行符。
上下文行控制
上下文行是匹配行附近的非匹配行。只有在使用以下选项之一时才会输出它们。无论这些选项如何设置,grep永远不会多次输出任何给定的行。如果指定了-o(–only-matching)选项,这些选项将无效,并在使用时发出警告。
-A num
–after-context=num
在匹配行之后打印num行的尾随上下文。
-B num
–before-context=num
在匹配行之前打印num行的前置上下文。
-C num
-num
–context=num
打印num行的前置和尾随输出上下文。
–group-separator=string
当使用-A、-B或-C时,在组之间的行之间打印字符串而不是–。
–no-group-separator
当使用-A、-B或-C时,不在组之间的行之间打印分隔符。
以下是关于grep如何选择在前缀字段和行内容之间打印分隔符的一些要点:
- 匹配行通常使用’:’作为前缀字段和实际行内容之间的分隔符。
- 上下文(即非匹配)行使用’-’代替。
- 当未指定上下文时,匹配行只是依次输出。
- 当指定上下文时,输入中相邻的行组成一组并依次输出,而默认情况下,非相邻组之间会出现分隔符。
- 默认的分隔符是一条’–’线;其存在和外观可以通过上述选项进行更改。
- 每个组可能包含多个匹配行,当它们足够接近以至于两个相邻的组连接并可以合并为一个连续的组时。
文件和目录选择
-a
–text
将二进制文件视为文本文件处理;等同于‘–binary-files=text’选项。
–binary-files=type
如果文件的数据或元数据表示文件包含二进制数据,则假设文件的类型为type。非文本字节表示二进制数据;这些是输出字节,当当前环境变量(参见环境变量)中未给出-z(–null-data)选项时,它们是不正确编码的输入字节(参见其他选项)。
默认情况下,type为‘binary’,grep在发现空输入二进制数据后会抑制输出,并抑制包含不正确编码数据的输出行。当某些输出被抑制时,grep会在标准错误输出后面跟随一条消息,表示有一个二进制文件匹配。
如果type为‘without-match’,当grep发现空输入二进制数据时,它会假设文件的其余部分不匹配;这等同于-I选项。
如果type为‘text’,grep会将二进制数据视为文本数据进行处理;这等同于-a选项。
当type为‘binary’时,grep可能会将非文本字节视为换行符,即使没有-z(–null-data)选项也是如此。这意味着选择‘binary’与‘text’之间会影响模式是否匹配文件。例如,当type为‘binary’时,模式‘q$’可能匹配紧跟着一个空字节的‘q’,尽管当type为‘text’时不会匹配。相反,当type为‘binary’时,模式‘.’(句点)可能不匹配空字节。
警告:-a(–binary-files=text)选项可能会输出二进制垃圾,如果输出是终端并且终端驱动程序将其中一些解释为命令,可能会导致严重的副作用。另一方面,在读取其文本编码未知的文件时,使用-a或在环境中设置‘LC_ALL=‘C’’可能有助于找到更多的匹配项,即使这些匹配项不适合直接显示。
-D action
–devices=action
如果输入文件是一个设备、FIFO或套接字,请使用action来处理它。如果action为‘read’,则所有设备都像普通文件一样读取。如果action为‘skip’,则设备、FIFO和套接字将被静默跳过。默认情况下,如果在命令行上使用设备或使用-R(–dereference-recursive)选项,则会读取设备;如果在递归过程中遇到设备并使用-r(–recursive)选项,则会跳过它们。此选项对通过标准输入读取的文件没有影响。
-d action
–directories=action
如果输入文件是一个目录,请使用action来处理它。默认情况下,action为‘read’,这意味着目录就像普通文件一样读取(某些操作系统和文件系统不允许这样做,会导致grep打印每个目录的错误消息或静默跳过它们)。如果action为‘skip’,则目录将被静默跳过。如果action为‘recurse’,grep将递归地读取每个目录下的所有文件,遵循命令行符号链接并跳过其他符号链接;这等同于-r选项。
–exclude=glob
使用通配符匹配跳过任何命令行文件名后缀与模式glob匹配的文件;名称后缀可以是整个名称,也可以是在名称中的斜杠(‘/’)之后立即跟随一个非斜杠字符的尾部部分。在递归搜索时,跳过任何子文件的基本名称与glob匹配的文件;基本名称是最后一个斜杠之后的部分。模式可以使用‘*’、’?’和‘[’…‘]’作为通配符,并使用\
来引用通配符或转义字符字面值。
–exclude-from=file
跳过文件中名称与从文件中读取的任何模式(如–exclude下所述)匹配的文件。
–exclude-dir=glob
跳过任何命令行目录名后缀与模式glob匹配的目录。在递归搜索时,跳过任何子目录的基本名称与glob匹配的目录。忽略glob中的任何冗余尾随斜杠。
-I
将二进制文件视为不包含匹配数据的文件进行处理;这等同于‘–binary-files=without-match’选项。
–include=glob
只搜索名称与glob匹配的文件,如–exclude下所述使用通配符匹配。如果给出了矛盾的–include和–exclude选项,最后匹配的一个获胜。如果没有–include或–exclude选项匹配,除非第一个这样的选项是–include
-r
–recursive
对于每个目录操作数,递归地读取和处理该目录中的所有文件。在命令行上跟随符号链接,但跳过递归遇到的符号链接。请注意,如果没有给出文件操作数,grep将搜索工作目录。这与‘–directories=recurse’选项相同。
-R
–dereference-recursive
对于每个目录操作数,递归地读取和处理该目录中的所有文件,并跟踪所有符号链接。
其他选项
–
分隔选项列表。如果存在后续参数,即使它们以‘-’开头,也被视为操作数。例如,’grep PAT – -file1 file2’会搜索名为-file1和file2的文件中的模式PAT。
–line-buffered
无论输出设备如何,都对标准输出使用行缓冲。默认情况下,对于交互式设备,标准输出是行缓冲的,否则是全缓冲的。全缓冲时,当输出缓冲区满时刷新缓冲区;而行缓冲时,每次输出一行后也会刷新缓冲区。缓冲区大小取决于系统。
-U
–binary
在区分文本和二进制I/O的平台,当读取和写入用户终端以外的文件时,使用后者,以便所有输入字节都按原样读取和写入。这覆盖了grep默认的行为,即是否使用文本或二进制I/O遵循操作系统的建议。在MS-Windows上,当grep使用文本I/O时,它将回车换行对作为换行符读取,将Control-Z作为文件结束符,并将换行符作为回车换行对写入。
当使用文本I/O时,–byte-offset(-b)计数和–binary-files启发式适用于文本I/O处理后的输入数据。此外,–binary-files启发式不需要与–binary选项一致;即使给定了–binary,它们也可能将数据视为文本,反之亦然。请参阅文件和目录选择。
此选项对GNU和其他POSIX兼容平台没有影响,这些平台不区分文本和二进制I/O。
-z
–null-data
将输入和输出数据视为由零字节(ASCII NUL字符)而不是换行符终止的行序列。与-Z或–null选项类似,此选项可以与类似于“sort -z”的命令一起使用以处理任意文件名。
使用说明
调用GNU grep的示例命令
grep -i 'hello.*world' menu.h main.c
这个命令会列出menu.h和main.c文件中包含字符串‘hello’后跟字符串‘world’的所有行;这是因为‘.*’在一行中匹配零个或多个字符。参见正则表达式。-i选项使grep忽略大小写,导致它匹配行‘Hello, world!’,否则它不会匹配。
更复杂的示例
显示了任何包含‘f’并以‘.c’结尾的行的位置和内容,这些行位于当前目录中所有以非‘.’开头、包含‘g’并以‘.h’结尾的文件名中。-n选项输出行号,–参数将任何后续参数视为文件名而不是选项,即使g.h扩展为以‘-’开头的文件名,空文件/dev/null也会使文件名被输出,即使只有一个文件名恰好是形如‘g.h’的形式。
grep -n -- 'f.*\.c$' *g*.h /dev/null
请注意,模式中使用的正则表达式语法与shell用于匹配文件名的通配符语法不同。
常见问题和答案
我如何只列出匹配文件的名称?
grep -l 'main' test-*.c
这个命令会在当前目录中搜索名为‘test-*.c’的文件,其内容提及‘main’。
我如何递归地搜索目录?
grep -r 'hello' /home/gigi
这个命令会在/home/gigi目录下搜索所有文件,查找‘hello’。为了更控制地搜索哪些文件,可以使用find和grep。例如,以下命令仅搜索C文件:
find /home/gigi -name '*.c' ! -type d \
-exec grep -H 'hello' '{}' +
这与以下命令不同:
grep -H 'hello' /home/gigi/*.c
后者只是查找/home/gigi下不以‘.’开头的C文件名以‘.c’结尾的文件中的‘hello’。上面的find命令类似于以下命令:
grep -r --include='*.c' 'hello' /home/gigi
如果模式或文件以“-”开头怎么办?
例如:
grep "$pattern" *
如果“pattern”的值以“-”开头,或者“*”扩展为以“-”开头的文件名,这可能会产生意外的结果。为了避免这个问题,你可以使用-e来表示模式,以及以“./”表示文件:
grep -e "$pattern" ./*
这将解决问题,但有一个问题是,如果有一个名为“-”的文件,grep会将“-”误解释为标准输入。
假设我想搜索整个单词,而不是单词的一部分?
grep -w 'hello' test*.log
这个命令只会搜索实例为‘hello’的整个单词;它不会匹配‘Othello’。要获得更多控制权,可以使用“<”和“>”来匹配单词的开始和结束。例如:
grep 'hello\>' test*.log
这个命令只会搜索以‘hello’结尾的单词,所以它会匹配单词‘Othello’。
我如何在匹配行的周围输出上下文?
grep -C 2 'hello' test*.log
这个命令会在每个匹配行前后打印两行上下文。
我如何使用grep强制输出文件名?
附加/dev/null:
grep 'eli' /etc/passwd /dev/null
你会得到:
/etc/passwd:eli:x:2098:1000:Eli Smith:/home/eli:/bin/bash
另一种方法是使用-H,这是GNU扩展:
grep -H 'eli' /etc/passwd
为什么人们在使用ps输出时使用奇怪的正则表达式?
ps -ef | grep '[c]ron'
如果模式没有用方括号括起来,它将不仅匹配ps输出行中的cron,还会匹配ps输出行中的grep。注意,在某些平台上,ps限制输出到屏幕宽度;grep没有行长度的限制,除了可用内存之外。
为什么grep报告“二进制文件匹配”?
如果grep从二进制文件中列出所有匹配的“行”,它可能会生成有用的输出,也可能干扰您的显示。因此,GNU grep抑制来自似乎为二进制文件的文件的输出。要强制GNU grep从似乎为二进制文件的文件输出行,请使用-a或“–binary-files=text”选项。要消除“二进制文件匹配”消息,请使用-I或“–binary-files=without-match”选项。
为什么grep不打印不匹配文件名的“-lv”?
grep -lv 'paul' /etc/motd | grep 'franc,ois'
这个命令会找到所有包含‘paul’和‘franc,ois’的行。要列出所有不包含匹配行的文件中的文件名,请使用-L或–files-without-match选项。
我可以使用“|”进行“或”,但是什么是“与”?
grep 'paul' /etc/motd | grep 'franc,ois'
这个命令会找到所有包含‘paul’和‘franc,ois’的行。
为什么空模式匹配每个输入行?
grep命令搜索包含与模式匹配的字符串的行。每行都包含空字符串,因此空模式导致grep在每一行上找到匹配项。它不是唯一的这样的模式:‘^’、’$’和许多其他模式都会导致grep匹配每一行。
要匹配空行,请使用模式’<math display="inline">'。要匹配空白行,请使用模式'^blank:*</math>’。要匹配没有行,请使用扩展正则表达式,如’a‘或’$a’。为了匹配每一行,一个可移植的脚本应该使用像’^’这样的模式,而不是空模式,因为POSIX没有指定空模式的行为。
如何在标准输入和文件中进行搜索?
使用特殊的文件名’-’:
cat /etc/passwd | grep 'alain' - /etc/motd
为什么我不能将shell的’set -e’与grep结合使用?
grep命令遵循cmp和diff等程序的惯例,其中退出状态1不是错误。shell命令’set -e’导致shell在任何一个子命令以非零状态退出时退出,这仅仅是因为grep没有选择任何行就退出了,通常这不是你想要的结果。
Bash的set -e -o pipefail也存在相关问题。由于grep并不总是读取其所有输入,输出到grep读取的管道的命令可能会在grep退出之前失败,并且该命令的失败可能会导致Bash退出。
为什么这个反向引用失败了?
echo 'ba' | grep -E '(a)\1|b\1'
这会输出一个错误消息,因为第二个’\1’没有可以引用的内容,意味着它永远不会匹配任何东西。
如何跨行匹配?
标准的grep无法做到这一点,因为它本质上是基于行的。因此,仅仅使用[:space:]字符类并不能按照你期望的方式匹配换行符。
使用GNU grep选项-z(–null-data),每个输入和输出“行”都是以空字符终止的;参见其他选项。因此,你可以匹配输入中的换行符,但通常情况下,如果有匹配项,整个输入都会被输出,所以这种用法通常与输出抑制选项一起使用,如-q,例如:
printf 'foo
bar
' | grep -z -q 'foo[[:space:]]\+bar'
如果这还不够,你可以在将其提供给grep之前转换输入,或者转向awk、sed、perl或其他许多被设计为跨行操作的工具。
grep、-E和-F代表什么?
grep的名字来自于Unix上的行编辑方式。例如,ed使用以下语法在屏幕上打印匹配行的列表:
global/regular expression/print
g/re/p
-E选项代表扩展grep。-F选项代表固定grep;
egrep和fgrep发生了什么?
7th Edition Unix有命令egrep和fgrep,它们是现代‘grep -E’和‘grep -F’的对应物。尽管将grep拆分成三个程序在1970年代的小型计算机上可能很有用,但POSIX在1992年认为egrep和fgrep已经过时,它们于2001年从POSIX中移除,GNU Grep 2.5.3在2007年弃用了它们,GNU Grep 3.8在2022年开始发出废弃警告;最终,它们计划被完全移除。
如果你更喜欢旧的名称,你可以使用自己的替代品,例如一个名为egrep的shell脚本,内容如下:
#!/bin/sh
exec grep -E "$@"