在perl中反勾号(``),system和exec都用来执行命令,这篇文章将给我们介绍它们各自的使用方法,联系,以及区别。
一、使用方法
1. 反勾号(``)
首先,我们有命令输入操作符,也叫反勾号操作符,因为它看起来象这样:
$info = `finger $user`;
一个用反勾号(技术上叫重音号)引起的字串首先进行变量替换,就象一个双引号引起的字串一样。得到的结果然后被系统当作一个命令行,而且那个命令的输出成为伪文本的值。(这是一个类似 Unix shell 的模块。)在标量环境里,返回一个包含所有输出的字串。在列表环境里,返回一列值,每行输出一个值。(你可以通过设置 $/ 来使用不同的行结束符。)
每次计算伪文本的时候,该命令都得以执行。该命令的数字状态值保存在 $?(参阅第二十八章获取 $? 的解释,也被称为 $CHILD_ERROR )。和这条命令的 csh 版本不同的是,对返回数据不做任何转换——换行符仍然是换行符。和所有 shell 不同的是,Perl 里的单引号不会隐藏命令行上的变量,使之避免代换。要给 shell 传递一个 $,你必须用反斜杠把它隐藏起来。我们上面的 finger 例子里的 $user 被 Perl 代换,而不是被 shell。(因为该命令 shell 处理,参阅第二十三章,安全,看看与安全有关的内容。)
反勾号的一般形式是 qx//(意思是“引起的执行”),但这个操作符的作用完全和普通的反勾号一样。你只要选择你的引起字符就行了。有一点和引起的伪函数类似:如果你碰巧选择了单引号做你的分隔符,那命令行就不会进行双引号代换;
$perl_info = qx(ps $$); # 这里 $$ 是 Perl 的处理对象 $perl_info = qx'ps $$'; # 这里 $$ 是 shell 的处理对象
下面是一个例子:
在笔者的F盘中存在一个perl文件F://Demo3.pls,它的作用就是被另外一个程序F://Demo1.pls调用,然后F://Demo3.pls读取log3.log中的数据。
Demo3.pls
#!/usr/bin/perl -w
use strict;
use warnings;
unless(open(FILE_H,"<F://log3.log")){
print "Can not open the file";
}
my @str = <FILE_H>;
my $count = @str;
close(FILE_H);
for(my $i = 0;$i<$count;$i++){
print "$str[$i]";
}
Demo.pls
#!/usr/bin/perl –w
my @str = qx/perl F://Demo3.pls/;#或者` perl F://Demo3.pls `效果#一样
print "@str";
Log3.log
首先,我们有命令输入操作符,也叫反勾号操作符,因为它看起来象这样:
$info = `finger $user`;
一个用反勾号(技术上叫重音号)引起的字串首先进行变量替换,就象一个双引号引起的字串一样。得到的结果然后被系统当作一个命令行,而且那个命令的输出成为伪文本的值。(这是一个类似 Unix shell 的模块。)在标量环境里,返回一个包含所有输出的字串。在列表环境里,返回一列值,每行输出一个值。(你可以通过设置 $/ 来使用不同的行结束符。)
代码解释,当执行Demo.pls时,my @str = qx/perl F://Demo3.pls/;这一句会被操作系统调用并且启动Demo3.pls,然后Demo3.pls会读取log3.log中的数据。
我们主要关注的是my @str = qx/perl F://Demo3.pls/;这句中的返回值@str,我们都知道@str是一个列表环境,反勾号返回Demo3.pls中的print的打印值,当然我们也可以使用标量环境,例如,my $str = qx/perl F://Demo3.pls/;这样也可以一次性的取出所有的数据。
最后总结:反勾号返回的是命令行返回的print的数值。具体的解释看上面的说明。
2. System
l system PATHNAME LIST
l system LIST
这个函数为你执行任何系统里的程序并返回该程序的退出状态——而不是它的输出。要捕获命令行上的输出,你应该用反勾号或者 qx//。system 函数的运转非常类似 exec,只不过 system 先做一个 fork,然后在 exec 之后等待执行的程序的结束。也就是说它为你运行这个程序并且在它完成之后返回,而 exec 用新的程序代替你运行的程序,所以如果替换成功的话它从不返回。
参数的处理因参数的数目的不同而不同,就象在 exec 里描述的那样,包括判断是否调用 shell 以及你是否用声明另外一个 PATHNAME 的方法使用了该函数其他的名称。
因为 system 和反勾号阻塞 SIGINT 和 SIGQUIT,所以向那些正在这样运行的程序发送这些信号之一(比如通过一个 Control-C)时并不会中断你的主程序。但是你运行的另外一个程序的确收到这个信号。请检查 system 的返回值,判断你运行的程序是否正常退出。
@args = ("command", "arg1", "arg2");
system(@args) == 0
or die "system @args failed: $?"
返回值是和该函数通过 wait(2) 系统调用返回的一样的退出状态。在传统的语意里,要获取实际的退出值,要除以 256 或者右移 8 位。这是因为低 8 位里有一些其他的东西。(实际上是其他的两些东西。)最低七位标识杀死该进程的信号号码(如果有的话),而第八位标识该进程是否倾倒了核心。你可以通过 $?($CHILD_ERROR)来检查所有失效可能性,包括信号和核心倾倒:
$exit_value = $? >> 8;
$exit_value = $? & 127; # 或者 0x7f, 0177, 0b0111_1111
$dumped_core = $? & 128; # 或者 0x80, 0200, 0b1000_0000
如果该程序是通过系统 shell (注:定义为 /bin/sh 或者任何在你的平台上有意义的东西,但不是那些用户碰巧在某个时候用到的 shell。)运行的,这可能是因为你只有一个参数而且该参数里面有 shell 元字符,那么通常返回码受那个 shell 的怪癖和功能的影响。换句话说,在这种情况下,你可能无法获取我们前面描述了详细信息。
3. exec
o exec PATHNAME LIST
o exec LIST
exec 函数结束当前程序的运行并且执行一条外部命令并且决不返回!!!如果你希望在该命令退出之后恢复控制,那么你应该使用 system。exec 函数只有在该命令不存在以及该命令是直接执行而没有通过你的系统的命令行 shell(下面讨论)执行的时候才失败并返回假。
如果只有一个标量参数,那么 exec 检查该参数是否 shell 的元字符。如果找到元字符,那么它代表的所有参数都传递给系统的标准命令行解释器(在 Unix 里是 /bin/sh)。如果没有这样的元字符,那么该参数被分裂成单词然后直接执行,出于效率考虑,这样做绕开了所有 shell 处理的过荷。而且如果该程序没有退出,这样也给你更多错误恢复的控制。
如果在 LIST 里有多于一个参数,或者如果 LIST 是一个带有超过一个值的数组,那么就决不会使用系统的 shell。这样也绕开了 shell 对该命令的处理。在参数中是否出现元字符并不影响这个列表触发特性,这么做也是有安全考虑的程序的比较好的做法,因为它不会把自己暴露在潜在的 shell 逃逸之中。
下面的例子令当前运行的 Perl 程序用 echo 程序代替自身,然后它就打印出当前的参数列表:
exec 'echo', 'Your arguments are: ', @ARGV;
下面这个例子显示了你可以 exec 一个流水线,而不仅仅是一个程序:
exec "sort $outfile | uniq"
or die "Can't do sort/uniq: $!/n";
通常,exec 从不返回——就算它返回了,它也总是返回假,并且你应该检查 $! 找出什么东西出错了。要注意的是,在老版本的 Perl 里,exec(和 system)并不刷新你的输出缓冲,所以你需要在一个或更多个文件句柄上通过设置 $| 打开命令缓冲功能以避免在 exec 的情况下丢失输出,或者在 system 的情况下打乱了输出顺序。在 Perl 5.6 里情况大致如此。
如果你让操作系统在一个现有的进程里运行一个新的程序(比如 Perl 的 exec 函数做的这样),你要告诉系统要执行的程序在哪里,但是你也告诉了这个新的程序(通过它的第一个参数)是什么程序执行了它。习惯上,你告诉它的名字只是该程序的位置的一个拷贝,但这么做不是必须的,因为在 C 语言的级别上,有两个独立的参数。如果这个名字不是拷贝,那么你就可能看到奇怪的结果:这个新程序认为自己是以一个和它所在的实际路径名完全不同的名字运行的。通常这样对那些满腹狐疑的程序来说没什么问题,但有些程序的确关心自己的名字,并且根据自己的名字的变化会有不同的性格。比如,vi 编辑器会看看自己是作为“vi”还是作为“view”调用的。如果作为“view”,那么它就自动打开只读模式,就好象它是带着 -R 命令行选项调用的一样。
这个时候就是 exec 的可选 PATHNAME 参数起作用的地方了。从语意上来看,它放在间接对象的位置,就好象 print 和 printf 的文件句柄一样。因此,它并不需要在后面有一个对象,因为它实际上不是参数列表的一部分。(从某种意义上来说,Perl 与操作系统采取的方法正相反,它认为第一个参数是最重要的,并且如果它不同那么就让你修改路径名。)比如:
$editor = "/usr/bin/vi";
exec $editor "view", @files # 触发只读模式
or die "Couldn't execute $editor: $!/n";
和任何其他间接对象一样,你也可以用一个包含任意代码的块代替上面这个保存程序名的简单标量,这样就可以把前面这个例子简化为:
exec { "/usr/bin/vi" } "view" @files # 触发只读模式
or die "Couldn't execute $editor: $!/n";
如前所述,exec 把一个离散的参数列表当作一个它应该绕开 shell 处理的标志。不过,仍然有一个地方可能把你拌倒。exec 调用(以及 system)不能区别单个标量参数和一个只有一个元素的列表。
@args = ("echo surprise") # 只有一个元素在列表里
exec @args # 仍然可能有 shell 逃逸
or die "exec: $!"; # 因为 @args == 1
为了避免这种情况,你可以使用 PATHNAME 语法,明确地把第一个参数当路径名复制,这样就强制其他的参数解释成一个列表,即使实际上只有一个元素:
exec { $args[0] } @args # 就算是只有一个元素的列表也安全了
or die "can't exec @args: $!";
第一个没有花括弧的版本,运行 echo 程序,给它传递“surprise”做参数。第二个版本不是这样——它试图运行一个字面上叫 echo surprise 的程序,但找不到(我们希望如此),然后把 $! 设置为一个非零值以表示失败。
因为 exec 函数通常是紧跟在 fork 之后调用的,所以它假定任何原先一个 Perl 进程终止的时候要发生的事情都被忽略。在 exec 的时候,Perl 不会调用你的 END 块,也不会调用与任何对象相关的 DESTROY 方法。否则,你的子进程结束的时候会做那些你准备在父进程里做的清理工作。(我们希望在现实生活中就是如此。)
因为把 exec 当作 system 用是一个非常普遍的错误,所以如果你带着流行的 -w 命令行开关运行,或者你用了 use warnings qw(exec syntax) 用法的时候,如果 exec 后面跟着的语句不是 die,warn,或则 exit,那么 Perl 就会警告你。如果你真的想在 exec 后面跟一些其他的语句,你可以使用下面两种风格之一以避免警告:
exec ('foo) or print STDERR "couldn't exec foo: $!";
{ exec ('foo') }; print STDERR "couldn't exec foo: $!";
正如上面的第二行显示的那样,如果调用 exec 的时候是一个块里的最后一条语句,那么就可以免于警告。