管道将一个进程的输出连接到另一个进程的输入。双方都不知道对方,也不关心对方是如何运作的。
但是,像这样把东西放在一起打破了 Unix 管道哲学的小工具,每个小工具都擅长非常狭窄的工作。如果您将这两件事联系起来,即使您想要一项任务,您也必须始终执行两项任务(尽管您可以进入配置以关闭一项任务,但工作量很大)。
我处理了很多 LaTeX,并通过Makefile控制一切。我并不真正关心命令的外观,我什至不必记住它们是什么:
short-clean.tex: short.tex
cat short.tex | try1.pl | try2.pl > $@
无论如何,让我们做吧
我会将自己限制在基本串联的约束中,而不是完全重写或重新排列,主要是因为有一些有趣的事情要展示。
考虑一下如果您通过在第一个程序的文本末尾添加第二个程序的文本来连接这两个程序会发生什么。
有多种方法可以解决这个问题,但是当您已经有两个工作程序可以完成它们的工作时,它们都没有多大意义。我会把它放在Makefile中并忘记它。
但是,假设您确实希望将所有内容都放在一个文件中。
或者,您可以通过在第一部分写入临时文件,然后在第二部分读取该临时文件来做同样的事情。
一个更复杂的程序将第一个程序写入第二个程序同时读取的管道(程序内部)。但是,您几乎必须重写所有内容,以便两个程序同时发生。
这是程序 1,它将大多数字母大写:
#!/usr/bin/perl
use v5.26;
$|++;
while( <<>> ) { # safer line input operator
print tr/a-z/A-Z/r;
}
这是程序 2,它折叠空格:
#!/usr/bin/perl
use v5.26;
$|++;
while( <<>> ) { # safer line input operator
print s/\s+/ /gr;
}
他们连续工作以完成工作:
$ perl program1.pl
The quick brown dog jumped over the lazy fox.
THE QUICK BROWN DOG JUMPED OVER THE LAZY FOX.
^D
$ perl program2.pl
The quick brown dog jumped over the lazy fox.
The quick brown dog jumped over the lazy fox.
^D
$ perl program1.pl | perl program2.pl
The quick brown dog jumped over the lazy fox.
THE QUICK BROWN DOG JUMPED OVER THE LAZY FOX.
^D
现在我想把这些结合起来。首先,我会做一些不影响操作但以后会更容易的更改。我不会使用隐式文件句柄,而是将这些显式文件句柄从实际文件句柄中删除:
方案一:
#!/usr/bin/perl
use v5.26;
$|++;
my $output_fh = \*STDOUT;
while( <<>> ) { # safer line input operator
print { $output_fh } tr/a-z/A-Z/r;
}
方案二:
#!/usr/bin/perl
$|++;
my $input_fh = \*STDIN;
while( <$input_fh> ) { # safer line input operator
print s/\s+/ /gr;
}
现在我有机会在不影响程序内容的情况下更改这些文件句柄。while
不知道也不关心那个文件句柄是什么,所以让我们先在程序 1 中写入一个文件,然后在程序 2 中从同一个文件中读取:
方案一:
#!/usr/bin/perl
use v5.26;
open my $output_fh, '>', 'program1.out' or die "$!";
while( <<>> ) { # safer line input operator
print { $output_fh } tr/a-z/A-Z/r;
}
close $output_fh;
方案二:
#!/usr/bin/perl
$|++;
open my $input_fh, '<', 'program1.out' or die "$!";
while( <$input_fh> ) { # safer line input operator
print s/\h+/ /gr;
}
但是,您不能再在管道中运行这些,因为程序 1 不使用标准输出并且程序 2 不读取标准输入:
% perl program1.pl
% perl program2.pl
但是,您现在可以加入计划、shebang 和所有:
#!/usr/bin/perl
use v5.26;
open my $output_fh, '>', 'program1.out' or die "$!";
while( <<>> ) { # safer line input operator
print { $output_fh } tr/a-z/A-Z/r;
}
close $output_fh;
#!/usr/bin/perl
$|++;
open my $input_fh, '<', 'program1.out' or die "$!";
while( <$input_fh> ) { # safer line input operator
print s/\h+/ /gr;
}
您可以跳过文件并改用字符串,但此时,您已经超越了仅仅连接文件的范围,还需要进行一些协调以使它们与数据共享标量。尽管如此,程序的核心并不关心你是如何制作这些文件句柄的:
#!/usr/bin/perl
use v5.26;
my $output_string;
open my $output_fh, '>', \ $output_string or die "$!";
while( <<>> ) { # safer line input operator
print { $output_fh } tr/a-z/A-Z/r;
}
close $output_fh;
#!/usr/bin/perl
$|++;
open my $input_fh, '<', \ $output_string or die "$!";
while( <$input_fh> ) { # safer line input operator
print s/\h+/ /gr;
}
所以让我们更进一步,做 shell 已经为我们做的事情。
#!/usr/bin/perl
use v5.26;
pipe my $input_fh, my $output_fh;
$output_fh->autoflush(1);
while( <<>> ) { # safer line input operator
print { $output_fh } tr/a-z/A-Z/r;
}
close $output_fh;
while( <$input_fh> ) { # safer line input operator
print s/\h+/ /gr;
}
从这里开始,它变得有点棘手,我不打算通过轮询文件句柄进行下一步,这样一件事可以写,下一件事可以读。有很多事情可以为您做到这一点。而且,您现在正在做很多工作来避免已经简单且有效的事情。
下一步是将代码分成函数(可能在库中),而不是所有这些废话,并将这些代码块作为隐藏其详细信息的命名事物处理:
use Local::Util qw(remove_comments minify);
while( <<>> ) {
my $result = remove_comments($_);
$result = minify( $result );
...
}
这可以变得更加有趣,您只需通过一系列步骤而不知道它们是什么或将有多少。而且,由于所有的婴儿步骤都是独立的,你基本上回到了管道的概念:
use Local::Util qw(get_input remove_comments minify);
my $result;
my @steps = qw(get_input remove_comments minify)
while( ! eof() ) { # or whatever
no strict 'refs'
$result = &{$_}( $result ) for @steps;
}
一个更好的方法是把它变成一个对象,这样你就可以跳过软引用:
use Local::Processor;
my @steps = qw(get_input remove_comments minify);
my $processer = Local::Processor->new( @steps );
my $result;
while( ! eof() ) { # or whatever
$result = $processor->$_($result) for @steps;
}
就像我之前所做的那样,程序的核心并不关心或提前知道这些步骤。这意味着您可以将步骤顺序移动到配置中,并为任何组合和顺序使用相同的程序:
use Local::Config;
use Local::Processor;
my @steps = Local::Config->new->get_steps;
my $processer = Local::Processor->new;
my $result;
while( ! eof() ) { # or whatever
$result = $processor->$_($result) for @steps;
}
我在Mastering Perl和Effective Perl Programming中写了很多关于这类东西的文章。但是,因为你能做到并不意味着你应该这样做。这重新发明了很多make已经可以为你做的事情。我不会在没有充分理由的情况下做这种事情——<code>bash 并且make
必须非常烦人才能激励我走到这一步。