[comp.lang.perl] pager pipes

tchrist@convex.COM (Tom Christiansen) (02/17/91)

I was trying to set up an automatic pager for STDOUT, and 
it doesn't seem to work.  If I choose a different handle,
it's fine.  Any clues?

    $pager = $ENV{'PAGER'} || 'more';
    open(STDOUT, "| $pager") || die "can't reopen STDOUT to $pager: $!";
    open(STDERR, ">&PIPE") || die "dup failed: $!";
    system 'ps t';
    system 'grep nada nonesuch';
    while (<>) { print; }
    close(STDOUT) || die "close failed: $!";

Notice that I never die of a bad dup even though there is no
PIPE handle.  If you %s/STDOUT/PIPE/g and add an appropriate
select, it all works fine.  This is under both 3.044 and 4.0beta.

--tom
--
Tom Christiansen		tchrist@convex.com	convex!tchrist
 "All things are possible, but not all expedient."  (in life, UNIX, and perl)

merlyn@iwarp.intel.com (Randal L. Schwartz) (02/17/91)

In article <1991Feb16.210445.7760@convex.com>, tchrist@convex (Tom Christiansen) writes:
| I was trying to set up an automatic pager for STDOUT, and 
| it doesn't seem to work.  If I choose a different handle,
| it's fine.  Any clues?
| 
|     $pager = $ENV{'PAGER'} || 'more';
|     open(STDOUT, "| $pager") || die "can't reopen STDOUT to $pager: $!";
|     open(STDERR, ">&PIPE") || die "dup failed: $!";
|     system 'ps t';
|     system 'grep nada nonesuch';
|     while (<>) { print; }
|     close(STDOUT) || die "close failed: $!";
| 
| Notice that I never die of a bad dup even though there is no
| PIPE handle.  If you %s/STDOUT/PIPE/g and add an appropriate
| select, it all works fine.  This is under both 3.044 and 4.0beta.

Arrrgh... Think about what $pager's stdout is at the time of the fork!

This works:

	open(TMP,"| $pager");  # $pager's stdout is Perl's stdout
	open(STDOUT, ">&TMP"); # Perl's stdout is now the pager's stdin
	close(TMP); # no need for this anymore

(The appropriate error checking is left as an exercise to the reader.)

print "Just another Perl hacker,"
-- 
/=Randal L. Schwartz, Stonehenge Consulting Services (503)777-0095 ==========\
| on contract to Intel's iWarp project, Beaverton, Oregon, USA, Sol III      |
| merlyn@iwarp.intel.com ...!any-MX-mailer-like-uunet!iwarp.intel.com!merlyn |
\=Cute Quote: "Intel: putting the 'backward' in 'backward compatible'..."====/

emv@ox.com (Ed Vielmetti) (02/18/91)

Not exactly a followup to the above, but here goes.

I have a pager set up for some particular output, like so
	open(PAGER,$pager) || warn "meaningful warning";
	print PAGER (join("\n",@result),\n);
	close(PAGER);

The result can be kind of long-winded, so I'd like to let people
interrupt out of the pager and still be returned gracefully to the
main loop of my script.  As it stands now, if $pager = "|cat" and you
control-C the output, you lose.

Is it a matter of eval'ing something, or some kind of signal handler
to set up, or something else I'm missing.  I wouldn't think that a ^C
sent to cat would leak back up to perl.

--Ed

tchrist@convex.COM (Tom Christiansen) (02/18/91)

From the keyboard of emv@ox.com (Ed Vielmetti):
:Not exactly a followup to the above, but here goes.
:
:I have a pager set up for some particular output, like so
:	open(PAGER,$pager) || warn "meaningful warning";
:	print PAGER (join("\n",@result),\n);
:	close(PAGER);
:
:The result can be kind of long-winded, so I'd like to let people
:interrupt out of the pager and still be returned gracefully to the
:main loop of my script.  As it stands now, if $pager = "|cat" and you
:control-C the output, you lose.
:
:Is it a matter of eval'ing something, or some kind of signal handler
:to set up, or something else I'm missing.  I wouldn't think that a ^C
:sent to cat would leak back up to perl.

