2

I have a scalar that I want to feed into open3 as the input. For example

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

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

However, the open3 is in a different module:

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

Then in another file:

package Example::Runner;

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

The problem is, in Example::Runner I have a reference I could read from <$in>, but what i need is something i can prefix with '<&' so that open3 will use it as the STDIN for the command it executes. Any idea how i convert a reference to a handle into something open3 can will use for its STDIN?

EDIT:

Its pretty clear that my contrived example is not enough... The reason I am not using {{DBI}} directly is that this code is actually part of a larger body of code that I use for footprintless automation. In other words, I have an environment of 30+ servers on which my admins have installed no special tools (just stuff that comes standard in RHEL 5/6). These servers are broken into sets of servers (db, app, web), for each environment (local, dev, qa, beta, prod), for each project (...). Anyway, one very common task is copying databases from one place to another. We accomplish that with a command akin to:

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"

This is the MOST basic version of cloning our production database back to a development environment (a more typical version includes a lot of switches on each command and a lot of piped commands in the middle). So, I wrote a library of abstractions around this (IPC::Open3::Callback::*). Along the way we ran into the need to perform some SQL commands that need to be run after the database is copied. So, we added the ability to run an arbitrary set of SQL scripts (based on the source and the destination of the clone operation). I could run them with a command like this:

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

But I have come across the need to munge some of the content of the SQL script so i wanted to slurp it in, do a little work on it, then provide it to the $command_runner as STDIN. That said, I attempted to deal with this using 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);
}

But if $in_handle is a scalar ref, it wont work. Anyway, that's the long story.

Lucas
  • 14,227
  • 9
  • 74
  • 124
  • I'm not sure I understand. Why can't you just give it a handle and then print to that handle? `my $pid = open3( \*SQL, $output, $output, "mysql -h 127.0.0.1" );if($pid){print SQL ();close(SQL);}` – hepcat72 Oct 08 '15 at 22:26
  • Or keep your original `SQL` and do `open (\*SQL2,$output,$output,...);print SQL2 ; close SQL2;` – mob Oct 08 '15 at 22:36
  • @hepcat72, @mob, I was trying to simplify the problem in this example, but the reason is it has to be there _before_ the command starts running or the `IO::Select` i am using to handle both OUT and ERR (and IN ish) gets wonky. (this is for `IO::Open3::Callback::safe_open_3`)... – Lucas Oct 08 '15 at 22:52
  • You can't do that with IPC::Open3. Use IPC::Run to have a way more flexible interface which also supports scalars and functions for input/output. – Steffen Ullrich Oct 09 '15 at 04:59
  • Maybe I don't understand exactly what you're trying to do, but why not use DBI? – ThisSuitIsBlackNot Oct 09 '15 at 14:51
  • So you're saying that unless the command has input upon run, things get wonky. I assume you believe that the command you're running detects no STDIN and messes things up before you supply it? How do you know that's what's happening? – hepcat72 Oct 09 '15 at 15:44
  • Assuming you're right and that the input needs to be there when open3 starts, you could use IO::Pipe::Producer to get a handle with waiting output for you to read in and supply that to open3... – hepcat72 Oct 09 '15 at 15:45
  • @ThisSuitIsBlackNot, its a contrived example. The only time i would use it for `mysql` command is when i have external scripts (provided by somebody else) that include more than simple sql statements... – Lucas Oct 10 '15 at 15:57
  • @hepcat72, its the `IO::Select` code [here](https://github.com/lucastheisen/ipc-open3-callback/blob/master/lib/IPC/Open3/Callback.pm#L165)... I never could reconcile how to handle both input and output. If I added the input handle to the select, then i had to figure out a way to both `can_read` and `can_write`... But the way it is now, if the IN buffer contains data, it is found before waiting on either OUT or ERR, but if it is not available before hand, it will wait on ERR or OUT for `select_timeout` before checking again... I'll take a look at IO::Pipe::Producer. – Lucas Oct 10 '15 at 16:07
  • @Lucas, I'd be interested to know how it goes. I wrote IO::Pipe::Producer. It wasn't intended for this purpose, but rather for piping subroutines together via stdout & stderr of forked processes in order to get output immediately from large data processing jobs. In other words, so you don't have to wait for a series of subroutines to complete before you see the first line of output. So it would be nice to see another use for it. – hepcat72 Oct 11 '15 at 17:16
  • So [@Lucas](http://stackoverflow.com/users/516433/lucas), did IO::Pipe::Producer work out? Did you confirm that the input needed to be ready on the handle before calling open3? – hepcat72 Oct 20 '15 at 15:24
  • Did IO::Pipe::Producer work for you @Lucas? – hepcat72 Nov 23 '15 at 15:42
  • @hepcat72, sorry, i have not had a chance to test this. work is extremely busy right now... – Lucas Nov 23 '15 at 15:43

1 Answers1

2

open \$var doesn't work because it doesn't create a system file handle from which the child can read.

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

First, you need a pipe.

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

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

Then, you'd print the data for mysql to read to TO_CHILD.

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

But that's dangerous. You are risking a deadlock. (A deadlock will occur if the child tries to send a large[1] amount to STDOUT or STDERR when you are trying to send a large[1] amount to its STDIN.) To avoid this problem, you'd need a select loop. This is very hard. You don't want to use something this low level. Use IPC::Run3 or IPC::Run instead of open3 as they do all the dirty work for you.

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

Better yet, avoid the needless shell:

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

But why are you using a client designed for human use as your interface? You probably should be using DBI.


  1. I believe that the rather small 4KiB is a "large" amount on some systems, though I seem to remember pipes having 128KiB on one of my Linux machines.
ikegami
  • 367,544
  • 15
  • 269
  • 518
  • I have looked into `IPC::Run` in the past, but its [lack of support for windows](http://stackoverflow.com/questions/16675950/perl-select-returning-undef-on-sysread-when-using-windows-ipcopen3-and-ios#comment24035835_16676271) held me back. As far as why not use `DBI`, I am trying to run an arbitrary SQL script. A simple enough script could be _slurped_ and split on `;` and looped through, but if there are function definitions or `delimiter` switching, or any number of other db specific operations, it would break... – Lucas Oct 19 '15 at 20:26
  • Quite the opposite, IPC::Run goes to extremes to support Windows. But without `fork` and without `select`, it has a lot of obstacles in its way. (`select` only works on sockets in Windows.) – ikegami Oct 19 '15 at 20:37