在Perl脚本中,我们应该使用Shell命令还是调用模拟Shell操作的Perl函数?

5

我希望了解最佳实践。假设我想获取文件中某一行的内容。我可以使用单行shell命令或编写子程序来获取答案,如下面的代码所示。

名为some_text的文本文件:

She laughed. Then both continued eating in silence, like strangers,
but after dinner they walked side by side; and there sprang up
between them the light jesting conversation of people who are free
and satisfied, to whom it does not matter where they go or what
they talk about.

获取文件第5行内容的代码

#!perl
use warnings;
use strict;

my $file = "some_text";
my $lnum = 5;
my $shellcmd = "awk 'NR==$lnum' $file";
print qx($shellcmd);
print getSrcLine($file, $lnum);

sub getSrcLine {
    my($file, $lnum) = @_;
    open FILE, $file or die "$!";
    my @ray = <FILE>;
    return $ray[$lnum-1];
}

我之所以问这个问题,是因为我看到很多 Perl 脚本,在某些时候调用了 Shell 命令,而在稍后的某个时候,则通过调用(库或手写的)函数来完成相同的任务,例如,rm -rfFile::Path::rmtree。我只是想让它保持一致。
那么,推荐采取什么做法呢?

推荐的方式取决于代码的意图。如果您想在非Unix机器上执行,则使用Perl库,否则请查看我发布的链接。希望这可以帮助到您。 - Rajeev
也许每个脚本都是在一段时间内开发的,可能由多个程序员完成。这些脚本不断演变,代码并不完全自洽,部分取决于参与者对Perl和shell的经验多少。 - Jonathan Leffler
2
请注意,虽然将整个五行文件读入以获取第五行并不太痛苦,但这可能是获取百万行文件的第五行的次优策略。 - Jonathan Leffler
3
虽然在这个例子中不太重要,但是FILE变量不是词法作用域(my)文件句柄,所以退出函数时它不会自动关闭。建议使用open my $FILE, '<', "$file" or die "$!";my @ray = <$FILE>;。这种方法在函数终止时会关闭文件。如果在调用getSrcLine后添加seek(FILE, 0, 0) or die "$!";,你可以证明FILE没有被关闭,因为它不会生成错误。 - Jonathan Leffler
@JonathanLeffler 感谢您的见解。我一直以为文件句柄在离开函数作用域后会被关闭。关于我的另一个问题,根据评论和回复,我想这真的归结于个人选择,如果可移植性不是问题的话。对我来说,Shell 命令编写速度更快,所以我倾向于在 Perl 脚本中经常使用它们。 - Unos
1
Unos,只有词法文件句柄会自动关闭。这些以$开头并在作用域内声明的句柄。请参见http://szabgab.com/open-files-in-the-old-way.html。无论如何,@ray = <$fh>将读取整个文件。而while (my $line = <$fh>) { }则逐行读取。 - szabgab
3个回答

14

如果Perl有一个函数可以完成操作,那么Perl认为你应该使用它的版本。但是,你提供了一个Perl模块提供的纯Perl方法来完成操作。这是非常不同的。通常情况下没有单一答案,所以你必须自己决定该怎么做:

  • 纯Perl方法是否能正确地完成操作?例如,File::Copy有一些限制,因为它为用户做了一些尴尬的决定,所以很多人认为它是有缺陷的。例如请参考File::Copy versus cp/mv

  • 纯Perl方法是否能在可接受的时间内完成操作?有时外部程序的运行速度要快得多,有时则慢得多。

  • 外部命令通常在同一类型的系统中是可移植的(例如所有类Unix的系统),但可能不能跨不同类型的系统(例如Windows和类Unix的系统)。你对此的容忍度可能会影响答案。即使你认为你正在运行相同的命令,类Unix系统的不同变种可能对操作有不同的开关参数。

  • 将包含空格、引号和特殊字符的复杂参数传递给外部命令可能会让你哭泣。你必须做出很多琐碎的工作,以确保你正确地处理了参数。Perl子程序则不需要关心这些。

  • 在使用外部命令时,你必须更加注意你所做的事情。如果你只调用rm,Perl将搜索你的PATH并使用第一个叫做rm的程序。这并不意味着它就是你认为的那个程序。我在《精通Perl》的“安全编程技巧”中经常谈到这一点。

  • 如果纯Perl方法需要一个模块,尤其是该模块有很多复杂的依赖项,那么你可能会在未来遇到依赖关系或分发问题。

就我个人而言,我会从纯Perl方法开始,直到它无法满足当前的需求。

对于你的具体示例,我会使用Perl。通过调用awk,这是一个原始的Perl,感觉很奇怪。你应该能够在Perl中完成awk做的所有事情。如果你有一个awk程序,你可以使用a2p程序将它转换为Perl:

 NR==5
a2p 将其转换为(在开始时进行一些设置位取模):
while (<>) {
    print $_ if $. == 5;
}

请注意,即使您有第五行,它仍会扫描整个文件。但是,您可以将翻译后的程序用作起点:

while (<>) {
    if( $. == 5 ) {
        print;
        last;
        }
}

我认为你不应该调用其他程序来避免这段Perl代码。

要删除目录树,我喜欢使用File::Path 。它有一些依赖项,但是它们都在Perl标准库中。该模块使用起来非常简单,如果有任何问题,也非常容易解决。在遇到无法运行的情况之前,我会一直使用它。


谢谢您提供详细的答案。我特别喜欢关于安全性的观点,因为在编写Perl代码时我并没有考虑太多。此外,我还遇到了非常方便的a2p程序!现在我深信只要Perl能够可靠地完成相同的任务,我应该尽量避免在我的脚本中使用外部shell命令。 - Unos
非常好的答案。我唯一要补充的是关于脚本的要求。如果你正在编写一个一次性使用的程序,如果它可以节省时间,那么调用*nix命令是相当合理的。最近我在为数据库导出到导入脚本编写数据处理脚本时就这样做了。对于其中的一部分,通过外部命令进行调用更快,并且该脚本在运行(正确)一次后将被丢弃。如果我要编写每周执行类似功能的程序,我会花时间用Perl来正确实现它。 - Christopher Cashell

4
如果你想让你的应用程序能够在非Unix系统上移植,那么一定要使用Perl编写所有代码。
如果不需要在非Unix系统上移植,那么就看个人喜好了...创建一个新进程会比较慢,但如果这对任务来说不重要,那就没关系。个人而言,我会选择可以更快实现的解决方案。

2

在我看来,可用的代码应该是第一优先级。例如,如果文件名中有空格,您的代码将失败。

使用 shell 脚本使编码更加困难,因为您的程序需要正确生成另一个由 sh 运行的程序。(如果使用系统的多参数版本以避免 shell,则此问题会消失。)

此外,使用外部工具可能会使错误处理变得困难。您甚至没有尝试去处理这些错误!

另一方面,使用外部工具有多个原因。例如,Perl 提供的文件复制工具不如 cp 命令好用;使用 sort 工具可以对任意大小的文件进行排序,而内存占用有限;等等。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接