我犯的令人困惑的grep错误

2020-11-06 07:15:40

在本文中,我将讨论5个非常令人困惑的错误,这些错误导致我在使用命令行工具(即grep;)搜索内容时浪费了大量时间。我之所以选择详细记录这些错误,是因为它们是初学者在某个时候很可能会犯的错误,也是为了知道能够自己调试。&;nsbp这些错误的根本原因是:不知道grep当前使用的正则表达式的风格(和/或不了解该风格支持什么功能);没有考虑外壳的转义规则;字符编码问题。

下面是一个包含几行文本的文件,我们将把它们放在文件hello.txt';中:

假设您想要使用grep&39;命令查找该文件中包含单词##39;Hello;和单词##39;World;的所有行。*您可以使用如下grep命令:

正如您所期望的那样,此grep命令找到了您期望的所有匹配项:

但现在您可以考虑添加一个附加要求,即在Hello和World之间至少要有一个字符,这样包含HelloWorld的行就不会包含在匹配项中。因为您知道';*&是零个或多个的正则表达式模式,而';*是一个或多个';的正则表达式模式,所以您决定尝试以下操作:

但这根本不匹配!这是怎么回事?这不是一个代表一个或多个正则表达式的正则表达式符号吗?答案与grep使用的默认正则表达式模式有关。*如果您不为grep指定任何标志,它将使用非常古老且非常原始的基本正则表达式。事实上,BRE的官方标准甚至不支持量词!这可能会导致非常令人困惑的行为,因为你可能只是尝试逃离它,然后发现它会给你带来你所期望的结果:

但由于我们仍在使用正则表达式,官方标准称这实际上是未定义的行为!您可以通过grep-E在未定义的行为中了解更多信息。

如果要查找该文件中包含单词日期的所有行,可以使用以下grep命令:

但现在,让我们假设您想要使用grep只查找单词Date;周围包含反引号字符的行。您可以尝试执行以下操作:

GREP:OCT:没有这样的文件或目录grep:21:没有这样的文件或目录grep:13:30:57:没有这样的文件或目录grep:est:没有这样的文件或目录grep:2020:没有这样的文件或目录。

你可能会想,哦,没问题,我只需要用双引号,然后试试这样的做法:。

但这仍然不起作用(至少在bash中不起作用)!但它根本找不到任何匹配项!这种情况下的问题与反号字符在我们的Shell中有特殊含义有关,即使用在双引号中也是如此。为了说明这一点,我们可以运行以下两个ECHO命令:

因此,通过阅读上面的示例结果,您可以理解为什么我们上次使用的grep命令没有找到任何结果:我们实际上是在搜索当前日期,而不是用反引号括起来的单词!解决方案(在bash shell中)是使用单引号:

这并不是您可能遇到的唯一问题,您的Shell可能会意外更改传递给grep的搜索字符串的含义。当您尝试在不使用引号的情况下使用包含字符的正则表达式时,也可能会遇到意外的反义字问题。?例如,考虑一下这个简单的ECHO语句,它只打印出以下内容:

如果您通过grep搜索字符过滤此ECHO语句,如下所示:

搜索会如期通过这条线。同样,如果您使用grep进行正则表达式搜索,搜索后跟任意数量的其他字符,如下所示:

这也会让这辆车通过。但是,如果您在当前目录中创建一个名为';a.txt';的新文件:

什么!??创建一个新文件如何改变我们的grep命令在Shell中运行的方式?这篇关于Shell全局处理的文章详细解释了这个问题。

这个错误并不是grep所特有的,因为它实际上大体上是关于正则表达式的,但它很常见,足以包含在本文中。假设您正在尝试使用grep来提取包含小数点的所有数字实例。在搜索中,您需要查找一个或多个数字,后跟句点,最后是一个或多个数字。您可以尝试编写如下grep命令:

它看起来工作得很好,因为它确实符合你想要的所有东西。但问题是,这也与你不想要的东西相匹配:

在上面的例子中,我们的正则表达式将匹配不是小数点数字的模式';234A328';。当您指出它时,这种情况就会变得显而易见,因为在大多数正则表达式引擎中,字符通常代表除换行符以外的任何字符。*为了匹配正则表达式中的文字句点字符,您需要对其进行转义:

