grep 未按预期工作

grep 未按预期工作

给定一个文件“test.log”,其中包含以下内容:

line1 Patient 123 45566
line2 Patient 432
line3 Patient 234 456
line4 Patient 321
line5

我正在尝试选择line 2line 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 1andline 3而不是line 2and 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

相关内容