grep 语法与正则表达式不同吗?

grep 语法与正则表达式不同吗?

我想从这里提取已删除软件包的名称“cat /var/log/dpkg.log | grep 'remove'”

 2013-09-09 15:57:34 remove activity-log-manager:i386 0.9.4-0ubuntu6.2 <none>
 2013-09-09 15:57:35 remove activity-log-manager-control-center:i386 0.9.4-0ubuntu6.2 <none>
 2013-09-09 15:57:38 remove alacarte:all 3.6.1-0ubuntu3 <none>
 2013-09-09 15:57:41 remove deepin-software-center:all 2.1.2.1~precise~NoobsLab.com <none>

我只想抓取软件包名称后面的 remove 和冒号之间的软件包名称。我不是正则表达式专家,我创建了一个似乎可以完成工作的正则表达式,但当我想用 grep 应用它时,什么也没有发生。这是正则表达式评估器中的工作正则表达式模式

(?<=remove)(.*?)(?=:)

但这不起作用:

cat /var/log/dpkg.log | grep 'remove' | grep '(?<=remove)(.*?)(?=:)'

我在这里遗漏了什么?

答案1

正则表达式语法有一个共同的核心,但也有不同风格。您的表达式似乎包含一些特定于 perl 风格的特点,特别是使用复杂的环视断言描述要匹配的模式的开始和结束,而 grep 默认使用基本正则表达式 (BRE) 语法,该语法仅支持一组更简单的零长度匹配例如 line- ( ^, $) 和 word-anchors ( \>, \<)。

您可以使用 -P 命令行开关在 grep 中启用 perl 兼容正则表达式 (PCRE) 支持(但请注意,手册页目前将其描述为“实验性的”)。在您的例子中,您可能还希望 -o 开关仅打印匹配的模式,而不是整行,即

cat /var/log/dpkg.log | grep 'remove' | grep -oP '(?<=remove)(.*?)(?=:)'

请注意,如果遇到没有 :i386 后缀的软件包,此表达式可能会失败,因为它可能会提前读取下一个单词中匹配的冒号,例如

echo "2013-09-07 08:31:44 remove cifs-utils 2:5.1-1ubuntu2 <none>" | grep -oP '(?<=remove)(.*?)(?=:)'
 cifs-utils 2

您可能希望看看 awk,例如

cat /var/log/dpkg.log | awk '$3 ~ /remove/ {sub(":.*", "", $4); print $4}'

除了 BRE 和 PCRE 之外,Gnu grep 还有一种模式,称为扩展正则表达式(ERE),由 -E 命令行开关指定。手册页指出

In  GNU grep,  there is  no difference in available functionality 
between basic and extended syntaxes.

然而你应该注意到“可用功能没有差异”不是意味着语法相同。例如,在 BRE 中,字符+通常被视为文字,并且只有在转义时才成为修饰符,表示“前面的正则表达式的一个或多个实例”,即

$ echo "123.456" | grep '[0-9]+\.[0-9]+'
$ echo "123.456" | grep '[0-9]\+\.[0-9]\+'
123.456

而对于 ERE 来说,情况恰恰相反

$ echo "123.456" | grep -E '[0-9]+\.[0-9]+'
123.456
$ echo "123.456" | grep -E '[0-9]\+\.[0-9]\+'

sed对于不使用和使用开关的调用也适用类似的区别-r

答案2

grep手册页中:

grep 在命名的输入文件 (如果没有命名文件,或者如果文件名为单个连字符减号 (-),则在标准输入中搜索) 中查找包含与给定模式匹配的行。

据我所知,grep无法编辑匹配的行;我会使用sed或可能tr。以下任何一种都应该可以满足您的要求:

cat /var/log/dpkg.log | grep 'remove' | sed 's/.*remove \([^:]*\):.*/\1/'
cat /var/log/dpkg.log | grep 'remove' | sed -E 's/.*remove ([^:]*):.*/\1/'
cat /var/log/dpkg.log | sed -n '/remove/s/.*remove \([^:]*:\).*/\1/p'
cat /var/log/dpkg.log | sed -nE '/remove/s/.*remove ([^:]*):.*/\1/p'

说实话,我不确定您(?<=remove)(.*?)(?=:)想要完成什么。在正则表达式中,括号用于定义捕获组:您可以看到我在 sed 命令中使用了它们 - 在那里,匹配的所有内容都将被捕获组(/1第一个要定义的组)的内容替换。

相关内容