在多个函数之间将 stdout 管道传输到 perl 中的 stdin(例如 shell 中的 cmd1 | cmd2 | cmd3)

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

我需要处理大量数据流。流大于将适合 RAM。

流需要按顺序应用多个转换。相互通信的异步执行任务的管道会很好地工作,很像下面的 shell 管道:

producer | transform1 | transform1 | transform2 | consumer

其中一些转换最好通过命令行实用程序实现,这些实用程序接受来自 STDIN 的输入并将其输出生成到 STDOUT。

但是其中一些转换很容易用 Perl 编写。我想避免必须管理许多小脚本,所以我希望它们由启动管道的程序中的 Perl subs 处理。这些 sub 需要在子进程中 运行,从 STDIN 读取并写入 STDOUT,就像此管道中的其他任务一样。这意味着我不能只使用 system.

如何在 Perl 中实现此管道?

# 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;
    }
}

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 how you use it.
# func4 | func2 | func1 | func3 | func2 | func4
pipefunc(\&func4,\&func2,\&func1,\&func3,\&func2,\&func4);

尝试:

seq 10 | perl pipefunc.pl

就像 shell 中的管道一样,它可以在大于系统虚拟内存的输入上正常工作。就像 shell 中的管道一样,它将 运行 每个函数的进程。

使用 system 很容易将 perl 函数与 shell 命令混合使用,因此通过将 shell 命令包装在 sub myfunc { system "shell command"; } 中,您可以获得以下效果:perlfunc | shell command | perlfunc | perlfunc | shell command.

myfunc1myfunc2myfunc3 接受输入作为子程序的参数,return 将输出作为子程序的 return 值。即改

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

类似于

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

然后你可以像这样链接你的函数:

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

IPC::Run 软件包将为您完成此操作。

#! /usr/bin/perl

use IPC::Run qw(run);

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

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