Yes, you do indeed need to set up a signal handler.  You probably want one
for INT, maybe QUIT, and probably for PIPE as well.  The PIPE is in case
your $pager should bail out early or be absent.

Unlike most applications of kill(2), a ^C will cause the the signal to be
delivered to the entire process group, not just to the "active" process.
This means that both the pager and perl will see it.  I think that people
who expect otherwise have been lulled into this belief by the behavior of
system(3), which will ignore keyboard interrupts and quits.  

You don't normally need an eval for this kind of thing, nor would it
really help you without the signal handler.  However, one thing I have
done along those lines is something like this:

    sub PLUMBER { die "caught SIG$_[0] -- plumber bailing out"; }
    $SIG{'PIPE'} = $SIG{'INT'} = $SIG{'QUIT'} = 'PLUMBER';

    eval 'do something()';
    die $@ if $@ && $@ !~ /bailing out/;

This way I can in effect longjmp all the way back up to the call to
something(), without having to muck about with getting all the intervening
routines to return gracefully.  Used in this way, you can think of die as
raising an exception its caller can catch if they wish.

--tom
--
Tom Christiansen		tchrist@convex.com	convex!tchrist
 "All things are possible, but not all expedient."  (in life, UNIX, and perl)

lwall@jpl-devvax.JPL.NASA.GOV (Larry Wall) (02/19/91)

In article <1991Feb17.230545.16898@convex.com> tchrist@convex.COM (Tom Christiansen) writes:
: Unlike most applications of kill(2), a ^C will cause the the signal to be
: delivered to the entire process group, not just to the "active" process.
: This means that both the pager and perl will see it.  I think that people
: who expect otherwise have been lulled into this belief by the behavior of
: system(3), which will ignore keyboard interrupts and quits.  

Perl blocks SIGINT and SIGQUIT while waiting for pipes to shut down and
while executing the system operator, just like system(3).  It does not
block signals on an output pipe until you do the close, however.  If you
have less than 4k to page, you can just shove it down the pipe and close,
but much more than 4k (typically) and you'll block before getting to the
close.  So you have to ignore the signals yourself.  Saying

    local($SIG{'INT'}) = 'IGNORE';
    local($SIG{'QUIT'}) = 'IGNORE';

in the right spot should suffice.

Larry

lwall@jpl-devvax.jpl.nasa.gov (Larry Wall) (05/10/91)

In article <1991Feb16.210445.7760@convex.com> tchrist@convex.COM (Tom Christiansen) writes:
: I was trying to set up an automatic pager for STDOUT, and 
: it doesn't seem to work.  If I choose a different handle,
: it's fine.  Any clues?
: 
:     $pager = $ENV{'PAGER'} || 'more';
:     open(STDOUT, "| $pager") || die "can't reopen STDOUT to $pager: $!";
:     open(STDERR, ">&PIPE") || die "dup failed: $!";
:     system 'ps t';
:     system 'grep nada nonesuch';
:     while (<>) { print; }
:     close(STDOUT) || die "close failed: $!";
: 
: Notice that I never die of a bad dup even though there is no
: PIPE handle.  If you %s/STDOUT/PIPE/g and add an appropriate
: select, it all works fine.  This is under both 3.044 and 4.0beta.

The problem is that open(STDOUT, "| $pager") implicitly closes STDOUT
before calling mypopen() to run $pager, so the pager has no standard
output.  It's not exactly a bug, nor is it exactly a misfeature, but
it's enough of a violation of expectations that I think it's worth
special casing, along with the corresponding STDIN behavior.

Basically it comes down to this: you have to call pipe() before you
fork(), so the only two ways to make sure the pipe is attached to
the right fd are 1) close the fd first, or 2) close the fd later and
dup2 the pipe onto it.

A similar situation holds with

   open(STDERR, ">&PIPE") || die "dup failed: $!";

in which STDERR is closed before the dup is attempted, so the die dies
without a whimper.  I s'pose that needs some TLC too.

Larry