Skip to content

Instantly share code, notes, and snippets.

@webstrand
Created November 7, 2019 17:57
Show Gist options
  • Save webstrand/eb088654a2a3f2025515e44b9f3dfe49 to your computer and use it in GitHub Desktop.
Save webstrand/eb088654a2a3f2025515e44b9f3dfe49 to your computer and use it in GitHub Desktop.
A `cat`-like program which exits when STDOUT is closed. Useful for piping files followed by stdin: `sipe some_file /dev/stdin | your-program`
#!/usr/bin/perl
use strict;
use warnings;
use v5.26;
use Errno qw(EAGAIN EWOULDBLOCK EINTR);
use Fcntl qw(O_WRONLY);
STDOUT->autoflush(1);
# This is a tool like cat, except that it watches for STDOUT close and exits
# immediately.
if(do { no warnings; defined sysread(STDOUT, my $tmp, 0) }) {
die "STDOUT is readable, please use cat";
}
my $skipped_file;
OUTER: for(@ARGV) {
my $FH;
unless(open($FH, "<", $_)) {
warn "$_: $!";
$skipped_file = 1;
next;
}
my $rin = '';
vec($rin, fileno($FH), 1) = 1;
vec($rin, fileno(STDOUT), 1) = 1;
for(;;) {
my $rout = '';
unless(select($rout=$rin, undef, undef, undef)) {
redo if not $! || $! == EINTR;
die "select: $!";
}
# STDOUT becomes readable when all readers have closed the pipe.
last OUTER if(vec($rout, fileno(STDOUT), 1));
# $FH becomes readable when there's data to be read.
my $buffer;
if(vec($rout, fileno($FH), 1)) {
unless(sysread($FH, $buffer, 8192)) {
last unless $!;
redo if $! == EAGAIN || $! == EWOULDBLOCK || $! == EINTR;
die "read: $!";
}
}
print STDOUT $buffer or die "write: $!";
}
}
exit($skipped_file ? 1 : 0);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment