Как я могу использовать скаляр в качестве ввода для 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.
- Я полагаю, что довольно маленький 4KiB - это "большой" объем в некоторых системах, хотя я, кажется, помню каналы, имеющие 128KiB на одной из моих машин Linux.