正则表达式:仅匹配最深的列表级别

正则表达式:仅匹配最深的列表级别

我编制了游戏中所需的材料清单,从最上层到最原始的成分。然而,现在我正在寻找一种快速统计数字的方法。

21 reinforced alloy
    21 damascus steel
        21 steel
            21 iron dust
            21 carbon
            21 iron
        21 iron dust
        21 carbon
        21 iron
    21 hardened metal
        21 damascus steel
            21 steel
                21 iron dust
                21 carbon
                21 iron
            21 iron dust
            21 carbon
            21 iron
        21 duralmin
            21 aluminum dust
            21 copper dust
            21 aluminum
                21 aluminum dust
        21 compressed carbon
            84 carbon
        21 aluminum bronze
            21 aluminum dust
            21 bronze
                21 copper dust
                21 tin dust
                21 copper
            21 aluminum
                21 aluminum dust
    21 corinthian bronze
        21 silver dust
        21 gold dust
        21 copper dust
        21 bronze
            21 copper dust
            21 tin dust
            21 copper
    21 solder
        21 lead dust
        21 tin dust
        21 lead
            21 lead dust
    21 billon
        21 silver dust
        21 copper dust
        21 silver
            21 silver dust
    21 gold 24 carat

最高层并不重要,因为我正在寻找我需要收集的原材料。例如,21 hardened metal21 damascus steel并不重要,因为我正在寻找 的总数42 damascus steel,这也无关紧要,因为我正在寻找42 iron dust, 42 carbon, 和42 iron(此示例不计算列表的其余部分),原材料总数。

到目前为止,我在正则表达式测试网站,但最终我希望能够使用,grep这样我就不必打开网站来进行计数。我想要得到类似“碳出现 5 次,这是匹配线”这样我可以更容易地计算,因为如果我知道碳出现 5 次,其中 4 次是21 carbon,1 次是84 carbon,我现在可以轻松计算出我总共需要21*4 + 84 = 168 carbon.

我正在尝试计算没有另一行且后面有大量选项卡的行,因为如果有的话,那么它就不是原材料。

/(\t+)\d+ aluminum\n(?!\1)/g(用我试图找到的任何原材料替换“铝”)

但这并没有发现任何东西。有没有办法实现我想用正则表达式实现的目标?如果是这样,怎么办?

感谢您的时间。


我不确定是否将其放在 SO 或 SE 上,但考虑到我最终希望能够使用,grep我认为这可能是更合适的地方。

答案1

如果你想使用类似 perl 的正则表达式,为什么不使用真正的正则表达式:

<your-file perl -l -0777 -ne '
  while (m{^(\s*+)(\d+) (.*)$(?!\n\1\s)}mg) {
    $count{$3} += $2
  }
  END {
    printf "%4d %s\n", $count{$_}, $_ for sort keys %count
  }'

这使:

  84 aluminum dust
 168 carbon
  42 copper
 105 copper dust
  21 gold 24 carat
  21 gold dust
  84 iron
  84 iron dust
  42 lead dust
  63 silver dust
  63 tin dust

-0777 -n意味着整个输入都被吸进了$_.操作m符的 ultiline 标志使得m{...}^匹配$在每行的开头和结尾,$_而不仅仅是在 的开头和结尾$_。如果没有该s 标志,则.与换行符不匹配,但请注意,\s如果输入中有空行,这可能会导致这里出现问题。

\s*+是 的非回溯版本\s*。这里并不是绝对必要的,因为 ( \d+) 后面的内容不能匹配空格。

Standardgrep不支持类似 perl 的正则表达式,例如您正在使用的那些\d和perl RE 运算符,但您可以使用它恰好也支持多行模式:(?!\1)pcregrep-o-M

<your-file pcregrep -Mo '^(\s*+)\K.*$(?!\n\1\s)'

您仍然需要通过管道进行其他操作,例如perl进行awk求和,因此这perl与用于所有操作相比没有什么优势。

如果缩进可能混合有制表符和空格,您可能希望输入通过其中之一expandunexpand首先将它们合并为空格或制表符。默认情况下,他们认为制表位与大多数终端或浏览器一样相隔 8 列(但 stackexchange 则不同,令人烦恼的是,它们相隔 4 列),但请参阅-t更改此设置的选项。

答案2

如果一条线的级别 <= 下一个元素的级别,则该线是“原始成分”(primi)。这相当于:

如果前一行的级别 <= 当前级别(或者如果它是最后一行),则前一行是初始行

使用带有字段分隔符“\t”的 awk,级别为NF,成分为最后一个字段$NF

awk -F '\t' 'prevlev>=NF  {print primi}; 
                          {prevlev = NF; primi=$NF } 
             END          {print $NF}'

为了总结它们,你可以按照以下方式运行一些东西

... | sed 's/ /\t/' | datamash -g 2 -s sum 1

答案3

您需要使用lookbehind和lookahead。您还需要一起处理整个输入,而不是逐行处理。以下命令应该执行您想要的操作:

grep -Pzo '(?<=\n)(\s+)(\S[^\n]*)(?!\n\1\s)' input_file
  • -P启用 Perl 语法。

  • -z使用空终止符,而不是换行符。

  • -o仅输出匹配项。

  • (?<=\n)向后寻找换行符。它代替了^,它通常匹配每行的开头。对于后面的负向查看,请使用(?<!...).我忽略第一行,因为大概总会有更深的层次。如果不是这种情况,您可以在将输入发送到 之前在输入的开头添加一个新行grep。可能有更好的方法可以做到这一点,但这里有一个:

    ( echo ; cat input_file ) | grep ...
    
  • (\s+)捕获缩进级别。这在后面称为\1\s匹配空白。这样做的一个潜在问题是换行符可能被视为缩进的一部分。例如,双换行符通常用作段落分隔符。您可以替换\s为您希望用于缩进的特定空格,[\ \t]

  • (\S[^\n]*)捕获感兴趣的文本。 \S匹配非空白。[^\n]匹配任何非换行符。

  • (?!\n\1\s)负向前视以确保下一行的缩进不会比当前行更深。对于积极的展望,请使用(?=...).

相关内容