【问题标题】:s/// returns out of place newlines/// 返回不合适的换行符
【发布时间】:2018-09-15 17:47:09
【问题描述】:

我正在尝试使用 Perl 重新排序 md5 文件的内容。对于每一行,我想要没有路径的文件名,然后是哈希。我想出的最好的命令是:

$ perl -pe 's|^([[:alnum:]]+).*?([^/]+)$|$2 $1|' DCIM.md5

输入文件 (DCIM.md5) 在 Linux 上由md5sum 生成。它看起来像这样:

e26ff03dc1bac80226e200c0c63d17a2  ./Path1/IMG_20150201_160548.jpg
01f92572e4c6f2ea42bd904497e4f939  ./Path 2/IMG_20150204_190528.jpg
afce027c977944188b4f97c5dd1bd101  ./Path3/Path 4/IMG_20151011_193008.jpg
  1. 哈希与
    中的第一组([[:alnum:]]+)匹配 正则表达式。
  2. 那么空格和文件路径是
    .*?匹配。
  3. 然后文件名匹配([^/]+)
  4. 表达式用^括起来(这里显然没有必要) 和$。没有$,表达式不会输出我所期望的。
  5. 我使用| 而不是/ 作为分隔符以避免在文件路径中转义。

该命令返回:

IMG_20150201_160548.jpg
 e26ff03dc1bac80226e200c0c63d17a2IMG_20150204_190528.jpg
 01f92572e4c6f2ea42bd904497e4f939IMG_20151011_193008.jpg
 afce027c977944188b4f97c5dd1bd101IMG_20151011_195133.jpg

匹配正确,输出序列正确(文件名没有路径,然后是哈希)但间距不正确:文件名后面有一个换行符。我希望它在哈希之后,像这样:

IMG_20150201_160548.jpg e26ff03dc1bac80226e200c0c63d17a2
IMG_20150204_190528.jpg 01f92572e4c6f2ea42bd904497e4f939
IMG_20151011_193008.jpg afce027c977944188b4f97c5dd1bd101

在我看来,我的命令输出了换行符,但我不知道如何改变这种行为。 或者问题可能来自 shell,而不是命令?

最后,一些版本信息:

$ perl -version
This is perl 5, version 22, subversion 1 (v5.22.1) built for i686-linux-gnu-thread-multi-64int
(with 69 registered patches, see perl -V for more detail)

【问题讨论】:

  • [^/]将匹配非斜杠(包括换行符)。也许试试[^\n/]
  • 如果你匹配一个十六进制字符串,那么最好使用[[:xdigit:]]而不是[[:alnum:]]。或者,Unicode 属性\p{Hex} 更简洁、更现代。
  • 我认为您在解决此问题方面的工作(“您的”指的是 OP),尤其是您获取文件名的策略,非常巧妙!我会将它与给出的答案结合起来。它会比我平时的更好:s|^.*/(.*)$|$1| 方法(必须与@Shawn 讨论的-l 开关以及您使用的-p 开关一起使用)。我会找到perl -lpe 's|([^/])$|$1| 来查找文件名是一行中的最后一个条目。我喜欢它!
  • 另外一个建议:使用[:xdigit:] 而不是[:alnum:],以帮助解决(当然不太可能)得到一行不是以有效哈希开头的情况。
  • @ChrisCharley,[^/] 将匹配换行符是非常正确的。我是否正确地推测它实际上是后面的加号,即<code> [^/] + </code>,导致这种行为成为问题?

标签: regex perl newline


【解决方案1】:

[^/]+ 匹配换行符,因此您输入中的那些是$2 的一部分,它在您转换后的$_ 中放在首位($1 中没有换行符,所以@ 末尾没有换行符987654326@...)

解决方案:阅读perlrun 中的-l 选项。特别是:

-l[八进制数] 启用自动行结束处理。它有两个不同的效果。首先,当与 -n 或 -p 一起使用时,它会自动选择 $/(输入记录分隔符)。其次,它分配 $\(输出记录分隔符)具有 octnum 的值,以便任何打印语句都将重新添加该分隔符。如果省略 octnum,则将 $\ 设置为 $/ 的当前值。

【讨论】:

  • 所以,基本上,您是说@raph82 缺少的只是一个-l 标志与-pe 一起使用,那么目标会实现吗?当我尝试它时,它对我有用。 $ perl -lpe 's|^([[:alnum:]]+).*?([^/]+)$|$2 $1|' DCIM.md5 给了我想要的输出。
  • @bballdave025 是的。无需使用正则表达式,使其更加神秘和嘈杂。即使它只是为了一个班轮。
  • 我喜欢这种方法。很好。
