【问题标题】:How to pipe the content of a variable as STDIN in a qx{} statement in Perl?如何在 Perl 的 qx{} 语句中将变量的内容作为 STDIN 管道传输?
【发布时间】:2016-10-21 09:25:50
【问题描述】:

我基本上想这样做:

$_ = "some content that need to be escaped &>|\"$\'`\s\\";
qx{echo $_ | foo}

这里有两个问题。首先$_ 的内容需要转义,因为它可以包含二进制数据。其次,调用echo 可能效率稍低。

如何将一些内容作为 STDIN 简单地通过管道传输到 Perl 中的命令?

【问题讨论】:

  • 你想读取foo的输出吗?
  • 是的,把它放到$_
  • 所以你想用foo来改变$_的内容。这是单线的吗?
  • 嗯,是的,它看起来像一个单行,但我想在 Perl 脚本中做。在对内容进行其他操作之前,我正在通过一些过滤器处理一些文件。
  • 在单行中,我可能会调用 perl 两次。像$ echo "foobar" | perl -pe 's/f/ff/' | wc -c | perl -pe '$_ *= 2' 这样的东西,所以你不必关心所有的转义。

标签: perl


【解决方案1】:

以下假设 @cmd 包含程序及其参数(如果有)。

my @cmd = ('foo');

如果要捕获输出,可以使用以下任意一种:

use String::ShellQuote qw( shell_quote );
my $cmd1 = shell_quote('printf', '%s', $_);
my $cmd2 = shell_quote(@cmd);
my $output = qx{$cmd1 | $cmd2};

use IPC::Run3 qw( run3 );
run3(\@cmd, \$_, \my $output);

use IPC::Run qw( run );
run(\@cmd, \$_, \my $output);

如果您不想捕获输出,可以使用以下任何一种:

use String::ShellQuote qw( shell_quote );
my $cmd1 = shell_quote('printf', '%s', $_);
my $cmd2 = shell_quote(@cmd);
system("$cmd1 | $cmd2");

system('/bin/sh', '-c', 'printf "%s" "$0" | "$@"', $_, @cmd);

use String::ShellQuote qw( shell_quote );
my $cmd = shell_quote(@cmd);
open(my $pipe, '|-', $cmd);
print($pipe $_);
close($pipe);

open(my $pipe, '|-', '/bin/sh', '-c', '"$@"', 'dummy', @cmd);
print($pipe $_);
close($pipe);

use IPC::Run3 qw( run3 );
run3(\@cmd, \$_);

use IPC::Run qw( run );
run(\@cmd, \$_);

如果您不想捕获输出,但也不想看到它,您可以使用以下任何一种:

use String::ShellQuote qw( shell_quote );
my $cmd1 = shell_quote('printf', '%s', $_);
my $cmd2 = shell_quote(@cmd);
system("$cmd1 | $cmd2 >/dev/null");

system('/bin/sh', '-c', 'printf "%s" "$0" | "$@" >/dev/null', $_, @cmd);

use String::ShellQuote qw( shell_quote );
my $cmd = shell_quote(@cmd);
open(my $pipe, '|-', "$cmd >/dev/null");
print($pipe $_);
close($pipe);

open(my $pipe, '|-', '/bin/sh', '-c', '"$@" >/dev/null', 'dummy', @cmd);
print($pipe $_);
close($pipe);

use IPC::Run3 qw( run3 );
run3(\@cmd, \$_, \undef);

use IPC::Run qw( run );
run(\@cmd, \$_, \undef);

注意事项:

  • 使用printf 的解决方案将对传递给程序的STDIN 的数据大小施加限制。

  • 使用printf 的解决方案无法将 NUL 传递给程序的 STDIN。

  • 使用 IPC::Run3 和 IPC::Run 的解决方案不涉及 shell。这样可以避免问题。

  • 您可能应该使用来自 IPC::System::Simple 的 systemcapture 而不是内置的 systemqx 来获得“免费”错误检查。

    李>

【讨论】:

  • 我喜欢IPC::Run 解决方案。看起来很简单!第一个解决方案是使用printf而不是echo,使用printf而不是echo有什么好处?
  • @Håkon Hægland,很难将任意值正确传递给echo,因为它们可能被解释为选项。此外,echo 的某些版本识别 \ 转义,需要进一步依赖于系统的转义。
【解决方案2】:

这个答案是一个非常幼稚的方法。它很容易陷入僵局。 不要使用它!

ikegami explains in a comment 下方:

如果父级向连接到子级 STDIN 的管道写入足够多的内容,并且如果子级在从其 STDIN 读取之前向连接到其 STDOUT 的管道输出足够多的内容,则会出现死锁。 (在某些系统上可能只有 4KB。)解决方案涉及使用选择、线程等。更好的解决方案是使用已经为您解决问题的工具(IPC::Run3 或 IPC::跑)。 IPC::Open2 和 IPC::Open3 级别太低,在大多数情况下都无法使用

我将保留原始答案,但鼓励读者从其他答案之一中选择解决方案。


您可以使用IPC::Open2 中的open2 来读取和写入同一个进程。

现在您无需担心逃避任何事情。

use IPC::Open2;
use FileHandle;

my $writer = FileHandle->new;
my $reader = FileHandle->new;

my $pid = open2( $reader, $writer, 'wc -c' );

# write to the pipe
print $writer 'some content that need to be escaped &>|\"$\'`\s\\';

# tell it you're done
$writer->close;

# read the out of the pipe
my $line = <$reader>;
print $line;

这将打印48

请注意,您不能将双引号 "" 用于您显示的确切输入,因为反斜杠 \ 的数量是错误的。

请参阅perldoc openperlipc 了解更多信息。

【讨论】:

  • 很好,但我更喜欢只使用核心模块。目前我使用File::Temp,但我不喜欢它
  • @nowox 根据我的corelist,IPC::Open2 和 FileHandle 都首次使用 perl 5 发布。我相信这意味着从 Perl 5 的初始版本开始。它没有比这更多的核心模块。 :)
  • 顺便说一下,File::Temp 是 5.6.1 自带的。
  • 如果父级向连接到子级 STDIN 的管道写入足够多的内容,并且如果子级在从其 STDIN 读取之前向连接到其 STDOUT 的管道输出足够多的内容,则会出现死锁。 (在某些系统上可能只有 4KB。)解决方案涉及使用select、线程等。更好的解决方案是使用已经为您解决问题的工具(IPC::Run3 或 IPC ::跑)。 IPC::Open2 和 IPC::Open3 级别太低,在大多数情况下都无法使用。
  • 如果您不想捕获输出,使用open2('&gt;&amp;STDOUT', $writer, ...) 可以避免该问题。但是你可以简单地使用open(my $writer, '|-', ...)
【解决方案3】:

我喜欢@simbabque 提供的解决方案,因为它避免了调用Shell。无论如何,为了比较,通过使用 Bash Here string,可以使用 Bash(但避免使用 echo)获得更短的解决方案:

$_ = q{some content that need to be escaped &>|\"$\'`\s\\};
$_ =~ s/'/'"'"'/g; # Bash needs single quotes to be escaped
system 'bash', '-c', "foo <<< '$_'";

而且,如果您需要捕获命令的输出:

use Capture::Tiny 'capture_stdout';
my $res = capture_stdout { system 'bash', '-c', "foo <<< '$_'" };

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2013-02-26
    • 2016-03-29
    • 1970-01-01
    • 1970-01-01
    • 2019-04-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多