我想匹配包含 foo 的行,除非下一行包含 bar。因此给定一个包含以下内容的文件:
1 foo 1
foo 2
baz bar bap
只会1 foo 1
打印。我使用负/foo(?!.*\n.*bar)/
前瞻让它工作https://regex101.com/r/ZMZsiN/1但是使用 grep 和 perl 在命令行上使用它都失败了。任何在 perl、sed、awk 或 python 中使用 grep 或单行语句的解决方案都很好。 Chatgpt 让我失望了。
一些尝试:
$grep -Pwe 'foo(?!.*\n.*bar)' testfile
1 foo 1
foo 2
$perl -wnl -e /'foo(?!\n.*bar)/ and print' testfile
1 foo 1
foo 2
$perl -ne 'print if /foo/ && ($_ = <>) !~ /bar/' testfile
foo 2
最后一个是基于 chatgpt 提供的内容,并且很接近,但我的 perlfu 还不够好,无法找出问题所在。
答案1
grep
orperl -n
一次只处理一行,因此正则表达式匹配的只是一行的内容(行分隔符甚至不包含在 grep
or perl
with 中-l
)。
您可以使用pcregrep
(-P
GNUgrep
可以构建支持使用 PCRE 的选项),它具有多行模式和-M
.
pcregrep -M '\bfoo\b(?!.*\n.*\bbar\b)'
除了根据需要将更多行拉入匹配的主题之外,多行模式pcregrep
还启用m
标志(隐式(?m)
),该标志在每行的开头和结尾处进行^
匹配$
,而不仅仅是主题的开头和结尾,并且不是启用该s
标志意味着.
不匹配换行符。
(\b
是为了词b
边界,-w
不会在有用的地方放置单词边界)。
使用perl -n
,您可以将记录分隔符设置为正则表达式不可能在整个文件上匹配的内容:
perl -0777 -ne '
print for m{^.*\bfoo\b.*\n(?!.*\bbar\b)}mg'
使用标准 Unix 工具箱,您可以使用sed
,但标准sed
没有字边界运算符,因此您需要笨拙的解决方法:
sed -n '/^\(.*[^[:alnum:]_]\)\{0,1\}foo\([^[:alnum:]_].*\)\{0,1\}$/ {
$!N
/\n\(.*[^[:alnum:]_]\)\{0,1\}bar\(.*[^[:alnum:]_]\)\{0,1\}.*$/!P
D
}'
答案2
和grep
:
(正则表达式经过强化单词边界)
grep -Pzo '(?m)\bfoo\b(?!.*\n.*\bbar\b)' file
foo
-P
是聚合酶链反应模式-z
被NULL
\0
分隔以启用将所有行解析为字符串(\0
意味着文件名结尾)-o
旨在仅显示匹配的部分
正则表达式匹配如下:
节点 | 解释 |
---|---|
(?m) |
为此块设置标志(^ 和 $ 匹配行的开头和结尾)(区分大小写)(. 不匹配 \n)(通常匹配空格和 #) |
\b |
单词字符 (\w) 和非单词字符之间的边界 |
foo |
'富' |
\b |
单词字符 (\w) 和非单词字符之间的边界 |
(?! |
展望看看是否有: |
.* |
任何字符(0次或多次(匹配尽可能多的数量)) |
\n |
'\n'(换行符) |
.* |
任何字符(0次或多次(匹配尽可能多的数量)) |
\b |
单词字符 (\w) 和非单词字符之间的边界 |
bar |
'酒吧' |
\b |
单词字符 (\w) 和非单词字符之间的边界 |
) |
前瞻结束 |
和php(我猜 regex101 是基于php
正则表达式风格PCRE
):
php -r '$pattern = "1 foo 1
foo 2
baz bar bap";
preg_match("/foo(?!.*\n.*bar)/m", $pattern, $matches, PREG_OFFSET_CAPTURE);
print_r($matches);'
输出
Array
(
[0] => Array
(
[0] => foo
[1] => 2
)
)
要打印foo
,请使用echo $matches[0][0];
答案3
就像在生活中一样,在软件中,根据什么采取行动要容易得多有过去发生的事情(即你读过的数据)而不是基于什么将要将来发生(即您尚未读取的数据)。
使用任何 awk:
$ awk '(p ~ /foo/) && !/bar/{print p} {p=$0} END{if (p ~ /foo/) print p}' file
1 foo 1
如果当前行包含 foo 并且下一行不包含 bar,则不会尝试打印当前行,而是打印上一行(如果它包含 foo 并且当前行不包含 bar)。