您可以通过使用同时进行计数和比较的命令来避免中间步骤:
find . -type f -exec perl -nle 'END { print $ARGV if $h{"{"} != $h{"}"} } $h{$_}++ for /([}{])/g' {}\;
这会为每个文件调用一次 Perl 程序,Perl 程序计算每种类型的花括号的数量,如果计数不匹配,则打印文件名。
你必须小心这个/([}{]])/
部分,如果你说它find
会认为它需要做替换。{}
/([{}]])/
警告:如果您尝试针对源代码运行此代码,则会出现误报和误报。考虑以下情况:
平衡,但在字符串中卷曲:
if ($s eq '{') {
print "I saw a {\n"
}
不平衡,但在字符串中卷曲:
while (1) {
print "}";
您可以使用B::Deparse扩展 Perl 命令:
perl -MO=Deparse -nle 'END { print $ARGV if $h{"{"} != $h{"}"} } $h{$_}++ for /([}{])/g'
结果是:
BEGIN { $/ = "\n"; $\ = "\n"; }
LINE: while (defined($_ = <ARGV>)) {
chomp $_;
sub END {
print $ARGV if $h{'{'} != $h{'}'};
}
;
++$h{$_} foreach (/([}{])/g);
}
我们现在可以查看程序的每一部分:
BEGIN { $/ = "\n"; $\ = "\n"; }
这是由-l
选项引起的。它将输入和输出记录分隔符设置为“\n”。这意味着读入的任何内容都将被分解为基于“\n”的记录,并且任何打印语句都将附加“\n”。
LINE: while (defined($_ = <ARGV>)) {
}
这是由-n
选项创建的。它遍历通过命令行传入的每个文件(如果没有传递文件,则为 STDIN)读取这些文件的每一行。这也恰好设置$ARGV
为 . 读取的最后一个文件<ARGV>
。
chomp $_;
$/
这会从刚刚读取的行中删除变量中的所有内容 ( $_
),它在这里没有任何用处。这是由-l
选项引起的。
sub END {
print $ARGV if $h{'{'} != $h{'}'};
}
这是一个 END 块,这段代码将在程序结束时运行。如果存储在与键相关联的值相等,它会打印$ARGV
(最后读取的文件的名称,见上文) 。%h
'{'
'}'
++$h{$_} foreach (/([}{])/g);
这需要进一步分解:
/
( #begin capture
[}{] #match any of the '}' or '{' characters
) #end capture
/gx
是一个正则表达式,它返回正在匹配的字符串中的“{”和“}”字符列表。由于没有指定字符串,因此$_
将匹配变量(保存从文件中最后读取的行,见上文)。该列表被输入到foreach
语句中,然后为列表中的每个项目(因此名称)运行它前面的语句。它还将$_
(如您所见$_
是 Perl 中的一个流行变量)设置为列表中的项目。
++h{$_}
此行将 $h 中关联的值$_
(将是 '{' 或 '}',见上文)加一。