代码之家  ›  专栏  ›  技术社区  ›  the wolf

管道元素之间有什么区别,或者将它们指定为perl-One内衬中的参数?

  •  2
  • the wolf  · 技术社区  · 14 年前

    在学习Perl的同时,我也在学习Linux(Ubuntu),所以这里有点像消防水龙带。

    有什么区别:

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

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

    第一个将文件名传递给Perl,第二个将文件内容传递给Perl。在Unix或Perl的特殊属性下,这总是正确的吗?

    3 回复  |  直到 14 年前
        1
  •  7
  •   brian d foy    14 年前

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

     while( <> ) { ... }
    

    在Unix shell中,这是一个常见的操作,因此您根本不必使用Perl:

     $ ifconfig | grep en0
    

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

     foreach( @ARGV ) { ... }
    

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

    然而,钻石运营商, <> 将自动遍历您在命令行上指定的文件名,以便 while 循环仍然有效。这是Perl特有的特性。

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

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

    $ find2perl . -type f
    

    输出是一个Perl程序,不需要依赖任何外部命令。

        2
  •  3
  •   Chas. Owens    14 年前

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

    第二个电话 珀尔 以文件名列表作为参数。如果传入参数 -N 将打开每个参数并从每个文件中读取每一行。

    因此,第一个操作文件的名称,第二个操作文件的内容。

    你可以看到代码 珀尔 正在使用 B::Deparse :

    perl -MO=Deparse -nle 'print'
    

    生产

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

    这个 BEGIN 块和chomp由 -l 期权与期权 while 循环由 -N 选择权。 ARGV 是一个特殊的文件句柄,执行从 斯坦丁 如果没有参数存在,或者依次打开每个参数(如果有)。

    这两种形式绝对不能互换。一影响 斯坦丁 以及其他命令行参数。如果你把第一个改成 find . -type f | xargs perl -nle '... #aka yada yada' 然后它们就可以互换了 xargs 版本可能会运行 珀尔 不止一次,因为命令行太长,backtick版本可能会爆炸。

    许多Unix程序充当过滤器。过滤器的规则是从 斯坦丁 如果命令行上没有文件,或者命令行上给定的文件列表中没有文件。一个简短的列表包括 cat , grep sort . 如您所见,Perl5使实现过滤器变得容易。但请注意,Perl5实现这一点的方式并不十分安全。它使用了过时的双参数版本 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.
    
        3
  •  0
  •   Community CDub    7 年前

    您会问“第一个将文件名传递给Perl,第二个传递看起来像是的文件内容。在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放入其中,并在空目录中创建26个文件,每个文件包含1000行。

    使用Ubuntu/Linux实用程序 cat *.txt 您可以看到文件的内容。这个 *.txt expanded 对所有26个 .txt 文件夹。具有 wc -l *.txt 您可以验证所有26个文件的行数。你可以使用 wc -l {a..e}.txt 巴什在哪里使用 brace 扩展。你可以把这些表格放在一根管子上 cat *.txt | wc -l 只需对所有26个文件进行一行计数。在第一个例子中, WC-L*.TXT 正在打开26个文件,计算行数,并显示结果。在第二个示例中 目录*.txt wc-l 程序 cat 正在打开26个文件并生成连接到stdout的文本流;该 γ 将其转换为指向下一个程序的管道;在本例中 wc -l 它在其stdin上接收该输出,并计算该输出的行数,而不考虑单独的文件。

    有了PerlOne语句,您可以轻松地搜索这些文件。例子:

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

    你可以使用 egrep awk 做同样的事情:

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

    如果将该表单转换为管道,则在Perl(或awk或egup)左侧的上一个命令的输出上进行操作。前一部分stdout的输出正被馈送到Perl的stdin。如果该命令生成文件名,则您将对文件名进行操作:

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

    除非你先用 :

    $ 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 ,命令行长度有限制,您应该注意此表单。您还可以使用反勾号以意外方式中断文件名。而不是反勾号表,使用 find 具有 xargs :

    $ 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