【问题标题】:Run Unix command from a perl Script从 perl 脚本运行 Unix 命令
【发布时间】:2018-11-05 20:47:48
【问题描述】:

我正在尝试在服务器上执行 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 脚本中做到这一点。

【问题讨论】:

  • 为什么不直接在 Perl 中完成大部分(可能全部)?
  • @Shawn 在本地 Perl 程序中唯一运行的是ssh 命令;其他一切都由远程主机上的 shell 执行。
  • 如果您不愿意在 perl 中安装安全所需的模块(通过使用 zdim's great answer),您是否会对不使用 perl 的基于 shell 的答案感兴趣?全部?

标签: shell perl


【解决方案1】:

首先,为了避免引用噩梦和 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::TinyIPC::Run3IPC::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 的评论,我无法弄清楚那个裸露的空间。

在这种情况下,管道的sortuniq 部分可以只输入为一个字符串,因为它只是程序名称和选项,但是一旦进行了更改或任何变量进入,这就变得很棘手.所以我使用shell_quote,以保持一致性并作为模板。


报告模块丢失且难以获取。然后转义需要转义的内容(直到您弄清楚如何获取模块)。在这种情况下,几乎没有什么需要修复的,但该位可以作为常见箍的示例,用于处理复杂的管道。

$text 的字符串需要达到grep 这样,一个字符串。由于它通过 shell,它会按空格将其分解为单词,因此我们需要保护(引用/转义)这些空格。不要忘记,我们还需要首先通过 Perl 的解析规则将其放入 shell。

一种方式

my $text_quoted = q(') . quotemeta($text) . q(');

quotemeta 还引用了其他各种内容。

我们还应该保护文件名模式,因为它可能依赖于 shell 元字符(如 *

my $remote_path_quoted = quotemeta $remote_path;

但同样,您必须检查这是否适合每种情况。

注意 &hairsp;如果在这些命令中插入了任何动态生成的变量(计算的、来自用户...),则需要对其进行验证,并小心地转义和引用

现在你的管道应该可以工作了(在我的模拟测试中)

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 不同?是的,您可以只使用内置函数,但这可能要困难得多。


&hairsp;一个很好的理由是当文件很大时使用系统sort,因为它不必一次加载整个文件,而且它的速度。但是,在此管道中,它通过管道提供数据并重复调用,因此这些优势并不适用。

【讨论】:

  • @Developer 已编辑并添加。让我知道是否需要更多/其他
  • 关于 awk 命令,shell_quote('awk', '-F', ' ', '{print $4}') 似乎是做正确的事。
  • 我没有安装您正在使用的这些模块,由于许可,我无法安装它们
  • @CharlesDuffy 是的,谢谢,已编辑。这是有道理的(事后)——-F 恰好需要一个空格,它确实会被转义。
  • @Developer 欢迎 :) 如果出现问题,请告诉我。
猜你喜欢
  • 2013-07-18
  • 2014-09-29
  • 2012-03-29
  • 2023-04-03
  • 2014-04-11
  • 1970-01-01
  • 2011-05-31
  • 2016-02-20
  • 1970-01-01
相关资源
最近更新 更多