首先,为了避免引用噩梦和 shell 注入的许多机会,我建议使用一个模块,例如 String::ShellQuote
然后,我认为您不需要所有这些外部工具,而如此长的管道既棘手又昂贵。它调用了许多程序,用于在 Perl 中做得很好的工作,并且需要非常精确的语法。
除了ssh-ing,外部工具在这里可能有用的另一件事是使用grep 提取感兴趣的行,以防文件很大(否则您可以将其读入标量) .
use warnings;
use strict;
use feature 'say';
use List::Util qw(uniq); # in List::MoreUtils prior to module v1.45
use String::ShellQuote qw(shell_quote);
my $remote_addr = ...
my $remote_path = ...
my $text = 'Internal Server Error';
my $remote_cmd = shell_quote('grep', '-a', $text, $remote_path);
my $cmd = shell_quote('ssh', $remote_addr, $remote_cmd);
my @lines = qx($cmd);
chomp @lines;
# Process @lines as needed, perhaps
my @result = sort { $b <=> $a } uniq map { (split)[3] } @lines;
say for @result;
一旦涉及到运行外部命令,就有很多选择。首先考虑使用模块。它们都大大简化了事情,特别是错误检查,并且通常更可靠,而有些也使更难的工作更易于管理。
IPC::System::Simple 的示例
use IPC::System::Simple qw(capturex);
my @lines = capturex('ssh', $remote_addr, $remote_cmd);
由于ssh 在运行时执行命令,它不需要外壳(对于该部分),因此使用capturex。有关更多选项以及如何检查错误,请参阅文档。
其他一些选项,从简单到更强大,是Capture::Tiny、IPC::Run3、IPC::Run。
有关所有这些的更多信息,请参阅this post 中的链接(并搜索更多)。
我看不出有必要按原样运行该管道†,但如果有一个(留在远程主机上?)然后按上述方式形成命令,然后组装完整的管道
my $cgrep = shell_quote('grep', '-a', $text, $remote_path);
my $cawk = shell_quote('awk', '-F', ' ', '{print $4}');
my $csort = shell_quote('sort', '-nr');
my $cuniq = shell_quote('uniq', '-c');
my $remote_cmd = "$cgrep | $cawk | $csort | $cuniq | $csort 2>/dev/null";
请注意,不应引用所需的 shell 功能(| 和重定向)。
awk 部分中的空间可能看起来很尴尬,但由于它被逃脱了,它最终适合-F。对我来说,这是在 shell 管道中运行外部程序的另一个问题。感谢Charles Duffy 的评论,我无法弄清楚那个裸露的空间。
在这种情况下,管道的sort 和uniq 部分可以只输入为一个字符串,因为它只是程序名称和选项,但是一旦进行了更改或任何变量进入,这就变得很棘手.所以我使用shell_quote,以保持一致性并作为模板。
报告模块丢失且难以获取。然后转义需要转义的内容(直到您弄清楚如何获取模块)。在这种情况下,几乎没有什么需要修复的,但该位可以作为常见箍的示例,用于处理复杂的管道。
$text 的字符串需要达到grep 这样,一个字符串。由于它通过 shell,它会按空格将其分解为单词,因此我们需要保护(引用/转义)这些空格。不要忘记,我们还需要首先通过 Perl 的解析规则将其放入 shell。
一种方式
my $text_quoted = q(') . quotemeta($text) . q(');
quotemeta 还引用了其他各种内容。
我们还应该保护文件名模式,因为它可能依赖于 shell 元字符(如 *)
my $remote_path_quoted = quotemeta $remote_path;
但同样,您必须检查这是否适合每种情况。
注意  如果在这些命令中插入了任何动态生成的变量(计算的、来自用户...),则需要对其进行验证,并小心地转义和引用。
现在你的管道应该可以工作了(在我的模拟测试中)
my $cmd = "ssh $remote_host grep -a $text_quoted $remote_path_quoted"
. q( | awk F ' ' '{print $4}' | sort -nr | uniq | sort -nr 2>/dev/null);
这可以在它们自己的变量等中分解成合理的组件,但我真的不推荐这种手动修补的解决方案。
我建议只使用第一部分(ssh + grep)并在 Perl 中完成其余部分,就像答案的主要部分一样。然后安装这些模块并切换到它们。
没有(许多)库,任何主要的计算工具都无法工作,并且每个生产安装都包含许多“附加”内容。随着对更多库的需求出现,它们被安装。为什么它必须与 Perl 不同?是的,您可以只使用内置函数,但这可能要困难得多。
†  一个很好的理由是当文件很大时使用系统sort,因为它不必一次加载整个文件,而且它的速度。但是,在此管道中,它通过管道提供数据并重复调用,因此这些优势并不适用。