我编制了游戏中所需的材料清单,从最上层到最原始的成分。然而,现在我正在寻找一种快速统计数字的方法。
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 metal
和21 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
与用于所有操作相比没有什么优势。
如果缩进可能混合有制表符和空格,您可能希望输入通过其中之一expand
或unexpand
首先将它们合并为空格或制表符。默认情况下,他们认为制表位与大多数终端或浏览器一样相隔 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)
负向前视以确保下一行的缩进不会比当前行更深。对于积极的展望,请使用(?=...)
.