给定一个文件“test.log”,其中包含以下内容:
line1 Patient 123 45566
line2 Patient 432
line3 Patient 234 456
line4 Patient 321
line5
我正在尝试选择line 2
并line 4
使用这种模式:
grep "Patient\s\d+\s" test.log
# but this works testing at https://rubular.com/
不起作用,这也不起作用:
grep "Patient\s\d+\n" test.log
# but this works testing at https://regexr.com/47qd5
我究竟做错了什么?
答案1
1. 使用命名类或 PCRE
GNUgrep
默认使用基本正则表达式 (BRE),但它也允许您使用扩展正则表达式 (ERE) 和 Perl 兼容的正则表达式 (PCRE)。
请注意,BRE 和 ERE 都不支持\s
也不支持\d
,但它们具有相似的功能。从man grep
:
最后,在括号表达式中预定义了某些命名的字符类,如下所示。它们的名称是不言自明的,分别是
[:alnum:]
、[:alpha:]
、[:cntrl:]
、[:digit:]
、[:graph:]
、[:lower:]
、[:print:]
、[:punct:]
、[:space:]
、[:upper:]
和[:xdigit:]
。例如,[[:alnum:]]
表示当前语言环境中数字和字母的字符类。在 C 语言环境和 ASCII 字符集编码中,这与[0-9A-Za-z]
. (请注意,这些类名中的方括号是符号名称的一部分,除了界定方括号表达式的方括号之外,还必须包含方括号。)大多数元字符在方括号表达式内会失去其特殊含义。要包含文字,]
请将其放在列表的第一位。同样,要包含文字,^
请将其放置在除开头之外的任何位置。最后,将其文字放在-
最后。
例子:
$ grep -E '^[[:digit:]]+$' << 'EOF'
> foo
> 123
> bar
> EOF
123
您还可以使用 PCRE,因为它支持\s
和\d
:
$ grep -P '^\d+$' << 'EOF'
> foo
> 123
> bar
> EOF
123
2.\n
不起作用
在 Unix 中,每个\n
分隔一个线。grep
印刷线匹配给定的模式。在这种情况下,匹配\n
本身没有意义。
您可以使用$
来匹配行尾:
$ grep -E 'foo bar$' << 'EOF'
> foo
> foo bar
> foo bar baz
> EOF
foo bar
或传递-z
/--null-data
选项来激活“多行”模式(您需要一些额外的解决方法来完全匹配您想要的):
$ grep -Poz '(?<=\n)?foo bar\n' << 'EOF'
> foo
> foo bar
> foo bar baz
> EOF
foo bar
3.你的第一个例子并不符合你的想法
最后一个\s
将匹配line 1
andline 3
而不是line 2
and line 4
:
$ grep -P 'Patient\s\d+\s' << 'EOF'
> line1 Patient 123 45566
> line2 Patient 432
> line3 Patient 234 456
> line4 Patient 321
> line5
> EOF
line1 Patient 123 45566
line3 Patient 234 456
答案2
将-P
开关与 GNU grep 一起用于 Perl 正则表达式,您的语法将按您的方式工作。
$ grep -V | head -n1
grep (GNU grep) 2.25
$ grep --help | grep "\-P"
-P, --perl-regexp PATTERN is a Perl regular expression
另请参阅这个答案了解更多信息。
答案3
正如其他人已经指出的那样,并非所有正则表达式都使用相同的符号。如果您所在的系统的默认grep
实现不是 GNU grep
,那么您就有 POSIX 正则表达式,并且它们不使用类似 Perl 的模式,例如\s
.
您似乎想要grep
以单个正整数(而不是零个或多个整数)结尾的行。查看您的数据,另一种表达方式是您希望提取仅包含三个空格分隔字段的所有行。
这很容易awk
:
$ awk 'NF == 3' test.log
line2 Patient 432
line4 Patient 321
NF
是当前记录(行)中的字段(列)数,并且只有一个条件行,默认操作是打印满足条件的所有行。
使用grep
、 以及更完整的模式来准确指定我们的期望:
$ grep -Ex '[[:alnum:]]+ [[:alpha:]]+ [[:digit:]]+' test.log
line2 Patient 432
line4 Patient 321
启用-E
扩展正则表达式(因为我们使用扩展+
修饰符),并-x
导致grep
匹配整行。
[[:alnum:]]+
匹配字母和数字(根据您的区域设置),而[[:alpha:]]+
和[[:digit:]]+
分别匹配字母和数字字符串。
另一种使用 ASCII 范围编写相同内容的方法(忽略您的区域设置):
grep -Ex '[A-Za-z0-9]+ [A-Za-z]+ [0-9]+' test.log
答案4
我在过时的 MacOS 上运行的 grep 版本grep (BSD grep) 2.5.1-FreeBSD
不支持,-P
所以我安装了 3.3 brew install grep --with-default-names
,然后我能够让它工作:
grep -P 'Patient\s\d+$' test.log