2

我有一个标量,我想输入 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我有一个可以从中读取的参考资料中<$in>,但我需要的是我可以加上前缀的东西,'<&'以便 open3 将它用作STDIN它执行的命令。知道我如何将对句柄的引用转换为 open3 可以用于它的东西STDIN吗?

编辑

很明显,我的人为示例还不够……我不直接使用 {{DBI}} 的原因是这段代码实际上是我用于无足迹自动化的更大代码体的一部分。换句话说,我有一个由 30 多台服务器组成的环境,我的管理员没有安装任何特殊工具(只是 RHEL 5/6 中的标准工具)。这些服务器分为服务器集(db、app、web)、每个环境(local、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"

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

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

但是我遇到了需要修改 SQL 脚本的一些内容,所以我想把它吞进去,做一些工作,然后将它提供给$command_runneras 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是标量 ref,它就行不通了。不管怎样,这就是说来话长了。

4

1 回答 1

2

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);

但这很危险。你冒着陷入僵局的风险。(当您尝试将大量[1]发送到其 STDIN时,如果子尝试发送大量[1]到 STDOUT 或 STDERR,则会发生死锁。)为避免此问题,您需要一个循环。这很难。你不想使用这么低级别的东西。使用IPC::Run3IPC::Run代替它们为你做所有的脏活。selectopen3

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

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

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

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


  1. 我相信相当小的 4KiB 在某些系统上是一个“大”数量,尽管我似乎记得在我的一台 Linux 机器上具有 128KiB 的管道。
于 2015-10-19T03:26:18.213 回答