使用Perl调用系统命令

4
在我们旧版本的代码中,我们通过Perl调用执行LDAP搜索,如下所示:
# Pass the base DN in via the ldapsearch-specific environment variable 
# (rather than as the "-b" paramater) to avoid problems of shell 
# interpretation of special characters in the DN.
$ENV{LDAP_BASEDN} = $ldn;

$lcmd = "ldapsearch -x -T -1 -h $gLdapServer" .
        <snip>
        " > $lworkfile 2>&1";
system($lcmd);

if (($? != 0) || (! -e "$lworkfile"))
{
  # Handle the error
}

上面的代码将导致成功的LDAP搜索,并且该搜索的输出将在文件$lworkfile中。
不幸的是,我们最近在这台服务器上重新配置了openldap,以便在/etc/openldap/ldap.conf和/etc/ldap.conf中指定了“BASE DC =”。这个变化似乎意味着ldapsearch忽略了LDAP_BASEDN环境变量,因此我的ldapsearch失败了。
我尝试了几种不同的修复方法,但目前还没有成功:
(1) 我尝试回到使用"-b"参数进行ldapsearch,但转义shell元字符。我开始编写转义代码:
my $ldn_escaped = $ldn;
$ldn_escaped =~ s/\/\\/g;
$ldn_escaped =~ s/`/\`/g;
$ldn_escaped =~ s/$/\$/g;
$ldn_escaped =~ s/"/\"/g;

由于我在Perl中没有正确转义那些正则表达式,所以出现了一些Perl错误(行号与带有反引号的正则表达式匹配)。

Backticks found where operator expected at /tmp/mycommand line 404, at end of line

同时我开始怀疑这种方法,并寻找更好的方法。

(2) 然后我看到了一些 Stackoverflow 的问题 (这里这里),他们提出了更好的解决方案。

以下是代码:

print("Processing...");

# Pass the arguments to ldapsearch by invoking open() with an array.
# This ensures the shell does NOT interpret shell metacharacters.
my(@cmd_args) = ("-x", "-T", "-1", "-h", "$gLdapPool",
                 "-b", "$ldn",
                 <snip>
                );

$lcmd = "ldapsearch";

open my $lldap_output, "-|", $lcmd, @cmd_args;

while (my $lline = <$lldap_output>)
{
  # I can parse the contents of my file fine
}

$lldap_output->close;

我使用方法(2)遇到的两个问题是:
a)使用带有参数数组的open或system无法让我将> $lworkfile 2>&1传递给命令,因此我无法停止ldapsearch输出被发送到屏幕上,这使得我的输出看起来很丑:
Processing...ldap_bind: Success (0)
        additional info: Success
b) 我无法确定如何选择传递给open的文件句柄的位置(即路径和文件名),也就是我不知道$lldap_output在哪里。我可以移动/重命名它,还是检查它以找出它在哪里(或者它实际上没有保存到磁盘)?基于问题(2)的问题,这使我想回到方法(1),但我不太确定如何操作。
5个回答

4

一种方法是使用IPC::Open3,使您的Perl代码能够处理外部程序的stdout和stderr流。


2

对于这个问题,我会使用IPC::Run3。这与open '-|'的方法很相似,但允许您同时重定向STDERR。

注意:$lldap_output是从ldapsearch读取的管道。没有文件在磁盘上创建。

如果您想要一个磁盘上的文件,可以像这样使用IPC::Run3:

use IPC::Run3;

my ($lcmd, @cmd_args) = ... # same as approach (2) above
my $lworkfile         = ... # same as approach (1) above

run3 [ $lcmd, @cmd_args ], undef, $lworkfile, $lworkfile;

这类似于方法(1),但使用-b替代$ENV{LDAP_BASEDN}

这个在Perl核心模块里吗?这个解决方案看起来不错,但是Perl报告“在@INC中找不到IPC/Run3.pm”。我猜我需要安装IPC-Run3-0.044模块,但我希望避免这样做,因为我必须对我们的服务器进行构建/安装更改。 - Dan J
不,IPC::Run3不是核心模块。 - cjm

1
感谢Greg Hewgill提供的答案。我在下面发布我的代码,以帮助其他想使用open3函数的人。
use File::Copy;
use IPC::Open3;

# Pass the arguments to ldapsearch by invoking open() with an array.
# This ensures the shell does NOT interpret shell metacharacters.
my(@cmd_args) = ("-x", "-T", "-1", "-h", "$gLdapPool",
                 "-b", "$ldn",
                 <snip>
                );
$lcmd = "ldapsearch";
my $lldap_output;

# First arg is undef as I don't need to pass any extra input to the 
# process after it starts running.
my $pid = open3(undef, $lldap_output, $lldap_output, $lcmd, @cmd_args);

# Wait for the process to complete and then inspect the return code.
waitpid($pid, 0);

my $ldap_retcode = $? >> 8;

if ($ldap_retcode != 0)
{
  # Handle error
}

# Copy the output to $lworkfile so I can refer to it later if needed       
copy($lldap_output, $lworkfile);

while (my $lline = <$lldap_output>)
{
  # I can parse the contents of my file fine
}

$lldap_output->close;

0

这里有一个巧妙的方法,可以使用普通的“open”命令从具有多个参数的外部程序中读取STDOUT和STDERR:

my @command_with_arguments = (YOUR_PROGRAM, ARG1, ARG2, ARG3);
foreach(@command_with_arguments){s/'/'"'"'/g;}
foreach(@command_with_arguments){s/(.+)/'$1'/;}
my $run_command = join (' ', @command_with_arguments) . " 2>&1 |";
open my $program_output, $run_command;

现在只需读取 $program_output 即可获取 STDOUT 和 STDERR。

0

请查看open的文档。您可以复制并重定向STDERR,运行命令,然后恢复STDERR。这比使用任何IPC ::(Open3、Run、Run3等)库更冗长,但如果您不能/不想安装额外的模块或不想使用IPC :: Open3,则可以在没有它们的情况下完成。


这很棒,但你真的成功做到了吗?当我运行以下命令时,命令无法重定向:open DUP, ">&STDERR"; open DUP, ">", "duperr.txt"; open COMMAND, "-|", $my_command; open STDERR, ">DUP_STDERR"; - schulwitz
@schulwitz:你正在恢复到错误的文件句柄,并且操作不正确,应该是 open STDERR, ">&DUP" - runrig
此外,您复制了stderr,但随后打开了副本以进行文件操作...您需要将STDERR打开到该文件。 - runrig

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接