2017-07-27 39 views
1

我正在嘗試使用Perl5到fork()一個子進程。子進程應該exec()另一個程序,將其STDIN重定向到一個命名管道,並且STDOUTSTDERR來記錄文件。父進程繼續以循環方式運行,使用waitpid並檢查$?重新啓動子進程,以防其死於非零退出狀態。對於exec()功能在exec()中重定向STDOUT和STDERR ...沒有shell

Perl文檔說:

如果在列表中有多個說法,這叫execvp(3)在列表中的參數。如果在LIST中只有一個元素,則檢查參數是否爲shell元字符,如果有,則將整個參數傳遞到系統的命令解析器(在Unix平臺上爲/bin/sh -c,但在其他平臺上有所不同)。如果參數中沒有shell元字符,它將被拆分爲單詞並直接傳遞給execvp,這樣更有效。例子:

exec '/bin/echo', 'Your arguments are: ', @ARGV; 
exec "sort $outfile | uniq"; 

這聽起來很酷,我想沒有一箇中介shell中運行我的外部程序,如以下示例所示。不幸的是,我無法將其與輸出重定向結合起來(如/bin/foo > /tmp/stdout)。

換句話說,這不起作用:

exec ('/bin/ls', '/etc', '>/tmp/stdout'); 

所以,我的問題是:我怎麼重定向STD*文件爲我的子命令,而無需使用的殼呢?

回答

5

通過<>重定向是shell功能,這就是爲什麼它不起作用的原因。你基本上調用/bin/ls並通過>/tmp/stdout只是另一種說法,通過echo更換命令時,這是很容易看到:

exec ('/bin/echo', '/etc', '>/tmp/stdout'); 

打印:

/etc >/tmp/stdout 

通常情況下,你的shell(/bin/sh)會解析該命令發現重定向嘗試,打開正確的文件,並修剪參數列表進入/bin/echo

但是 - 程序開始exec()(或system())將繼承其調用進程的STDINSTDOUTSTDERR文件。因此,要處理這個正確的方法是

  • 接近每個特殊的文件句柄,
  • 重新打開它們,你需要的日誌文件指指點點,
  • 最後調用exec()啓動程序。

重寫你上面的示例代碼,這工作得很好:

close STDOUT; 
open (STDOUT, '>', '/tmp/stdout'); 
exec ('/bin/ls', '/etc'); 

...或者,使用perldoc建議的間接對象語法:

close STDOUT; 
open (STDOUT, '>', '/tmp/stdout'); 
exec { '/bin/ls' } ('ls', '/etc'); 

(事實上,根據到文檔,這個最後的語法是避免在Windows中實例化一個shell的唯一可靠方法。)

4

The follo wing shell命令告訴shell啓動/bin/ls,其中/etc用於其STDOUT重定向的參數。

/bin/ls /etc >/tmp/stdout 

在另一方面,下面的Perl語句告訴Perl,以/bin/ls以取代目前的方案,/etc>/tmp/stdout的參數。

exec('/bin/ls', '/etc', '>/tmp/stdout'); 

你沒有告訴Perl重定向STDOUT!請記住exec不會啓動新進程,因此如果更改子進程的fd 1,則會影響在同一進程中運行ls

但是比起剛剛殺青這一問題(如格雷格·肯尼迪那樣),但留下您的其他問題的完整(如誤報發射lsls錯誤的無能),我會告訴你解決這些問題的所有:

use IPC::Open3 qw(open3); 

my $stdout = ''; 
{ 
    # open3 will close the handle used as the child's STDIN. 
    # open3 has issues with lexical file handles. 
    open(local *CHILD_STDIN, '<', '/dev/null') or die $!; 

    my $pid = open3('<&CHILD_STDIN', local *CHILD_STDOUT, '>&STDERR', 
     '/bin/ls', '/etc'); 

    while (my $line = <CHILD_STDOUT>) { 
     $stdout .= $line; 
    } 

    waitpid($pid, 0); 
} 

雖然這爲您節省了一百行代碼,但open3還是相當低級的。 (如果您必須處理兩個管道,則會遇到問題。)我反而推薦使用IPC::Run3(更簡單)或IPC::Run(更靈活)。

use IPC::Run3 qw(run3); 
run3([ '/bin/ls', '/etc' ], \undef, \my $stdout); 

use IPC::Run qw(run); 
run([ '/bin/ls', '/etc' ], \undef, \my $stdout); 
相關問題