Home > database >  Piping stdout to stdin in perl between multiple functions (like cmd1 | cmd2 | cmd3 in shell)
Piping stdout to stdin in perl between multiple functions (like cmd1 | cmd2 | cmd3 in shell)

Time:03-18

In bash you can make functions and connect them with pipes:

myfunc1 | myfunc2 | myfunc3

Simple. Elegant. Readable.

Is there a similar solution in Perl?

So if I have the perl functions myfunc1()..myfunc3() which reads from STDIN and prints to STDOUT, I can connect them in a similar fashion?

CodePudding user response:

The Perl equivalent for subs is:

myfunc3(myfunc2(myfunc1()));

Instead of reading from a pipe and printing to a pipe, the functions read from the argument list, and return values.

Simple. Elegant. Readable.


Now, you insist on using STDIN and STDOUT. If you insist on doing something weird, it's to be expected there's no readily available solution. That's on you, not Perl.

To achieve what you describe, you can use this:

use strict;
use warnings;
use feature qw( say );

use Capture::Tiny qw( capture_stdout );

sub wrap {
   my $f = shift;

   return sub {
      my $in   = shift;
      my @args = @_;

      local *STDIN;
      if ( defined($in) ) {
         open *STDIN, '<', \$in or die $!;
      }

      return capture_stdout { $f->( @args ) };
   };
}

sub myfunc1 { say for "abc", "def", "ghi"; }
sub myfunc2 { while (<STDIN>) { chomp; print "<$_>\n"; } }
sub myfunc3 { while (<STDIN>) { chomp; print "[$_]\n"; } }

*_myfunc1 = wrap(\&myfunc1);
*_myfunc2 = wrap(\&myfunc2);
*_myfunc3 = wrap(\&myfunc3);

print _myfunc3(_myfunc2(_myfunc1()));

Output:

[<abc>]
[<def>]
[<ghi>]

CodePudding user response:

Have myfunc1, myfunc2, and myfunc3 accept input as arguments to the subroutine and return the output as the subs' return value. That is, change

sub myfunc1 {
    while (<STDIN>) {
        do something
        print STDOUT $_;
    }
}

to something like

sub myfunc1 {
    return map {
        do something;
        $_
    } @_;
}

Then you can chain your functions like:

print STDOUT myfunc3(myfunc2(myfunc1(<STDIN>)));
        

CodePudding user response:

The IPC::Run package will do this for you.

#! /usr/bin/perl

use IPC::Run qw(run);

my @cat = qw(cat);
my @wc = qw(wc -l);

run @cat, '|', \@wc;

CodePudding user response:

I am unaware of a module that does this, but this function may work:

sub func1 {
    # Run some UNIX filter, that reads STDIN and prints to STDOUT
    while(<STDIN>) {
        print "foo $_";
    }
}

sub func2 {
    # Run some UNIX filter, that reads STDIN and prints to STDOUT
    while (<STDIN>) {
        print "bar $_";
    }
}

sub func3 {
    # Run some UNIX filter, that reads STDIN and prints to STDOUT
    exec q{perl -pe 's/o/e/g; s/a/i/g;'};
}

sub func4 {
    # Run some UNIX filter, that reads STDIN and prints to STDOUT
    exec q{perl -pe 's/^/A/g; s/$/B/g;'};
}

# This is the function to do the work
sub pipefunc {
    my $func = pop;

    my $pid = open(my $kid_to_read, "-|");
    defined($pid) || die "can't fork: $!";
    if ($pid) {
        open STDIN, "<&", $kid_to_read or die;
        &$func();
    } else { # child
        close $kid_to_read;
        if($_[1]) {
            # More than one function remaining: Recurse
            pipefunc(@_);
        } else {
            # Only one function remaining: Run it
            $func = pop;
            &$func();
        }
        exit 0;
    }
}

# This is how you use it.
# func4 | func2 | func3 | func2 | func4
pipefunc(*func4,*func2,*func1,*func3,*func2,*func4);

Try:

seq 10 | perl pipefunc.pl

Just like a pipe in shell this will work fine on input that is larger than the virtual memory on the system. Just like a pipe in shell it will run a process for each function.

Using exec it is easy to mix perl functions with shell commands, so you can almost do: perlfunc | shell command | perlfunc | perlfunc | shell command.

  • Related