ETOOBUSY 🚀 minimal blogging for the impatient
Rediscovering IPC::Exe
TL;DR
I recently rediscovered IPC::Exe.
In a previous post (IPC::Cmd considered harmful) I talked about my
hurdles with using the CORE module IPC::Cmd
and how I was surprised about
its behaviour. It even got some attention in Reddit (which,
incidentally, makes me wonder whether I should ditch the Disqus commenting
completely, given that nobody seems to use it).
In that post I also shared a few alternatives, like IPC::Run which I eventually selected for the problem I had at the time.
Then I recently had to do the Crypt::LE update and I re-discovered a host of small utilities (e.g. the program described in Send notifications through Mailgun with HTTP::Tiny), including one where I used IPC::Exe.
From the NAME section:
Execute processes or Perl subroutines & string them via IPC. Think shell pipes.
The interface is a bit complicated, but at the end of the day IPC::Run’s interface is not simple anyway.
Additionally, there’s a lot of attention regarding the redirection of output streams:
"2>#" or "2>null" silence stderr
">#" or "1>null" silence stdout
"2>&1" redirect stderr to stdout
"1>&2" or ">&2" redirect stdout to stderr
"2>&-" close stderr
"1><2" or "2><1" swap stdout and stderr
(+) shell-way works too:
\"3>&1", \"1>&2", \"2>&3", \"3>&-"
but little to no mention about standard input, which was my real interest at
the time (and, looking at the code, I solved in an overcomplicated way).
Spoiler alert, if you just want to close the first process’s standard input,
just pass '\</dev/null'
.
As an example, let’s re-create the program to get the expiration date of a TLS certificate, driving it all from Perl:
#!/usr/bin/env perl
use 5.024;
use warnings;
use experimental 'signatures';
no warnings 'experimental::signatures';
use IPC::Exe 'exe';
use Date::Parse 'str2time';
use POSIX 'strftime';
my $epoch = str2time(certificate_expiration_date(shift // 'example.com'));
say strftime('%Y-%m-%dT%H:%M:%SZ', gmtime($epoch));
sub certificate_expiration_date ($target) {
my ($domain, $port) = split m{:}, $target;
$target = $domain . ':' . ($port || 443);
my @parts = &{
exe qw< openssl s_client -connect >, $target, -servername => $domain,
\'</dev/null', \'2>/dev/null',
exe + {stdout => 1}, qw< openssl x509 -noout -enddate >,
};
$_ || die "pipe unsuccessful\n" for @parts;
defined(my $line = readline $parts[-1])
or die "cannot read from pipeline: $!\n";
return $line =~ s{.*=}{}rmxs;
}
The pipeline of two openssl
commands is implemented with two calls to
exe
; the first one contains two redirections:
- one for standard input, so that
s_client
will not wait for inputs; - one for standard error, to avoid printing too much stuff in output.
The expression &{ ... }
is put because the whole exe
chaining returns a
reference to a sub, which must be invoked to make the magic happen. We
might just as well do something like this:
my $sub = exe .... exe ... exe ...;
$sub->();
but maybe it’s clunkier. I don’t know.
So there you have it, another arrow that might come handy in some specific scenario!