Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

running "less" from perl pipeline

I am trying to set up arbitrary pipelines from Perl, in much the same way as a shell might.

This has the desired effect, it's like "echo foo | sed s/oo/ar/":

#!/usr/bin/perl
use strict;

use IO::Handle;

$| = 1;

my ($first_prog, $second_prog) = ([qw(echo foo)], [qw(sed s/oo/ar/)]);
#$second_prog = ["less"];

pipe(my $second_prog_input, my $first_prog_output)   
    or die "problem setting up pipe: $!\n";

if (fork) {
    STDOUT->fdopen(fileno($first_prog_output), 'w') or die;
    exec(@$first_prog) or die;
}
else {
    STDIN->fdopen(fileno($second_prog_input), 'r') or die;
    exec(@$second_prog) or
      die "couldn't exec: $!: command was @$first_prog\n";
}

However, when I make the second argument "less", my terminal flashes, and I don't see the output in the pager. Other than the brief flash, there is no sign of less running at all.

Now something I don't get at all is that the following does behave like "echo foo | less":

pipe(my $first_prog_output, STDIN) or die "problem setting up pipe: $!\n";

my ($first_prog, $second_prog) = ([qw(echo foo)], ["less"]);

open($first_prog_output, "-|", @$first_prog) or
  die "$!: command was @$first_prog\n";

exec(@$second_prog) or
  die "couldn't exec: $!: command was @$first_prog\n";

But I don't understand at all what that call to pipe() is doing. The first argument is supposed to be a "reader", and the second one a "writer". How is STDIN a "writer"?

I'm very confused by all this, and think there may be something fundamental about the underlying Unix API that I'm missing or I've forgotten.

like image 674
projix Avatar asked Jul 24 '12 23:07

projix


1 Answers

It's an interesting timing issue:

  • Shell forks, creates a process group, sets the terminal foreground group to it, and execs perl.
  • perl forks and execs less.
  • perl execs echo.
  • echo exits, shell believes job is done and sets itself as the terminal foreground process group once again.
  • less tries to acquire terminal. It is not in the terminal foreground process group. less fails.

The simplest solution: change fork to !fork (or equivalently, swap your if and else blocks). That way, the final stage of the pipeline defines the lifetime of the job. (If you watch carefully, the shell forks and execs processes in this same order when it runs a pipeline, too.)

like image 183
ephemient Avatar answered Oct 02 '22 18:10

ephemient