EDIT: just to clarify, I want to know how one would implement the piping of content to an exec
'd process, putting aside the question of whether Perl offers better ways to achieve the same end-result that don't involve this technique.
It's easier to describe what I want to do with a toy zsh
script example:
#!/usr/bin/env zsh
# -----------------------------------------------------------------------------
# handle command-line arguments if any
# nb: ${(%):-%x} is zsh-speak for "yours truly"
# (( $# % 2 )) && (print -rl -- "$@"; [[ -t 0 ]] || cat) | exec ${(%):-%x}
(( $# > 0 )) && ([[ -t 0 ]] || cat; print -rl -- "$@") | exec ${(%):-%x}
# -----------------------------------------------------------------------------
# standard operation below this line
nl
The script above is pretty much a pass-through wrapper for the nl
("number lines") utility, except that, if command-line arguments are present, it will append them to to its stdin. For example:
$ seq 3 | /tmp/nlwrapper.sh
1 1
2 2
3 3
$ seq 3 | /tmp/nlwrapper.sh foo bar baz
1 1
2 2
3 3
4 foo
5 bar
6 baz
Note that
the command-line arguments could have just as easily been prepended to stdin (in fact, if one uncomments the script's 7th line, the resulting script will prepend or append the command-line arguments to stdin depending on whether their number is odd or even, respectively); I am interested in both functionalities.
the script consists of two entirely independent sections: a preamble that handles the command-line arguments (if any), and the body (in this toy example consisting of a single line) that takes care of the script's main functionality (numbering lines); this is an essential design feature.
To elaborate on these two points a bit further: the "body" section knows nothing about the business with the command-line arguments. The code that implements this handling of command-line arguments could be prepended pretty much "as-is" to any zsh
script that processes stdin. Moreover, changes to how the command-line arguments are handled (prepend vs append, etc) leave everything below the comment # standard operation below this line
untouched. The two sections are truly independent of each other.
What would be the equivalent of the preamble above in a Perl script?
I know that the Perl-equivalent of the script above would have the general form
#!/usr/bin/env perl
use strict;
use English;
# -----------------------------------------------------------------------------
# handle command-line arguments if any
if ( @ARGV > 0 ) {
# (mumble, mumble) ... -t STDIN ... exec $PROGRAM_NAME;
}
# -----------------------------------------------------------------------------
# standard operation below this line
printf "%6d\t$_", $., $_ while <>;
My problem is implementing this bit:
([[ -t 0 ]] || cat; print -rl -- "$@") |
I do know that
[[ -t 0 ]]
test in Perl is -t STDIN
;cat
part could be implemented with print while <>
; andprint -rl -- "$@"
bit could be implemented with CORE::say for @ARGV
What I don't know is how to put these elements together to get the desired functionality.
Might be easier just using cat
with some common shell features. (bash
used here.)
cat <( seq 3 ) <( printf 'foo\nbar\nbaz\n' ) | prog
Solution:
use POSIX qw( );
sub munge_stdin {
pipe(my $r, my $w) or die("pipe: $!");
$w->autoflush();
local $SIG{CHLD} = 'IGNORE';
defined( my $pid = fork() ) or die("fork: $!");
if (!$pid) {
eval {
close($r) or die("close pipe: $!");
while (<STDIN>) {
print($w $_) or die("print: $!");
}
for (@ARGV) {
print($w "$_\n") or die("print: $!");
}
POSIX::_exit(0);
};
warn($@);
POSIX::_exit(1);
}
close($w) or die("close pipe: $!");
open(STDIN, '<&', $r) or die("dup: $!");
@ARGV = ();
}
munge_stdin();
print while <>; # or: exec("cat") or die("exec: $!");
exec()
in the Perl script.