内容来源基本上是整理info grep而来,当然只整理了关于用法的部分。有些地方给出了些解释,算是对grep的一个细节概述吧。
1.1 grep选项
grep家族:grep -G或者grep使用基本正则表达式;grep -E或者egrep使用扩展正则表达式;grep -F或者fgrep使用固定字符串进行匹配,不使用正则表达式进行匹配,速度很快。
grep [options] pattern [file...]
1.1.1 关于匹配类型选项
-G:使用基本正则表达式,即默认的。所以一般都会省略该选项。
-E:使用扩展正则表达式进行匹配,grep -E等价于egrep。
-F:使用固定字符串进行匹配,不使用正则表达式进行匹配,grep -F等价于fgrep。
-P:使用Perl正则表达式。
1.1.2 关于匹配控制选项
-e:可以指定多个匹配模式,多个匹配模式之间是or的逻辑关系。并且使用-e可以避免模式中“-”开头时当作grep的选项来解析。
如:grep '-a'被grep认为是-a选项,使用-e可以避免,grep -e '-a'。
多个模式如:-e pattern1 -e pattern2。多个匹配模式也可以写入file通过-f执行。
-f:指定pattern的来源文件。-f file。
-i:ignore case 忽略大小写。
-v:reverse反向显示,显示没有被模式匹配的行。
-w:指定单词精确匹配。可以替代\<WORD\>或者\bWORD\b的锚定匹配方式。
单词的组成字符只能是:字母、数字和下划线,其他字符都会分隔单词。
-x:指定精确行匹配。必须匹配整行完全相同的内容。
1.1.3 关于输出控制选项
-c:不输出匹配内容,输出每个文件的匹配行数(不是匹配次数),若指定-v将输出反转后的行数。
--color:为匹配的内容加颜色。
-l:输出匹配的内容在哪个文件里。常和-R配合使用。-lv的优先级是先-v再-l。
-L:输出哪些文件不包含匹配的内容,和-l相反。
-m NUM:输出每个文件匹配行的前NUM行。
-n:同时输出每个文件匹配内容所在的行号。如果多个文件中有匹配内容还将输出文件名。
-o:显示被模式匹配的字符串,而不是显示行。每个匹配的串分行显示。
-q:静默,不输出任何内容直接退出,如果匹配了内容,状态码$?=0,否则不为0。
-s:静默,禁止输出对不存在文件或无权访问文件给出的错误信息。
-Z:在每个输出文件名后使用\0符号而不是换行符。常和-l选项一起使用来使结果精确安全。
1.1.4 关于输出上下文选项
-A NUM:除了显示被匹配的行,还显示该行的后NUM行。
(after)。如果后NUM行中又出现了匹配内容,将不会重复计算NUM。
-B NUM:除了显示被匹配的行,还显示该行的前NUM行。
(before)如果前NUM行中又出现了匹配内容,将不会重复计算NUM。
-C NUM:除了显示被匹配的行,还显示该行的前NUM行和后NUM行。
(context上下文,上下各NUM行)如果前后NUM行中又出现了匹配内容,将不会重复计算NUM。
1.1.5 关于搜索文件和目录选项
-r和-R:递归搜索给定的目录,该目录下的所有文件和子目录里的文件都搜索。
--include=glob_file:递归搜索时只搜索通配符匹配的file或目录。可以通配符指定包含的文件或目录。
--exclude-from=glob_file:递归搜索时,排除通配符匹配的file,可以通配符指定排除的搜索文件。
--exclude-dir=directory:递归搜索时,排除搜索的目录。
1.2 grep正则
grep支持3种正则:基本正则表达式grep -G,扩展正则表达式grep -E,Perl语言正则表达式grep -P。
grep的正则匹配工作在贪婪模式下,如果一行中有多个符合条件的匹配,它将总是选择最长匹配作为结果。
大多数字符包括所有的字母、数字是匹配它们自身的正则表达式,但是有些具有特殊意义的元字符作为正则表达式匹配时需要使用反斜线\转义。
1. 字符类和中括号表达式
中括号[]中的是字符列表,它会匹配中括号中的任意一个字符,如果中括号中以脱字符“^”开头,则匹配不包含在中括号列表中的字符。
中括号中使用横线“-”分隔两个字符表示匹配这两个字符之间包括这两个字符范围内的任意单个字符。在C语言风格下,[a-d]扩展后等价的是[abcd],但是很多区域字符集的排序方法是根据字典的,这种情况下[a-d]扩展后等价的是[aBbCcDd]。
某些特定名称的字符类是预定义的字符类,包括[:alnum:]、[:alpha:]、[:digit:]、[:punct:]、[:cntrl:]、[:graph:]、[:lower:]、[:print:]、[:upper:]、[:blank:]、[:space:]、[:xdigit:]。
它们的意义分别是:
[:alnum:]所有的数字和大小写字母
[:alpha:]所有的大小写字母
[:blank:]所有水平空白=space+tab
[:cntrl:]所有控制字符(非打印字符),在ascii表中的八进制0-37对应的字符和177的del
[:digit:]所有数字
[:graph:]所有打印字符,不包含空格=数字+字母+标点
[:lower:]所有小写字母
[:print:]所有打印字符,包含空格=数字+字母+标点+空格
[:punct:]所有标点符号
[:space:]所有水平或垂直空白=空格+tab+分行符+垂直tab+分页符+回车键
[:upper:]所有大写字母
[:xdigit:]所有十六进制数字
在这些预定义的字符类中,左右两边的中括号和冒号是字符类的一部分,因此想要匹配某一字符类,应该将它放在另一个中括号中。
例如[[:alnum:]]代表的是[0-9A-Za-z],[^[:alnum:]]代表但是[^0-9A-Za-z]。
字符类中中括号的特殊意义:
"]" 表示字符类的结束符号,放在中括号中的第一个字符位将表示字面意义。
例如"[]abc]"匹配的是"]"或"a"或"b"或"c"。
"[." 表示校验类的开始符号。[.ab.]表示将“ab”作为整体匹配,不匹配a或b。
".]" 表示校验类的结束符号。
"[=" 表示等价类的开始符号。
例如[=e=]表示将字母e的第一声和第三声等不同音节的同字母看成相同字符。
"=]" 表示等价类的结束符号。
"[:" 表示字符类的开始符号。
":]" 表示字符类的结束符号。
大多数元字符在中括号[]中都失去了它们的特殊意义(此处指的是扩展表达式),但是"]"、"^"和"-"这三个在中括号中有特殊意义的字符需要特殊对待。如果想匹配符号“]”,应该将它放在中括号的第一个字符位即[]],此时它代表的只是普通字面的符号,而不是中括号的结束标志;如果想匹配符号“-”,也应该将它放在非第一个字符位;如果想匹配符号“^”,应该将它放在非第一个字符位。
2. 行首尾部的锚定
脱字符“^”和美元符号“$”这两种元字符分别代表行首和行尾的空字符。
3. 反斜线“\”和特殊表达式
“\<”和“\>”分别表示单词的左边界和右边界的空字符;
\b表示单词边界(可左可右)的空字符;
\B表示非单词边界的空字符;
\w是[[:alnum:]]的同义词;
\W是[^[:alnum:]]的同义词。
4. 匹配的重复次数
. 点“.”匹配任意一个字符。
? 前面的字符条目是可选的并且最多匹配一次。即匹配0或1次。
* 前面的字符条目将匹配0或多次。
+ 前面的字符匹配1或多次。
{n} 前面的字符精确匹配n次。
{n,} 前面的字符匹配至少n次。
{n,m} 前面的字符匹配至少n次至多m次。
5. 串联正则表达式
两个正则表达式可以被串联起来,串联后的正则表达式的匹配结果是对应子字符串的串联结果。
6. 二者选一的“|”选项
两个正则表达式可以使用"|"连接起来表示它们是二者选一,匹配的结果是"|"连接起来的两个正则表达式的任意一个的结果。
7. 正则表达式之间的优先级
重复选项的匹配优先级高于串联匹配高于二者选一的“|”匹配。使用括号可以改变优先级规则。
8. 分组子表达式和后向引用
\(char\):1.将匹配作为整体;2.该整体可以使用\N(N为数字)后向引用。
扩展正则下省略反斜线。
\1:引用第一个左括号和对应右括号匹配的内容。
\2:引用第二个左括号和对应右括号匹配的内容。
\3:引用第三个左括号和对应右括号匹配的内容。
……
从左括号向右数以确定是第几个整体,从而确定\N的N是几。
例如:
\(ab\)匹配的是ab整体。
\(r..t\).*\(b..h$\)从前向后分别是\1和\2。
\(r..t.*\(b..h$\)\)从前向后外面的括号是\1,内部括号的整体是\2。
反向引用失效的情况:
当使用二者选一的选项"|"时,如果括号()中的内容没有参与匹配,后向引用将不起作用。
例如:
echo 'ba' | grep -E '(a)\1u|b\1'
将只匹配“aau”的行,不匹配“ba”的行,因为在二者选一的第二个正则中\1代表的分组没有参与匹配,所以第二个正则中的“\1”失效,但是第一个正则中的“\1”有效。
在grep的bug报告中指明反向引用的速度很慢,可能需要多消耗几个数量级的时间。
9. 基本正则表达式和扩展正则表达式的异同
在基本正则中,元字符"?"、"+"、"{"、"|"、"("和")"失去它们特殊的意义,应该使用反斜线版本"\?"、"\+"、"\{"、"\|"、"\("和"\)"来替代它们表示它们特殊元字符意义。在意义上它们是相同的。
1.3 grep常见问题
1.3.1 如何列出匹配了内容的某些文件
grep -l 'main' *.c
这会列出当前目录中所有的c文件中匹配了“main”的c文件。
1.3.2 如何搜索递归目录
grep -r 'hello' /home/gigi
会在指定的/home/gigi下搜索所有文件来匹配“hello”。
对于更多的搜索控制应该使用find -print0,grep,xargs -0联合操作。
find /home/gigi -name '*.c' -print0 | xargs -0r grep -H 'hello'
这会搜索/home/gigi目录下的所有c文件,然后交给xargs,最后传递给grep,-H的作用是当给出了多个搜索文件时,在grep的stdout中同时打印出匹配的行来源于哪个文件。
上面的命令不同于:
grep -rH 'hello' *.c
因为当grep中使用-r或-R递归搜索时,如果搜索文件给出的是文件名而不是目录名,将不会进行递归。例如这里的命令只会搜索当前目录下的所有c文件,除非当前目录下有“.c”结尾的目录。
上面find命令行类似于下面的命令:
grep -rH --include='*.c' 'hello' /home/gigi
1.3.3 匹配模式pattern以短横线“-”开头会怎样?
grep '-ex' ./*.txt
表示搜索当前目录下所有txt文件中包含"x"的文件,它被grep认为等价于这样的命令:
grep -e 'x' ./*.txt
甚至认为是
grep -e -x ./*.txt
使用-e选项可以避免横线“-”开头的模式被grep当成选项来解析。所以应该使用这样的命令搜索包含“-ex”字符的txt文件。
grep -e '-ex' ./*.txt
或者使用中括号、小括号、转义符“\”。
grep '[-]ex' ./*.txt
grep '\(-\)ex' ./*.txt
grep '\-ex' ./*.txt
1.3.4 如何搜索一个单词
grep -w 'hello' *
搜索当前目录下所有文件中的单词hello,它不会匹配xhelloy,也不会匹配xhello和helloy。
更宽松的边界指定可以在模式中使用"\<"、"\>"、"\b"或"\w"等锚定符锚定。如\<hello匹配以hello开头的单词,\>匹配以hello结尾的单词。
1.3.5 如何让“grep”强制输出匹配的来源文件
使用-H选项或者添加一个特殊的搜索文件/dev/null。
grep -H 'root' /etc/passwd
grep 'root' /etc/passwd /dev/null
因为默认多个文件时的选项就是-H(输出来源文件),单个文件时的选项是-h(禁止输出来源文件)。
1.3.6 为什么在ps命令后使用grep时使用特殊的正则表达式
ps -ef | grep '[c]ron'
ps -ef | grep 'cron'
第一个命令消除了grep自身进程的结果,而第二个匹配了两行:一行cron,一行grep cron。
之所以会这样,是因为grep '[c]ron'进程在ps -ef的结果中的字符是“grep [c]ron”,该grep程序相当于是对这样的一行进行结果匹配,这样就很容易理解为什么传给grep程序的stdin中的这一行无法被其自身匹配。
root 20490 20182 0 20:10 pts/0 00:00:00 grep --color=auto [c]ron
第一条命令等价于这样的命令:
ps -ef | grep 'cron' | grep -v 'grep'
1.3.7 为什么grep -lv不输出没有匹配结果的文件名
grep -lv输出那些包含了一行或多行不匹配的文件名,例如a.txt中有部分行匹配了,部分行没匹配,而b.txt完全没有匹配行,那么grep -lv将输出a.txt而不是b.txt。
由此可推测,grep -lv中的优先级是先grep -v再-l,即输出包含反转后的内容的文件。
如果想输出完全没有匹配行的文件名,使用-L选项。
1.3.8 使用“|”可以实现or逻辑,如何实现and逻辑
grep 'paul' /etc/motd | grep 'franc,ois'
匹配包含了"paul"和"franc,ois"的行。
1.3.9 如何指定搜索来源为标准输出和输入文件
使用stdin符号"-"或者stdin设备/dev/stdin
ls | grep 'flip' - *
ls | grep 'flip' /dev/stdin *
1.3.10 为什么后向引用"\NUM"失效了
当使用二者选一的选项"|"时,如果括号()中的内容没有参与匹配,后向引用将不起作用。
echo 'ba' | grep -E '(a)\1u|b\1'
将只匹配“aau”的行,不匹配“ba”的行,因为在二者选一的第二个正则中\1代表的分组没有参与匹配,所以第二个正则中的“\1”失效,但是第一个正则中的“\1”有效。
所以使用“|”时,如果左边的正则中使用了正则分组,右边的正则中对其后向引用将总是失效。