我正在尝试在服务器上执行ssh,然后执行grep以获取日志文件中不同错误的计数。一旦有了这些详细信息,就将它们记录到CSV文件中。但是,当我尝试运行grep命令时,出现错误。
#!/usr/bin/perl
my $addr = "user\@servername";
my $text = qq|Internal Server Error|;
my $remote_path = "/data/logs/error";
my $cmd = `ssh $remote_addr "grep -a $text $remote_path | awk -F " " '{print $4}' | sort -nr | uniq -c | sort -nr 2>/dev/null"`;
print $cmd;
但是当我运行脚本时
grep: Internal: No such file or directory
grep: Server: No such file or directory
grep: Error: No such file or directory
有没有建议我们如何在shell脚本中做到这一点?
答案 0 :(得分:5)
首先,为了避免引人入胜的噩梦和大量的shell注入机会,我建议使用String::ShellQuote之类的模块
然后,我看不到您需要所有这些外部工具,而如此长的管道既棘手又昂贵。它为Perl中确实完成的工作调用了许多程序,并且需要非常精确的语法。
除了ssh
以外,外部工具可能对这里有用的另一件事是在文件很大的情况下使用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;
一旦涉及到运行外部命令,就会有很多选择。首先考虑使用模块。它们都大大简化了工作,尤其是通过错误检查,并且通常更可靠,而有些还使更困难的工作更易于管理。
的示例use IPC::System::Simple qw(capturex);
my @lines = capturex('ssh', $remote_addr, $remote_cmd);
由于ssh
在运行时执行命令,因此不需要外壳(对于该部分),因此使用capturex
。有关更多选项以及如何检查错误,请参见文档。
Capture::Tiny,IPC::Run3,IPC::Run是其他一些选项,从简单到更强大。
有关所有这些的更多信息,请参见this post中的链接(并搜索更多信息)。
我看不到需要按原样运行该管道†,但是如果有一个管道(驻留在远程主机上?),则按上述方式形成命令,然后组装完整的管道< / p>
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
,即一个字符串。由于它通过外壳,它将被空间分解成单词,因此我们需要保护(引用/转义)那些空间。别忘了,我们还需要首先通过Perl的解析规则将其放入外壳。
一种方式
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
,因为它不必一次加载整个文件,而且不必因为速度而加载整个文件。但是,在此管道中,它是通过管道馈送数据并重复调用的,因此这些优点不适用。