Как я могу использовать скаляр в качестве ввода для open3 в Perl

У меня есть скаляр, который я хочу ввести в 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"

Это базовая версия 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 скалярная ссылка, она не будет работать. Во всяком случае, это длинная история.

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

Но это опасно. Вы рискуете в тупик. (В случае тупика, если ребенок пытается отправить большую сумму[1] на STDOUT или STDERR, когда вы пытаетесь отправить большую сумму[1] на его STDIN.) Чтобы избежать этой проблемы, вам потребуется 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.


  1. Я полагаю, что довольно маленький 4KiB - это "большой" объем в некоторых системах, хотя я, кажется, помню каналы, имеющие 128KiB на одной из моих машин Linux.
Другие вопросы по тегам