Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to pipe the content of a variable as STDIN in a qx{} statement in Perl?

Tags:

perl

I basically would like to do this:

$_ = "some content that need to be escaped &>|\"$\'`\s\\";
qx{echo $_ | foo}

There are two problems here. First the content of $_ needs to be escaped as it can contain binary data. Second, invoking echo might be slightly inefficient.

How can I simply pipe some content as STDIN to a command in Perl?

like image 622
nowox Avatar asked Dec 24 '22 00:12

nowox


2 Answers

The following assume @cmd contains the program and its arguments (if any).

my @cmd = ('foo');

If you want to capture the output, you can use any of the following:

use String::ShellQuote qw( shell_quote );
my $cmd1 = shell_quote('printf', '%s', $_);
my $cmd2 = shell_quote(@cmd);
my $output = qx{$cmd1 | $cmd2};

use IPC::Run3 qw( run3 );
run3(\@cmd, \$_, \my $output);

use IPC::Run qw( run );
run(\@cmd, \$_, \my $output);

If you don't want to capture the output, you can use any of the following:

use String::ShellQuote qw( shell_quote );
my $cmd1 = shell_quote('printf', '%s', $_);
my $cmd2 = shell_quote(@cmd);
system("$cmd1 | $cmd2");

system('/bin/sh', '-c', 'printf "%s" "$0" | "$@"', $_, @cmd);

use String::ShellQuote qw( shell_quote );
my $cmd = shell_quote(@cmd);
open(my $pipe, '|-', $cmd);
print($pipe $_);
close($pipe);

open(my $pipe, '|-', '/bin/sh', '-c', '"$@"', 'dummy', @cmd);
print($pipe $_);
close($pipe);

use IPC::Run3 qw( run3 );
run3(\@cmd, \$_);

use IPC::Run qw( run );
run(\@cmd, \$_);

If you don't want to capture the output, but you don't want to see it either, you can use any of the following:

use String::ShellQuote qw( shell_quote );
my $cmd1 = shell_quote('printf', '%s', $_);
my $cmd2 = shell_quote(@cmd);
system("$cmd1 | $cmd2 >/dev/null");

system('/bin/sh', '-c', 'printf "%s" "$0" | "$@" >/dev/null', $_, @cmd);

use String::ShellQuote qw( shell_quote );
my $cmd = shell_quote(@cmd);
open(my $pipe, '|-', "$cmd >/dev/null");
print($pipe $_);
close($pipe);

open(my $pipe, '|-', '/bin/sh', '-c', '"$@" >/dev/null', 'dummy', @cmd);
print($pipe $_);
close($pipe);

use IPC::Run3 qw( run3 );
run3(\@cmd, \$_, \undef);

use IPC::Run qw( run );
run(\@cmd, \$_, \undef);

Notes:

  • The solutions using printf will impose a limit on the size of the data to pass to the program's STDIN.

  • The solutions using printf are unable to pass a NUL to the program's STDIN.

  • The presented solutions that use IPC::Run3 and IPC::Run don't involve a shell. This avoids problems.

  • You should probably use system and capture from IPC::System::Simple instead of the builtin system and qx to get "free" error checking.

like image 118
ikegami Avatar answered Dec 27 '22 11:12

ikegami


This answer is a very naive approach. It's prone to deadlock. Don't use it!

ikegami explains in a comment below:

If the parent writes enough to the pipe attached to the child's STDIN, and if the child outputs enough to the pipe attached to its STDOUT before it reads from its STDIN, there will be a deadlock. (This can be as little as 4KB on some systems.) The solution involved using something like select, threads, etc. The better solution is to use a tool that has already solved the problem for you (IPC::Run3 or IPC::Run). IPC::Open2 and IPC::Open3 are too low-level to be useful in most circumstances

I'll leave the original answer, but encourage readers to pick the solution from one of the other answers instead.


You can use open2 from IPC::Open2 to read and write to the same process.

Now you don't need to care about escaping anything.

use IPC::Open2;
use FileHandle;

my $writer = FileHandle->new;
my $reader = FileHandle->new;

my $pid = open2( $reader, $writer, 'wc -c' );

# write to the pipe
print $writer 'some content that need to be escaped &>|\"$\'`\s\\';

# tell it you're done
$writer->close;

# read the out of the pipe
my $line = <$reader>;
print $line;

This will print 48.

Note that you can't use double quotes "" for the exact input you showed because the number of backslashes \ is wrong.

See perldoc open and perlipc for more information.

like image 21
simbabque Avatar answered Dec 27 '22 11:12

simbabque