【解决方案2】:

替代解决方案,它使用了其他答案中的许多概念,以及 cmets ...

$ perl -pe 's|(\p{hex}+).*?([^/]+?)$|$2 $1|' DCIM.md5

...和解释。

在调查所有答案并试图找出答案后,我认为问题的根源在于[^/]+ 贪婪。它的贪婪导致它捕获换行符;它忽略了$ 锚。

这对我来说很难弄清楚,因为我在使用 Perl 之前使用 sed 进行了很多解析,即使是贪婪的通配符也不会捕获 sed 中的换行符。希望这篇文章能帮助那些(像我一样习惯于sed)也想知道(像我一样)为什么$ 没有“像我期望的那样”的人。

通过尝试我将发布的另一个替代答案,我们可以看到“贪婪”问题。

写入文件:

$ cat > DCIM.md5<<EOF
> e26ff03dc1bac80226e200c0c63d17a2  ./Path1/IMG_20150201_160548.jpg
> 01f92572e4c6f2ea42bd904497e4f939  ./Path 2/IMG_20150204_190528.jpg
> afce027c977944188b4f97c5dd1bd101  ./Path3/Path 4/IMG_20151011_193008.jpg
> EOF

摆脱贪婪的[^/]+,将其更改为[^/]+?。解析。

$ perl -pe 's|([[:alnum:]]+).*?([^/]+?)$|$2 $1|' DCIM.md5
IMG_20150201_160548.jpg e26ff03dc1bac80226e200c0c63d17a2
IMG_20150204_190528.jpg 01f92572e4c6f2ea42bd904497e4f939
IMG_20151011_193008.jpg afce027c977944188b4f97c5dd1bd101

已完成所需的输出。

accepted answer,@Shawn,

$ perl -lpe 's|^([[:alnum:]]+).*?([^/]+)$|$2 $1|' DCIM.md5

基本上更改了$ 锚点,以使其行为符合sed 人的期望。

@CrafterKolyan 的answer 负责处理贪婪的[^/] 捕获换行符,因为它说你不能有正斜杠换行符。这个答案还是需要$锚来防止出现以下情况

1) .* 捕获空字符串(0 或更多任意字符)

2) [^/\n]+ 捕获 .

@Borodin 的回答采用了完全不同的方法,但这是一个很棒的概念。

此外,@Borodin 制作了一个很棒的 comment,它允许这个答案的更精确/更精确的版本,这是我放在这篇文章顶部的版本。

最后,如果有人想关注Perl programming model,这里还有另一种选择。

$ perl -pe 's|([[:xdigit:]]+).*?([^/]+?)(\n\|\Z)|$2 $1$3|' DCIM.md5

附:因为sedperl (no non-greedy wildcards,) 不太一样,所以这里有一个sed 示例来展示我所讨论的行为。

$ sed 's|^\([[:alnum:]]\+\).*/\([^/]\+\)$|\2 \1|' DCIM.md5

这基本上是perl 表达式的“直接翻译”除了,用于[^/] 之前的额外'/'。我希望它能帮助那些比较sedperl的人。

【讨论】:

    【解决方案3】:

    使用[^/\n] 代替[^/]

    perl -pe 's|^([[:alnum:]]+).*?([^/\n]+)$|$2 $1|' DCIM.md5

    【讨论】:

    • 为什么这被否决了?它精确地提供了 OP 所需的输出。此外,它还提供了可执行代码,这总是很好的答案。
    【解决方案4】:

    进行替换让您必须编写一个正则表达式模式来匹配您想要的一切以及您所做的一切。通常最好只匹配您需要的部分并从中构建另一个字符串

    这样

    for ( <> ) {
        die unless m< (\w++) .*? ([^/\s]+) \s* \z >x;
        print "$2 $1\n";
    }
    

    或者如果你必须有一个单线

    perl -ne 'die unless m< (\w++) .*? ([^/\s]+) \s*\z >x; print "$2 $1\n";' myfile.md5
    

    输出

    IMG_20150201_160548.jpg e26ff03dc1bac80226e200c0c63d17a2
    IMG_20150204_190528.jpg 01f92572e4c6f2ea42bd904497e4f939
    IMG_20151011_193008.jpg afce027c977944188b4f97c5dd1bd101
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-10-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-03-27
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多