Perl 命令行选项

Perl 有大量命令行选项,可以帮助您使程序更加简洁,并为使用 Perl 的单次命令行脚本打开许多新可能性。在这篇文章中,我们将探讨其中一些最有用的选项。

安全网选项

有三个选项我认为可以被视为“安全网”,因为它们可以防止你在做特别聪明(或愚蠢)的事情时出洋相!虽然它们并非总是必要的,但很少能看到有经验的 Perl 程序员在没有它们的情况下工作。

这些中的第一个是 -c。此选项编译程序而不运行它。这是一个确保在编辑程序时没有引入任何语法错误的好方法。当我正在编写程序时,我从未超过几分钟不保存文件并运行它

  $ perl -c <program>

这确保了程序仍然可以编译。当你只做了一些更改时,修复问题比输入几百行代码然后尝试调试要容易得多。

下一个安全网是 -w 选项。此选项打开警告,如果 Perl 在你的代码中找到任何问题,它将给你显示这些警告。每个警告都是你程序中潜在的错误,应该进行调查。在 Perl 的现代版本(自 5.6.0 以来)中,-w 选项已被 use warnings 祈使语句所取代,它比命令行选项更灵活,因此你不应该在新的代码中使用 -w

最后一个安全网是 -T 选项。此选项将 Perl 设置为“污染模式”。在这种模式下,Perl 本身不信任来自程序源外部接收的任何数据 - 例如,来自命令行传递的数据,从文件读取的数据,或从 CGI 参数获取的数据。

污染数据不能用于与外界交互的表达式 - 例如,你不能在 system 调用中使用它,也不能用它作为打开文件的名称。完整的限制列表可以在 perlsec 手册页中找到。

为了在这些潜在的危险操作中使用这些数据,你需要清除它。你可以通过检查它是否符合正则表达式来实现。关于污染模式的详细讨论将占用一整篇文章,所以这里不再详细说明,但使用污染模式是一个非常好的习惯 - 尤其如果你正在编写(如 CGI 程序)从用户那里获取未知输入的程序。

实际上还有一个选项也属于这个系列,那就是 -d。此选项将您置于 Perl 调试器中。这也是一个太大的话题,无法在这篇文章中展开,但建议您查看“perldoc perldebug”或 Richard Foley 的《Perl Debugger Pocket Reference》。

命令行程序

接下来要探讨的一些选项使得在命令行上运行简短的 Perl 程序变得容易。第一个选项,-e,允许您定义由编译器执行的 Perl 代码。例如,当你可以在命令行中输入此内容时,就无需在 Perl 中编写“Hello World”程序。

  $ perl -e 'print "Hello World\n"'

您可以有任意多的 -e 选项,并且它们将以命令行上出现的顺序运行。

  $ perl -e 'print "Hello ";' -e 'print "World\n"'

请注意,与正常的 Perl 程序一样,除了最后一行代码外,所有代码都需要以分号 ; 字符结尾。

虽然可以使用 -e 选项来加载模块,但 Perl 提供了 -M 选项来使这一过程更容易。

  $ perl -MLWP::Simple -e'print head "http://www.example.com"'

所以 -Mmoduleuse module 是一样的。如果你不希望导入模块的默认导入,可以使用 -m。使用 -mmodule 等同于 use module(),这会关闭任何默认导入。例如,下面的命令不会显示任何内容,因为 head 函数没有被导入到你的 main 包中。

  $ perl -mLWP::Simple -e'print head "http://www.example.com"'

-M-m 选项实现了各种语法糖,使它们尽可能容易使用。你可以将任何通常传递给 use 语句的参数列在 = 符号之后。

  $ perl -MCGI=:standard -e'print header'

这个命令从 CGI.pm 导入了“:standard”导出集,因此 header 函数可用于你的程序。可以使用引号和逗号作为分隔符列出多个参数。

  $ perl -MCGI='header,start_html' -e'print header, start_html'

在这个例子中,我们只导入了两个方法 headerstart_html,因为我们只使用了这两个方法。

隐式循环

其他两个命令行选项,-n-p,在 -e 代码周围添加循环。这两个选项都非常有用,可以逐行处理文件。如果你输入如下内容:

  $ perl -n -e 'some code' file1

那么 Perl 将将其解释为

  LINE:
    while (<>) {
      # your code goes here
    }

注意使用了空文件输入操作符,它会逐行读取命令行上给出的所有文件。输入文件的每一行将被依次放入 $_ 中,以便你可以处理它。作为一个例子,尝试

  $ perl -n -e 'print "$. - $_"' file

这将被转换为

  LINE:
    while (<>) {
      print "$. - $_"
    }

这段代码将打印出文件的每一行及其当前行号。

-p 选项使其更加简单。此选项每次循环都会打印出 $_ 的内容。它创建这样的代码

  LINE:
    while (<>) {
      # your code goes here
    } continue {
      print or die "-p destination: $!\n";
    }

这使用了很少使用的 continue 块在 while 循环中,以确保总是调用 print 语句。

使用此选项,我们的行号生成器变为

  $ perl -p -e '$_ = "$. - $_"'

在这种情况下,不需要显式调用打印,因为 -p 会为我们调用 print

注意,LINE: 标签在那里,这样你就可以很容易地跳到下一个输入记录,无论你在嵌套循环有多深。你可以使用 next LINE 来这样做。

  $ perl -n -e 'next LINE unless /pattern/; print $_'

当然,那个例子可能会被写成

  $ perl -n -e 'print unless /pattern/'

但在更复杂的例子中,next LINE 构造可能会使代码更容易理解。