#与ECHO";234A328";|grep-eo";[0-9]+\不匹配。[0-9]+";#与ECHO";234.328";|grep-eo";[0-9]+\匹配。[0-9]+";

我们得到的教训是,在使用包含字符的搜索时要小心,因为它可能并不总是字面意思是句点字符。

下面是我们将放在一个名为Animals.txt;的文件中的一些文本。请注意,此文件中的两列用制表符(\t)分隔:

假设我们想要编写一条grep语句来从该文件中提取第一列。我们可以通过编写一个正则表达式来快速而粗略地实现这一点,该正则表达式将提取所有向上的内容,并包括制表符。以下是使用以下grep命令执行此操作的尝试:

原因也是因为Grep的默认正则表达式模式:BRE或基本正则表达式。但是,如果我们尝试对扩展正则表达式使用-E标志,这并不能解决问题:

事实上,如果您查看BRE和ERE的官方标准,您会发现它不支持只匹配一个制表符!。在POSIX BRE或ERE中,只有几个字符可以用反斜杠转义,而且它们不包括制表符。

令人困惑的是,GNU grep确实支持ERE之类的东西,尽管POSIX标准并不正式支持它。

在我们的例子中,解决方案是对与Perl兼容的正则表达式使用-P标志:

遗憾的是,并非所有版本的grep都支持';-p;标志,因此此解决方案并非始终可用。

这是一个你不会每天都会遇到的问题,但当你遇到这个问题时,你可能会非常困惑,无法弄清楚到底发生了什么。*如果您碰巧使用以UTF-16编码的文件,您必须记住这样一个事实,即grep不知道字符编码,因此,您grep所需的内容可能只有在其字符编码与您键入grep命令的终端的当前编码相匹配的情况下才能被找到。(#xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx。

例如,假设您有两个文件:第一个以UTF-8编码的文件包含以下文本:

在我的机器上,如果我使用以下grep命令对这两个文件执行grep搜索:

当你知道正在发生什么的时候,这一事实并不太令人惊讶,但困难的是注意到你得到的文件一开始就是用不同的格式编码的。如果采用常规ASCII字符并将其重新编码为UTF-16,则得到的文件看起来就像常规ASCII编码文本,在字符之间放置空值。因此,如果您将文件打印到终端上,空值将被忽略,您看到的打印内容将看起来与常规ASCII文本没有区别(字节顺序标记除外)。像vim这样的程序会自动识别编码,并将文件显示为普通文本,所以你很可能不会注意到编码。

00000000:fffe4800 6500 6c00 6c00 6f00 2000 5700.H.E.L.O.。.W.00000010:6f00 7200 6c00 6400 2000 3400 3500 3600 o.r.l.d.。.4.5.6.00000020:2100 0a00!...。

如您所见,UTF-16编码的文件看起来就像ASCII文本,每个字符之间都有空字符。

那么,我们如何使用grep在UTF-16编码的文件中找到匹配项呢?这实际上是grep实际上不是最好的工具的少数几种情况之一。一种选择是将您的文件标准化为UTF-8/ASCII编码。*您可以使用';iconv';命令在不同编码之间转换文件:

由于该文件现在在file3.txt中被编码为ASCII/UTF-8,因此您最初的grep命令应该会找到预期的匹配项。

另一个不太理想的选择是将';-P&P标志与grep一起使用,并在grep命令中显式包含用于UTF-16编码的空字符:

这看起来相当混乱,而且由于并非所有版本的grep都支持';-P;,所以您不能总是使用此选项。它还要求您在每次怀疑可能存在UTF-16文件时进行单独的搜索(如果存在更多编码,则搜索更多)。

另一件需要注意的事情是,上述命令中的';-a&a;标志是必需的,否则grep将把UTF-16文件视为二进制数据并拒绝搜索它们。

希望您已经在本文中了解了一些关于grep和Shell环境的知识。我觉得我需要写一个结束语,以避免文章结尾过于草率,但在这一点上真的没有什么可说的了,如果我继续写下去,我就会漫无边际了。如果你愿意的话,我想我们可以谈谈天气。你过得怎么样?