打印匹配模式 1 的行,除非下一行包含模式 2

打印匹配模式 1 的行,除非下一行包含模式 2

我想匹配包含 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

greporperl -n一次只处理一行,因此正则表达式匹配的只是一行的内容(行分隔符甚至不包含在 grepor perlwith 中-l)。

您可以使用pcregrep-PGNUgrep可以构建支持使用 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模式
  • -zNULL \0分隔以启用将所有行解析为字符串(\0意味着文件名结尾
  • -o旨在仅显示匹配的部分

正则表达式匹配如下:

节点 解释
(?m) 为此块设置标志(^ 和 $ 匹配行的开头和结尾)(区分大小写)(. 不匹配 \n)(通常匹配空格和 #)
\b 单词字符 (\w) 和非单词字符之间的边界
foo '富'
\b 单词字符 (\w) 和非单词字符之间的边界
(?! 展望看看是否有:
.* 任何字符(0次或多次(匹配尽可能多的数量))
\n '\n'(换行符)
.* 任何字符(0次或多次(匹配尽可能多的数量))
\b 单词字符 (\w) 和非单词字符之间的边界
bar '酒吧'
\b 单词字符 (\w) 和非单词字符之间的边界
) 前瞻结束

(我猜 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)。

相关内容