如果你需要在主代码循环之前或之后执行处理,可以使用 BEGINEND 块。以下是一个基本的计数文本文件单词的方法

  $ perl -ne 'END { print $t } @w = /(\w+)/g; $t += @w' file.txt

每次循环时,我们将所有单词(定义为连续的 \w 字符串)提取到 @w 中,并将 @w 中元素的数量添加到我们的总变量 $t 中。在循环完成后,END 块运行并打印出 $t 中的最终值。

当然,人们对于什么是有效单词的定义可能会有所不同。Unix wc(单词计数)程序使用的定义是空白字符分隔的字符串。我们可以通过稍微修改我们的程序来模拟这一点,如下所示

  $ perl -ne 'END { print $x } @w = split; $x += @w' file.txt

但有一些命令行选项可以使这更加简单。首先,-a 选项开启 autosplit 模式。在这种模式下,每个输入记录都被分割,结果列表存储在名为 @F 的数组中。这意味着我们可以编写如下单词计数程序

  $ perl -ane 'END {print $x} $x += @F' file.txt

默认情况下,用于分割记录的是一到多个空白字符。当然,你可能希望使用其他字符来分割输入记录,你可以通过 -F 选项来控制。所以,如果我们想将程序改为以所有非单词字符分割,我们可以这样做

  $ perl -F'\W' -ane 'END {print $x} $x += @F' file.txt

为了更具体地了解我们可以使用这些选项做什么,让我们看看 Unix 密码文件。这是一个简单的以冒号分隔的文本文件,每条记录对应一个用户。该文件中的第七列是该用户的登录外壳路径。因此,我们可以使用如下命令行脚本来生成特定系统上最常用的外壳的报告:

  $ perl -F':' -ane '$s{$F[6]}++;' \
  > -e 'END { print "$_ : $s{$_}" for keys %s }' /etc/passwd

好吧,这个命令比一行长,输出也没有排序(尽管添加排序也很简单),但你可能已经对命令行可以执行的操作有所了解。

记录分隔符

在我的上一篇文章中,我谈到了很多关于 $/$\ —— 输入和输出记录分隔符的内容。$/ 定义了每次从文件句柄请求下一个记录时 Perl 将读取多少数据,而 $\ 包含一个值,该值将追加到程序打印的任何数据末尾。默认情况下,$/ 的值是一个新行,而 $\ 的默认值是一个空字符串(这也是为什么你通常会在调用 print 时显式添加新行)。

现在,在由 -n-p 设置的隐式循环中,定义 $/$\ 的值可能很有用。你当然可以在 BEGIN 块中这样做,但 Perl 通过 -0(这是一个零)和 -l(这是一个 L)命令行选项为你提供了更简单的选项。这可能会有些令人困惑(嗯,这让我很困惑),所以我会慢慢解释。

使用 -0 并提供十六进制或八进制数字将 $/ 设置为该值。特殊值 00 将 Perl 设置为段落模式,而特殊值 0777 将 Perl 设置为文件吸入模式。这些与将 $/ 设置为空字符串和 undef 相同。

使用 -l 并不提供任何值有两个作用。首先,它会自动 chomp 输入记录,其次,它会将 $\ 设置为 $/。如果你给 -l 提供一个八进制数字(与 -0 不同,它不接受十六进制数字),它会将 $\ 设置为该数字表示的字符,并开启自动 chomp

老实说,我很少使用 -0 选项,我通常使用不带参数的 -l 选项,只是为了在每行输出的末尾添加新行。例如,我通常会写我的原始“Hello World”示例如下:

  $ perl -le 'print "Hello World"'

如果我在做需要更改输入和输出记录分隔符值的事情,那么我可能已经超出了命令行脚本的范围。

原地编辑

通过我们已经看到的选项,构建一些强大的命令行程序非常容易。在命令行程序中,使用 Unix I/O 重定向是很常见的,如下所示:

  $ perl -pe 'some code' < input.txt > output.txt

这将从 input.txt 中读取记录,执行某种转换,并将转换后的记录写入 output.txt。在某些情况下,你不想将更改后的数据写入不同的文件,如果将更改后的数据写回到同一文件,通常会更方便。

你可以使用 -i 选项来得到这种外观。实际上,Perl 会重命名输入文件,并从该重命名版本中读取,同时将原始名称的新文件写入。如果 -i 被提供一个字符串参数,那么该字符串将被追加到文件原始版本的名称中。例如,要在一个数据文件中将所有“PHP”的实例更改为“Perl”,你可以编写如下内容:

  $ perl -i -pe 's/\bPHP\b/Perl/g' file.txt

Perl逐行读取输入文件,进行替换,然后将结果写回与原始文件同名的新的文件中——实际上会覆盖它。如果你对Perl的能力不是很自信,你可能要备份原始文件,如下所示

  $perl -i.bak -pe 's/\bPHP\b/Perl/g' file.txt

最终你将在file.txt中获得转换后的数据,原始文件备份在file.txt.bak中。如果你喜欢vi,你可能喜欢使用-i~

更多信息

Perl有大量的命令行选项。本文仅列出了一些最实用的选项。要查看完整列表(以及有关此处所涉及选项的更多信息),请参阅“perlrun”手册页。

标签

大卫·克罗斯

大卫·克罗斯是一位经验丰富的Perl程序员,在开发创新和高效的软件解决方案方面具有丰富的专业知识。在Perl社区中拥有强大的影响力,他贡献了众多模块,并通过演讲和研讨会分享了他的知识。在编码之外,大卫喜欢深入研究开源项目并与同行爱好者合作。他讨厌写个人简介,因此他把这项工作交给了ChatGPT。

浏览他的文章

反馈

这篇文章有问题吗?请在GitHub上打开一个问题或拉取请求,帮助我们。