system函數能夠直接讓perl調用操做系統中的命令並執行。html
例如:shell
#!/usr/bin/perl system 'date +"%F %T"'; system 'echo hello world'; system 'echo',"hello","world";
執行結果:數組
2018-06-21 18:32:50 hello world hello world
注意system的參數能夠被單個引號包圍,也能夠用多個引號分隔成多個參數,若是分隔開,system會將它們用空格的方式鏈接起來。bash
另外,上面使用了單引號、雙引號,都能正確執行,但注意,雙引號會解析perl中的特殊符號。例如:併發
$myname="Malongshuai"; system "echo $myname"; # 輸出:Malongshuai system 'echo $USER'; # 輸出當前登陸的用戶:root
可見,雙引號中的變量$myname
被perl解析了,而單引號中的變量$USER
不被perl解析,perl將其交給bash,由shell負責解析,因此會輸出當前用戶名。app
在system中,還可使用shell的重定向、管道等功能。函數
$myname="Malongshuai"; system "echo $myname >/tmp/a.txt"; print "==============================\n"; system "cat <1.plx"; print "==============================\n"; system 'find . -type f -name "*.pl" -print0 | xargs -0 -i ls -l {}'; system 'sleep 30 &';
system有兩種語法:spa
system LIST system PROGRAM LIST
這裏忽略第二種,由於它是一種以欺騙的防止執行命令的:LIST中的第一個參數做爲命令,但欺騙本身說本身執行的是PROGRAM命令。操作系統
下面將詳細討論第一種語法。命令行
在討論以前,先解釋一下bash命令行執行命令時的引號解析問題。例如:
awk -F ":" 'NR<=3{username=$1;print "username:",username}' /etc/passwd find /root -type f -name "*.log"
shell命令行中執行命令時,包含兩部分:一個是程序名,一個是程序的參數部分。在真正執行以前,shell的詞法分析行爲會解析程序名稱、參數部分。但有些時候命令行中會使用一些shell的特殊符號來實現shell的特殊功能。例如shell的星號通配符*
、管道功能|
、重定向功能> < >> << <<<
、命令替換功能$()
等。但有些程序自身,其用法規則中可能也會使用一些特殊符號(如find -name "*.log"
的星號),這會和shell的特殊符號衝突。因爲shell的解析行爲在命令執行以前,爲了保留特殊符號給程序自身來解釋,須要使用引號來保護這些特殊符號以免被shell解析。
正如上面awk中的":"
和'{}'
以及find中的"*.log"
,它們都使用引號包圍特殊符號,使得這些符號"逃過"shell的解析過程,從而讓程序自身解析。
更通俗一點,若是不是執行命令要依賴於shell環境的存在,若是能直接在最純粹的環境中執行命令,那麼特殊符號是無需加引號保護的。例如,awk若是能脫離shell單獨執行,下面的第一條命令纔是正確的,第二條命令倒是錯誤的。
awk -F : NR<=3{username=$1;print "username:",username} /etc/passwd awk -F ":" 'NR<=3{username=$1;print "username:",username}' /etc/passwd
system LIST
中的system要求的是列表上下文參數LIST,就像print函數同樣。因此,當LIST是一個標量字符串,它其實也是一個列表,只不過是只包含一個元素的列表。
例如:
system 'find /perlapp -type f -name "*.pl"'; # 是一個標量字符串構成的LIST system "ls","-lh","/root"; # 包含多元素的列表參數 @cmd_arg=qw(-lh /root); system "ls",@cmd_arg; # 包含多元素的列表參數
對於system LIST
語法,perl在執行LIST中的命令以前,會先檢查LIST:
* ? []
,shell中的重定向< > >> <<< <<
,shell中的管道|
,shell的後臺任務符號&
,命令替換$()
等等):
/bin/sh -c STRING
的方式來執行LIST,其中LIST就是STRING部分execvp
系統函數(man execvp)來執行,它的效率比unix的system()更高注:bash -c STRING
的c選項會從STRING中讀取命令並執行。
幾個示例:
@arg1=qw(-lh /root); system "ls",@arg1; # 1.可正確執行 system "ls -lh /root/*.log"; # 2.可正確執行 @arg2=qw(-lh /root/*.log); system "ls",@arg2; # 3.將執行失敗 system "ls -lh","/root"; # 4.執行失敗,更準確的是spawn過程就失敗 system "ls","-lh /root"; # 5.執行失敗 system "ls","-l -h","/root"; # 6.執行失敗
上面第二個system能執行成功,而第三個system會執行失敗,是由於:
/bin/sh -c ls -lh /root/*.log
命令第四個system也執行失敗,由於不止一個參數,因而取第一個參數做爲命令來spawn新的進程,但這第一個參數是ls -lh
總體,而不是ls,這等價於"ls -lh" /root
,因此spawn失敗,找不到這個命令。
第5個system執行失敗,由於"-lh /root"做爲列表的第二個元素,它是一個總體。因此它等價於ls "-lh /root"
,這顯然是錯誤的。
第6個system執行失敗,緣由同上。
因此能夠稍微總結下,若是使用多個參數的system,每一個本來在unix shell命令行中須要空格分開的選項和參數,都須要單獨做爲列表的獨立元素。
正如:
system "ls","-lh","/root"; @args=qw(-lh /root); system "ls",@args;
更復雜一點的示例:
@cmd_arg1=qw(/perlapp -type f -name *.pl); system "/usr/bin/find",@cmd_arg1; # 1.正確 @cmd_arg2=qw(/perlapp -type f -name "*.pl"); # 加上了雙引號 system "/usr/bin/find",@cmd_arg2; # 2.錯誤 $prog="/usr/bin/awk"; @arg3=("-F",":",'NR<=3{username=$1;print "username: ",username}','/etc/passwd'); system $prog,@arg3; # 3.正確
上面第二個system中,是多參數的system,不會調用shell來解析,而*.pl
使用了引號包圍,但對於find來講,引號不可識別的字符,它會將其看成要查找文件名的一部分,因此執行失敗。之因此在shell命令中的find要加上引號,是爲了防止*
被shell解析。
第三個system中,沒有使用qw()
的方式生成列表,由於awk的表達式部分存在空格,使用qw生成列表的方式沒法保留空格,因此這裏採用最原始的生成列表的形式。固然,也能夠實現split來生成:
@arg3=split /%/,q(-F%:%NR<=3{username=$1;print "username: ",username}%/etc/passwd);
關於使用單個參數的system仍是使用多參數的system。
若是對shell解析熟悉,使用單個參數比較好,能比較直接地使用shell相關的功能(重定向、管道等)。但使用單個參數,引號引用和轉義引用方面畢竟比較複雜,容易出錯,可能須要屢次調試。
多個參數也有好處,不用擔憂太多引號問題,但卻失去了使用shell功能的能力。若是想要在多參數的system中使用管道、重定向等特殊符號帶來的shell功能,能夠將'/bin/sh','-c'
做爲system的前兩個參數,使得system強制調用shell來執行命令。
/bin/sh -c STRING
執行命令的方式是shell從STRING中讀取命令來執行。因此,爲了保證完整性,STRING部分建議全都包含在一個引號中。例如:
shell> bash -c 'find . -type f -name "*.pl" | xargs ls -l'
回到system的調用/bin/sh -c
的用法,例如:
$arg1=q(find . -type f -name "*.pl" -print0); # 1 $arg2=q( | xargs -0 -i ls -l {}); # 2 system '/bin/sh','-c',"$arg1 $arg2"; # 3
上面3行,每行都有關鍵點:
*.pl
仍是要加上引號包圍/bin/sh
和-c
$arg1
中的命令,甚至有時候會由於$arg1
不完整或有多餘字符而報錯看上去規則不少,並且書寫必須十分規範,失之毫釐,結果將差之千里。如非必須,還不如直接寫成單個參數的system。例如,上面的3行等價於:
system '/bin/sh','-c','find . -type f -name "*.pl -print0 | xargs -0 -i ls -l {}"'; system 'find . -type f -name "*.pl -print0 | xargs -0 -i ls -l {}"';
system執行命令時的返回值爲$?
,它和bash的$?
不太一致。當最後一個管道關閉時、反引號執行命令、wait()或waitpid()成功執行時或system(),都會返回$?
。在Perl中,$?
包含兩部分共16字節,低8位是信號信息,高8位纔是所執行的命令的狀態碼。也就是說,perl中的$?
的高8位纔對應bash中的$?
。
所以,要獲取退出狀態碼,須要使用$?>>8
。
#!/usr/bin/perl system '(exit 4)'; print $?>>8,"\n"; # 輸出4
若是,想要直接在執行的命令上判斷命令是否正確執行,而後決定是否die。能夠在system的前面加上一個!
取反。這是由於在shell中,非0的狀態碼錶示命令錯誤執行,0狀態碼才表示執行正確。這和perl的布爾值正好相反,因此加上感嘆號取反:
!system '(exit 4)' or die "command return error num: ",$?>>8;
須要注意,這裏不能使用$!
,在perl中有多種不一樣的錯誤捕獲變量,$!
捕獲的是perl在發起系統調用層面的錯誤,而system執行的命令的錯誤發生在命令執行時。對於system函數來講,perl只要成功執行system,無論裏面的命令是否執行成功,perl發起的系統調用都已經結束了。
關於如何獲取信號信息,參見官方手冊。或者:
The 「low」 octet combines several things. The highest bit notes if a core dump happened.The hexadecimal and binary representations (recall them from Chapter 2) can help mask out the parts you don’t want: my $low_octet = $return_value & 0xFF; # mask out high octet my $dumped_core = $low_octet & 0b1_0000000; # 128 my $signal_number = $low_octet & 0b0111_1111; # 0x7f, or 127
在Perl中,除了system,還有exec、fork、pipe、IPC等進程操做方式,它們的細節,均可man system、man exec、man fork等等來獲取。在後文會一一解釋,此處先解釋system執行的細節。
在執行到system時,system會直接拷貝一份當前perl進程(稱爲子進程),而後本身進入睡眠態,並使用waitpid()等待子進程執行完畢。
unix系統中的system()用來調用一個shell解釋器來執行命令,用來啓動一個新的程序,是fork+execl("/bin/sh -c COMMAND")+waitpid()
的結合,由於多一層shell的調用,效率相比於fork+exec來講較低,且須要waitpid()的等待,沒法控制子進程也沒法併發。
perl的system()和unix的system()不太同樣,多了一層判斷來決定是使用fork+execl("/bin/sh -c COMMAND")+waitpid()
仍是直接使用fork+execvp(COMMAND)+waitpid()
。
由於是直接拷貝的,因此子進程初始時和perl父進程是徹底一致的。因此,標準輸入(STDIN)、標準輸出(STDOUT)、標準錯誤輸出(STDERR)都是和父進程共享的。
system 'read -p "enter your name: " name;echo "your name is: " $name';
在system中的命令執行以前,perl首先會解析system的參數列表,關於解析的方式,在前文已經詳細解釋過了。若是命令是直接執行的,則命令所在進程就是perl進程的子進程。若是命令須要經過經過調用/bin/sh -c
來執行,則shell進程是子進程,真正執行的命令則是孫進程(grandchild)或者是下一代。
例如,在參數中放入shell的for循環,由於這是bash內置屬性,它會直接在當前bash進程中完成。
system 'for i in {1..10};do echo $i;done';
這些內容比較複雜,可參見:bash內置命令的特殊性,後臺任務的"本質"
當命令執行完畢後,將回到perl進程,perl進程會執行wait(),而後結束system。
exec和system除了一種行爲以外,其它用法和system徹底一致。exec和system的區別之處在於:
因爲exec執行完命令後,當即退出當前perl進程,因此命令執行的正確與否,沒法被捕獲。但若是exec啓動待執行命令過程就出錯了,這屬於perl的系統調用過程出錯,可使用$!
捕獲。
exec 'date'; die "date couldn't run: $!";
通常來講,不多直接使用exec,而是fork+exec同時使用。關於fork,見perl和操做系統交互:fork。
perl中的system()和exec()執行命令時,都是直接執行命令,並將執行結果輸出到某個地方(好比屏幕)。可是反引號(`COMMAND`
)能夠將執行的結果插入到某個地方或者進行賦值,而不是直接輸出。就像shell中的反引號同樣。
例如,將操做系統中date命令的執行結果賦值給一個變量。
#!/usr/bin/perl my $date=`date +"%F %T"`; print $date;
因爲反引號是將命令的輸出結果捕獲起來並插入到某個地方或賦值,若是反引號單獨成一個語句,也便是在空上下文(void)中,它的結果會丟棄。通常來講,這是畫蛇添足或者是浪費的行爲,除非是要經過執行命令臨時作出某些設置:
`date +"%F %T"`; # 命令的結果將直接丟棄
qx()和反引號執行命令是同樣的,只不過寫法不一樣,使得某些特殊符號的處理變得更容易,就像shell中也有一個$()
的方式替換` `
。特別地,因爲perl反引號是以雙引號的方式解釋反引號內部的內容,若是反引號中間有perl能夠解釋的特殊符號,就會被perl先解釋,再傳遞給shell去執行。若是使用qx並使用單引號做爲定界符(即qx'COMMAND'
),perl將使用單引號的方式去解釋COMMAND,使得perl再也不解釋一些特殊符號。
例以下面的例子中,在shell環境中導出了一個環境變量name,值爲"Gaoxiaofang",而perl程序內部正好也定義了一個變量$name
,這時使用反引號`COMMAND`
和qx'COMMAND'
就再也不同樣。
如下是shell中執行的命令:
export name="Gaoxiaofang"
如下是perl程序中的內容
#!/usr/bin/perl $name="Malongshuai"; my $new_name1=`echo $name`; print $new_name1; # 輸出Malongshuai my $new_name2=qx'echo $name'; print $new_name2; # 輸出Gaoxiaofang
但須要注意的是,shell反引號作的命令替換,因爲經常使用來插入到某個表達式中間,因此shell在反引號執行完畢後會自動移除換行符,除非使用雙引號包圍反引號。而perl則有所不一樣,perl總會保證所執行即所得,perl的反引號會保留每個換行符。
通常來講,在perl中使用反引號的時候,都會使用chomp去除最後一個換行符。
chomp(my $date=`date +"%F %T"`); print $date;
若是在列表上下文中使用反引號,則反引號中命令的每一行輸出都會保存爲列表的元素。
my @new_name=qx'who'; print "$new_name[0]";
一樣地,能夠將反引號放進foreach,由於foreach的迭代目標正是一個列表:
foreach (`cat /etc/passwd`){ print $_ if m%bin/bash%; }