perl中如何執行外部命令

shupan001發表於2011-11-21

在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 的時候是一個塊裡的最後一條語句,那麼就可以免於警告。


相關文章