管道元素之间有什么区别,或者在Perl单行中指定它们作为参数?

时间:2010-09-24 01:51:37

标签: perl bash ubuntu

在学习Perl的同时,我也在学习Linux(Ubuntu),所以这里有点火热的时间。

有什么区别:

find . -type f | perl -nle '... #aka yada yada'

perl -nle '... # same yada yada' `find . -type f`

第一个将文件NAMES传递给Perl,第二个传递文件CONTENTS。在Unix或Perl的特殊属性下,这总是如此吗?

3 个答案:

答案 0 :(得分:7)

第一个生成文件列表并将其“管道”为perl。然后perl通过读取标准输入来读取列表:

 while( <> ) { ... }

这在unix shell中很常见,所以你根本不需要使用perl:

 $ ifconfig | grep en0

第二个生成文件名列表并将其转换为命令行参数,然后在@ARGV中显示在您的程序中:

 foreach( @ARGV ) { ... }

这个功能并不是Perl特有的。 shell在命令之后提供程序可以访问的某种数据结构中的位。其他语言也有类似的结构,即使它们看起来不一样。

但是,菱形运算符<>将自动浏览您在命令行中指定的文件名,以便while循环仍然有效。这是Perl特有的功能。

当你有一长串参数时,第二种方法的问题往往会出现。有些shell限制了可以在命令行上显示的内容。因为那个原因我不喜欢第二个版本。

但是,您可以将其转换为自包含的Perl程序,而不是使用find(1)(shell版本):

$ find2perl . -type f

输出是Perl程序,不必依赖任何外部命令。

答案 1 :(得分:3)

第一个将文件名(每行一个)发送到程序的STDIN-n导致perl循环(因为没有命令行参数)。

第二个调用perl,文件名列表作为参数。如果在-n中传递参数将打开每个参数并从每个文件中读取每一行。

所以第一个操作文件的名称,第二个操作文件的内容。

您可以使用B::Deparse看到代码perl正在为您撰写:

perl -MO=Deparse -nle 'print'

产生

BEGIN { $/ = "\n"; $\ = "\n"; }
LINE: while (defined($_ = <ARGV>)) {
    chomp $_;
    print $_;
}
-e syntax OK

BEGIN块和chomp由-l选项创建,while循环由-n选项创建。 ARGV是一个特殊的文件句柄,如果没有参数,则执行从STDIN读取的魔法,或者如果存在则依次打开每个参数。

这两种形式绝对不可互换。一个影响STDIN和其他命令行参数。如果您将第一个更改为find . -type f | xargs perl -nle '... #aka yada yada',那么它们将大部分可以互换(xargs版本可能会多次运行perl并且反引号版本可能会爆炸,因为命令行也是长)。

许多UNIX程序充当过滤器。过滤器的规则是,如果在命令行上没有文件,或者从命令行上给出的文件列表中,它们从STDIN读取。简短列表包括catgrepsort。正如您所见,Perl 5简化了过滤器的实现。但请注意,Perl 5实现这一点的方式并不十分安全。它使用过时的两个参数版本open,这意味着某些文件名可能会产生意想不到的后果:

perl -nle print "cat /etc/passwd|"

该命令实际运行cat /etc/passwd,而不是打开名为cat /etc/passwd|的文件。为防止出现此问题,建议您检查@ARGV是否有可疑名称,或使用ARGV::readonly模块为您清除@ARGV

perl -MARGV::readonly -nle print "echo foo|"
Can't open < echo foo|: No such file or directory.

答案 2 :(得分:0)

你问“第一个将文件NAMES传递给Perl,第二个传递文件CONTENTS似乎。在Unix下它或Perl的特殊属性是否总是如此?”此行为不是特定于Perl。部分原因是由Unix完成的。它更像是一个广泛遵循的惯例。管道行为(命令后跟|)由操作系统完成。程序对其命令行输入或其产生的输出执行的操作是命令特定的。

实施例。请在Bash中关注您的计算机。

$ mkdir pipetestdir; cd pipetestdir    
$ for f in {a..z}; do printf "%s\n" "File: $f, line: "{1..1000} > $f.txt; done

这将创建一个空目录,cd进入它,并在空目录中创建每行1000行的26个文件。

使用Ubuntu / Linux实用程序cat *.txt,您可以看到文件的内容。 Bash *.txt expanded为所有26 .txt个文件。使用wc -l *.txt,您可以验证所有26个文件的行数。您可以使用wc -l {a..e}.txt的格式,其中Bash使用brace扩展。您可以将这些表单放到管道中,并使用cat *.txt | wc -l来获取所有26个文件的单行数。在第一个示例中,wc -l *.txt打开26个文件,计算行数并显示结果。在cat *.txt | wc -l的第二个示例中,程序cat打开26个文件并生成到STDOUT的连接文本流; |将其转换为指向下一个程序的管道;在这种情况下wc -l在其STDIN上接收该输出并计算其行,而不考虑单独的文件。

使用Perl一个衬垫,您可以轻松搜索这些文件。例如:

$ perl -lne 'print if /^.*666/' *.txt    # the devil's line from 26 files...

您可以使用egrepawk执行相同操作:

$ egrep '^.*666$' *.txt
$ awk "/^.*666$/ {print}" *.txt

如果将该窗体转换为管道,则操作Perl(或awk或egrep)左侧上一个命令的OUTPUT。前一部分STDOUT的输出正在输入Perl的STDIN。如果该命令生成文件名,则使用文件名:

$ ls *.txt | perl -lne 'print if /c|d|z/'
$ find . -name '*.txt' | perl -lne 'print if /c|d|z/'

除非您先使用cat扩展它们:

$ cat *.txt | perl -lne 'print if /^.*?(c|d|z).*?666$/'

与此类似的输出:

$ perl -lne 'print if /^.*?(c|d|z).*?666$/' *.txt

也许这就是你对可以互换的形式感到困惑的地方?他们不是!正在发生两件截然不同的事情。如果您使用cat *.txt | perl '...'所有文件正在被汇总到一个长文本流中并发送到管道中的下一个阶段;在这种情况下perl '...'。 Perl无法区分哪个文件来自哪个文件。只是因为我们在创建它们时在每个文件中放置了一个标记,我们可以看到哪个文件是哪个。

在另一种形式perl '...' *.txt中,perl打开文件并完全控制每个文本流和文件。您可以控制是否打开文件,是否打印文件名等等......

但是,避免使用cat a.txt | perl '...'的特定形式(即在单个文件中使用cat)以避免可怕的Useless Use of Cat Award: - }

您具体询问了表格:

$ perl -nle '... # same yada yada' `find . -type f`

作为brian d foy pointed out,命令行长度有限制,你应该警惕这种形式。您还可以使用后退标记以意外方式中断文件名。请使用findxargs

,而不是后退刻度表单
$ find . -type f -print0 | xargs -0 perl -nle 'print if /^.*666$/'

要查看破坏文件名的问题,请键入以下命令:

$ mv z.txt "file name with spaces" 
$ perl -ple '' `find . -name "file*"`       #fails...
$ find . -name "file*" -print0 | xargs -0 perl -ple '' #works...
$ find . -type f -exec perl -wnl -e '/\s1$/ and print' {} + #alternative
相关问题