如何在 perl 中使用标量作为 open3 的输入

Posted

技术标签:

【中文标题】如何在 perl 中使用标量作为 open3 的输入【英文标题】:How can I use a scalar as input to open3 in perl 【发布时间】:2015-10-08 22:07:16 【问题描述】:

我有一个标量,我想输入 open3 作为输入。例如

my $sql = "select * from table;";
open( SQL, "<", \$sql );

my ($output);
open3( '<&SQL', $output, $output, "mysql -h 127.0.0.1" );

但是,open3 位于不同的模块中:

package main;

use Example::Runner;

my $sql = "select * from table;";
open( my $in_handle, "<", \$sql );

my ($out_handle);
Example::Runner::run( $in_handle, $out_handle, $out_handle
    'mysql -h 127.0.0.1' );

然后在另一个文件中:

package Example::Runner;

sub run 
    my ($in, $out, $err, @command) = @_;
    open3( ?, $out, $err, "mysql -h 127.0.0.1" );

问题是,在Example::Runner 中,我有一个可以从&lt;$in&gt; 读取的参考,但我需要的是我可以在'&lt;&amp;' 前加上前缀,以便open3 将其用作命令的STDIN它执行。知道如何将句柄的引用转换为 open3 可以用于其STDIN 的东西吗?

编辑

很明显,我的人为示例还不够……我不直接使用 DBI 的原因是这段代码实际上是我用于 footprintless 自动化。换句话说,我有一个由 30 多台服务器组成的环境,我的管理员没有安装任何特殊工具(只是 RHEL 5/6 中的标准工具)。这些服务器分为服务器集(db、app、web),用于每个环境(本地、dev、qa、beta、prod),用于每个项目(...)。无论如何,一项非常常见的任务是将数据库从一个地方复制到另一个地方。我们使用类似于以下的命令来完成此操作:

use IPC::Open3::Callback::CommandRunner;
use IPC::Open3::Callback::Command qw(command pipe_command);

my $source_config = hostname => 'proj1-prod-db', sudo_username => 'db';
my $dest_config = hostname => 'proj1-prod-db', sudo_username => 'db';
my $command_runner = IPC::Open3::Callback::CommandRunner->new();
$command_runner->run_or_die( pipe_command(
    command( "mysqldump dbname", $source_config ),
    command( "mysql dbname", $dest_config ) ) );
# runs: ssh proj1-prod-db "sudo -u db mysqldump dbname" | ssh proj1-dev-db "sudo -u db mysql dbname"

这是MOST 将我们的生产数据库克隆回开发环境的基本版本(更典型的版本包括每个命令上的许多开关和中间的许多管道命令)。所以,我围绕这个(IPC::Open3::Callback::*)编写了一个抽象库。在此过程中,我们遇到了需要执行一些在复制数据库后需要运行的 SQL 命令。因此,我们添加了运行任意 SQL 脚本集的功能(基于克隆操作的源和目标)。我可以用这样的命令运行它们:

$command_runner->run_or_die( pipe_command(
    "cat $post_restore",
    command( "mysql dbname", $dest_config ) ) );

但是我遇到了一些 SQL 脚本内容的需求,所以我想把它吞进去,做一些工作,然后提供给$command_runner作为STDIN。也就是说,我尝试使用fileno 来处理这个问题:

sub safe_open3_with 
    my ($in_handle, $out_handle, $err_handle, @command) = @_;

    my @args = (
        $in_handle ? '<&' . fileno( $in_handle ) : undef,
        $out_handle ? '>&' . fileno( $out_handle ) : undef,
        $err_handle ? '>&' . fileno( $err_handle ) : undef,
        @command
    );
    return ( $^O =~ /MSWin32/ ) ? _win_open3(@args) : _nix_open3(@args);

但是如果$in_handle 是一个标量引用,它就不起作用。总之,说来话长。

【问题讨论】:

我不确定我是否理解。为什么你不能给它一个句柄然后打印到那个句柄? my $pid = open3( \*SQL, $output, $output, "mysql -h 127.0.0.1" );if($pid)print SQL (&lt;WHATEVERYOUREADIN&gt;);close(SQL); 或保留您原来的SQL 并执行open (\*SQL2,$output,$output,...);print SQL2 &lt;SQL&gt;; close SQL2; @hepcat72,@mob,我试图简化这个例子中的问题,但原因是它必须在 在命令开始运行之前或@987654340 @我用来处理 OUT 和 ERR(以及 IN ish)变得很不稳定。 (这是给IO::Open3::Callback::safe_open_3)... 使用 IPC::Open3 无法做到这一点。使用 IPC::Run 获得更灵活的接口,它还支持输入/输出的标量和函数。 也许我不明白你到底想做什么,但为什么不使用 DBI? 【参考方案1】:

open \$var 不起作用,因为它没有创建孩子可以从中读取的系统文件句柄。

$ perl -E'open(my $fh, "<", \"abc") or die $!; say fileno($fh);'
-1

首先,你需要一个管道。

pipe(local *CHILD_STDIN, local *TO_CHILD)
   or die("Can't create pipe: $!\n");

my $pid = open3($cmd, '<&CHILD_STDIN', local *FROM_CHILD, undef);

然后,您将打印mysql 的数据以读取到TO_CHILD

print(TO_CHILD do  local $/; <$in> );
close(TO_CHILD);

但这很危险。你冒着陷入僵局的风险。 (如果孩子在您尝试向其 STDIN 发送大量[1] 数量时尝试向 STDOUT 或 STDERR 发送大量[1] 则会发生死锁.) 为了避免这个问题,你需要一个select 循环。这很难。你不想使用这么低级别的东西。使用IPC::Run3 或IPC::Run 而不是open3,因为它们会为您完成所有肮脏的工作。

use IPC::Run3 qw( run3 );
run3($shell_cmd, \$sql, \my $out, \my $err);

更好的是,避免不必要的外壳:

run3([ $prog, @args ], \$sql, \my $out, \my $err);

但您为什么要使用专为人类使用而设计的客户端作为您的界面?您可能应该使用DBI。


    我相信相当小的 4KiB 在某些系统上是一个“大”量,尽管我似乎记得在我的一台 Linux 机器上具有 128KiB 的管道。

【讨论】:

我过去曾研究过IPC::Run,但它的lack of support for windows 阻止了我。至于为什么不使用DBI,我正在尝试运行任意SQL 脚本。一个足够简单的脚本可以 slurped 并在; 上拆分并循环通过,但如果有函数定义或delimiter 切换,或任何数量的其他数据库特定操作,它会中断.. . 恰恰相反,IPC::Run 极端支持Windows。但是没有fork 和没有select,它的道路上有很多障碍。 (select 仅适用于 Windows 中的套接字。)

以上是关于如何在 perl 中使用标量作为 open3 的输入的主要内容,如果未能解决你的问题,请参考以下文章

我们在 C 或 C++ 中是不是有类似于 IPC::Open3 的 perl

perl IPC:Open3 最小通过 perlcritic?

Perl:open3 但对于 n 个文件描述符

Perl 行为差异关闭由 open() 产生的子进程与 IPC::Open3

如何检查 Perl 标量变量是不是已初始化?

Perl引用介绍