python中的subprocess.Popen()使用詳解---以及注意的問題(死鎖)


從python2.4版本開始,可以用subprocess這個模塊來產生子進程,並連接到子進程的標准輸入/輸出/錯誤中去,還可以得到子進程的返回值。

subprocess意在替代其他幾個老的模塊或者函數,比如:os.system os.spawn* os.popen* popen2.* commands.*

一、subprocess.Popen

subprocess模塊定義了一個類: Popen

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class subprocess.Popen( args,
   bufsize = 0 ,
   executable = None ,
   stdin = None ,
   stdout = None ,
   stderr = None ,
   preexec_fn = None ,
   close_fds = False ,
   shell = False ,
   cwd = None ,
   env = None ,
   universal_newlines = False ,
   startupinfo = None ,
   creationflags = 0 )

各參數含義如下:

args:

args參數。可以是一個字符串,可以是一個包含程序參數的列表。要執行的程序一般就是這個列表的第一項,或者是字符串本身。

?
1
2
subprocess.Popen([ "cat" , "test.txt" ])
subprocess.Popen( "cat test.txt" )

這兩個之中,后者將不會工作。因為如果是一個字符串的話,必須是程序的路徑才可以。(考慮unix的api函數exec,接受的是字符串列表)但是下面的可以工作

?
1
subprocess.Popen( "cat test.txt" , shell = True )

這是因為它相當於

?
1
subprocess.Popen([ "/bin/sh" , "-c" , "cat test.txt" ])

在*nix下,當shell=False(默認)時,Popen使用os.execvp()來執行子程序。args一般要是一個【列表】。如果args是個字符串的話,會被當做是可執行文件的路徑,這樣就不能傳入任何參數了。

注意:

shlex.split()可以被用於序列化復雜的命令參數,比如:

?
1
2
3
4
5
6
7
8
9
>>> shlex.split( 'ls ps top grep pkill' )
[ 'ls' , 'ps' , 'top' , 'grep' , 'pkill' ]
>>> import shlex, subprocess
>>>command_line = raw_input ()
/ bin / cat - input test.txt - output "diege.txt" - cmd "echo '$MONEY'"
>>>args = shlex.split(command_line)
>>> print args
[ '/bin/cat' , '-input' , 'test.txt' , '-output' , 'diege.txt' , '-cmd' , "echo '$MONEY'" ]
>>>p = subprocess.Popen(args)

可以看到,空格分隔的選項(如-input)和參數(如test.txt)會被分割為列表里獨立的項,但引號里的或者轉義過的空格不在此列。這也有點像大多數shell的行為。

在linux下,當shell=True時,如果arg是個字符串,就使用shell來解釋執行這個字符串。如果args是個列表,則第一項被視為命令,其余的都視為是給shell本身的參數。也就是說,等效於:

?
1
subprocess.Popen([ '/bin/sh' , '-c' , args[ 0 ], args[ 1 ], ...])

在Windows下,下面的卻又是可以工作的

?
1
2
subprocess.Popen([ "notepad.exe" , "test.txt" ])
subprocess.Popen( "notepad.exe test.txt" )

這是由於windows下的api函數CreateProcess接受的是一個字符串。即使是列表形式的參數,也需要先合並成字符串再傳遞給api函數

?
1
subprocess.Popen( "notepad.exe test.txt" shell = True )

等同於

?
1
subprocess.Popen( "cmd.exe /C " + "notepad.exe test.txt" shell = True

bufsize參數:

如果指定了bufsize參數作用就和內建函數open()一樣:0表示不緩沖,1表示行緩沖,其他正數表示近似的緩沖區字節數,負數表示使用系統默認值。默認是0。

executable參數:

指定要執行的程序。它很少會被用到:一般程序可以由args 參數指定。如果shell=True ,executable 可以用於指定用哪個shell來執行(比如bash、csh、zsh等)。*nix下,默認是 /bin/sh ,windows下,就是環境變量 COMSPEC 的值。windows下,只有當你要執行的命令確實是shell內建命令(比如dir ,copy 等)時,你才需要指定shell=True ,而當你要執行一個基於命令行的批處理腳本的時候,不需要指定此項。

stdin stdout和stderr:

stdin stdout和stderr,分別表示子程序的標准輸入、標准輸出和標准錯誤。可選的值有PIPE或者一個有效的文件描述符(其實是個正整數)或者一個文件對象,還有None。如果是PIPE,則表示需要創建一個新的管道,如果是None,不會做任何重定向工作,子進程的文件描述符會繼承父進程的。另外,stderr的值還可以是STDOUT,表示子進程的標准錯誤也輸出到標准輸出。

preexec_fn參數:

如果把preexec_fn設置為一個可調用的對象(比如函數),就會在子進程被執行前被調用。(僅限*nix)

close_fds參數:

如果把close_fds設置成True,*nix下會在開子進程前把除了0、1、2以外的文件描述符都先關閉。在 Windows下也不會繼承其他文件描述符。

shell參數:

如果把shell設置成True,指定的命令會在shell里解釋執行。

cwd參數:

如果cwd不是None,則會把cwd做為子程序的當前目錄。注意,並不會把該目錄做為可執行文件的搜索目錄,所以不要把程序文件所在目錄設置為cwd 。

env參數:

如果env不是None,則子程序的環境變量由env的值來設置,而不是默認那樣繼承父進程的環境變量。注意,即使你只在env里定義了某一個環境變量的值,也會阻止子程序得到其他的父進程的環境變量(也就是說,如果env里只有1項,那么子進程的環境變量就只有1個了)。例如:

?
1
2
3
4
>>> subprocess.Popen( 'env' , env = { 'test' : '123' , 'testtext' : 'zzz' })
test = 123
<subprocess.Popen object at 0x2870ad2c >
testtext = zzz

universal_newlines參數:

如果把universal_newlines 設置成True,則子進程的stdout和stderr被視為文本對象,並且不管是*nix的行結束符('/n'),還是老mac格式的行結束符('/r' ),還是windows 格式的行結束符('/r/n' )都將被視為 '/n' 。

startupinfo和creationflags參數:

如果指定了startupinfo和creationflags,將會被傳遞給后面的CreateProcess()函數,用於指定子程序的各種其他屬性,比如主窗口樣式或者是子進程的優先級等。(僅限Windows)

二、subprocess.PIPE

?
1
subprocess.PIPE

一個可以被用於Popen的stdin 、stdout 和stderr 3個參數的特輸值,表示需要創建一個新的管道。

?
1
subprocess.STDOUT

一個可以被用於Popen的stderr參數的輸出值,表示子程序的標准錯誤匯合到標准輸出。

實例:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
>>>p = subprocess.Popen( "df -h" ,shell = True ,stdout = subprocess.PIPE)
>>>out = p.stdout.readlines()
>>>out
[b 'Filesystem  Size Used Avail Capacity Mounted on\n' , b '/dev/ad0s1a 713M 313M 343M 48% /\n' , b 'devfs   1.0K 1.0K  0B 100% /dev\n' , b '/dev/ad0s1e 514M 2.1M 471M  0% /tmp\n' , b '/dev/ad0s1f 4.3G 2.5G 1.4G 64% /usr\n' , b '/dev/ad0s1d 2.0G 121M 1.7G  6% /var\n'
>>> for line in out:
...  print line.strip()
...
Filesystem  Size Used Avail Capacity Mounted on
/ dev / ad0s1a 713M 313M 343M 48 % /
devfs   1.0K 1.0K  0B 100 % / dev
/ dev / ad0s1e 514M 2.1M 471M  0 % / tmp
/ dev / ad0s1f 4.3G 2.5G 1.4G 64 % / usr
/ dev / ad0s1d 2.0G 121M 1.7G  6 % / var

stdout可以使用read(),readline(),readlines()等方法

三、方便的函數

1、subprocess.call

?
1
subprocess.call ( * popenargs , * * kwargs )

執行命令,並等待命令結束,再返回子進程的返回值。參數同Popen,查看/usr/lib/python2.7/subprocess.py

去掉文檔,其實是這樣的:

?
1
2
3
def call( * popenargs, * * kwargs):
  return Popen( * popenargs, * * kwargs).wait()
>>> subprocess.call( 'ifconfig' ,shell = True )

2、subprocess.check_call

?
1
subprocess.check_call ( * popenargs , * * kwargs )

執行上面的call命令,並檢查返回值,如果子進程返回非0,則會拋出CalledProcessError異常,這個異常會有個returncode

屬性,記錄子進程的返回值。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def check_call( * popenargs, * * kwargs):
  retcode = call( * popenargs, * * kwargs)
  if retcode:
   cmd = kwargs.get( "args" )
   raise CalledProcessError(retcode, cmd)
  return 0
>>> subprocess.check_call( 'ifconfig' )
>>> subprocess.call( 'noifconfig' )
Traceback (most recent call last):
  File "<stdin>" , line 1 , in <module>
  File "/usr/local/lib/python2.7/subprocess.py" , line 493 , in call
  return Popen( * popenargs, * * kwargs).wait()
  File "/usr/local/lib/python2.7/subprocess.py" , line 679 , in __init__
  errread, errwrite)
  File "/usr/local/lib/python2.7/subprocess.py" , line 1228 , in _execute_child
  raise child_exception
OSError: [Errno 2 ] No such file or directory

異常子進程里拋出的異常,會在父進程中再次拋出。並且,異常會有個叫child_traceback的額外屬性,這是個包含子進程錯誤traceback信息的字符串。遇到最多的錯誤回是 OSError,比如執行了一個並不存在的子程序就會產生OSError。另外,如果使用錯誤的參數調用Popen,會拋出ValueError。當子程序返回非0時,check_call()還會產生CalledProcessError 異常。

安全性不像其他的popen函數,本函數不會調用/bin/sh來解釋命令,也就是說,命令中的每一個字符都會被安全地傳遞到子進程里。

3、check_output

?
1
2
3
4
5
6
7
8
9
10
check_output()執行程序,並返回其標准輸出.
def check_output( * popenargs, * * kwargs):
  process = Popen( * popenargs, stdout = PIPE, * * kwargs)
  output, unused_err = process.communicate()
  retcode = process.poll()
  if retcode:
   cmd = kwargs.get( "args" )
   raise CalledProcessError(retcode, cmd, output = output)
  return output
p = subprocess.check_output( 'ifconfig' )

結果是所有行/n分割的一個字符串可以直接print出來 這里開始

4、Popen對象

產生對象

?
1
2
p = subprocess.Popen( "df -h" ,shell = True ,stdout = subprocess.PIPE)
>>> dir (p)

Popen對象有以下方法:

?
1
Popen.poll()

檢查子進程是否已結束,設置並返回returncode屬性。

?
1
2
3
4
>>> p.poll()
0
 
Popen.wait()

等待子進程結束,設置並返回returncode屬性。

?
1
2
>>> p.wait()
0

注意: 如果子進程輸出了大量數據到stdout或者stderr的管道,並達到了系統pipe的緩存大小的話,子進程會等待父進程讀取管道,而父進程此時正wait着的話,將會產生傳說中的死鎖,后果是非常嚴重滴。建議使用communicate() 來避免這種情況的發生。

?
1
Popen.communicate( input = None )

和子進程交互:發送數據到stdin,並從stdout和stderr讀數據,直到收到EOF。等待子進程結束。可選的input如有有的話,要為字符串類型。

此函數返回一個元組: (stdoutdata , stderrdata ) 。

注意,要給子進程的stdin發送數據,則Popen的時候,stdin要為PIPE;同理,要可以接收數據的話,stdout或者stderr也要為PIPE。

?
1
2
p1 = subprocess.Popen( 'cat /etc/passwd' ,shell = True ,stdin = subprocess.PIPE,stdout = subprocess.PIPE)   
>>> p2 = subprocess.Popen( 'grep 0:0' ,shell = True ,stdin = p1.stdout,stdout = subprocess.PIPE)

注意:讀到的數據會被緩存在內存里,所以數據量非常大的時候要小心了。

?
1
2
3
4
>>> p.communicate() 
(b 'Filesystem  Size Used Avail Capacity Mounted on\n/dev/ad0s1a 713M 313M 343M 48% /\ndevfs   1.0K 1.0K  0B 100% /dev\n/dev/ad0s1e 514M 2.1M 471M  0% /tmp\n/dev/ad0s1f 4.3G 2.5G 1.4G 64% /usr\n/dev/ad0s1d 2.0G 121M 1.7G  6% /var\n' , None )
 
Popen.send_signal(signal)

給子進程發送signal信號。

注意:windows下目前只支持發送SIGTERM,等效於下面的terminate() 。

?
1
Popen.terminate()

停止子進程。Posix下是發送SIGTERM信號。windows下是調用TerminateProcess()這個API。

?
1
Popen.kill()

殺死子進程。Posix下是發送SIGKILL信號。windows下和terminate() 無異。

?
1
Popen.stdin

如果stdin 參數是PIPE,此屬性就是一個文件對象,否則為None 。

?
1
Popen.stdout

如果stdout參數是PIPE,此屬性就是一個文件對象,否則為None 。

?
1
Popen.stderr

如果stderr 參數是PIPE,此屬性就是一個文件對象,否則為None 。

?
1
Popen.pid

子進程的進程號。注意,如果shell 參數為True,這屬性指的是子shell的進程號。

?
1
2
3
4
>>> p.pid 
22303
 
Popen.returncode

子程序的返回值,由poll()或者wait()設置,間接地也由communicate()設置。

如果為None,表示子進程還沒終止。

如果為負數-N的話,表示子進程被N號信號終止。(僅限*nux)

用subprocess來代替其他函數都可以用subprocess來完成,我們假定是用 “from subprocess import *” 來導入模塊的:

代替shell命令:

?
1
p = `ls - l`

等效於

?
1
p = Popen([ 'ls' , '-l' ],stdout = PIPE).communicate()[ 0 ]

代替shell管道:

?
1
p = `dmesg | grep cpu`

等效於

?
1
2
3
4
5
6
7
8
9
10
p1 = Popen([ 'dmesg' ],stdout = PIPE)
p2 = Popen([ 'grep' , 'cpu' ],stdin = p1.stdout,stdout = PIPE)
output = p2.communicate()[ 0 ]
output
cpu0: <ACPI CPU> on acpi0\nacpi_throttle0: <ACPI CPU Throttling> on cpu0\n
 
>>> p1 = subprocess.Popen( 'cat /etc/passwd' ,shell = True ,stdout = subprocess.PIPE)   
>>> p2 = subprocess.Popen( 'grep 0:0' ,shell = True ,stdin = p1.stdout,stdout = subprocess.PIPE)
>>> p3 = subprocess.Popen( "cut -d ':' -f 7" ,shell = True ,stdin = p2.stdout,stdout = subprocess.PIPE)
>>> print p3.stdout.read()

代替os.system()

?
1
lsl = os.system( 'ls ' + '-l' )

這個是一個返回狀態

等效於

?
1
2
p = Popen( 'ls -l' , shell = True )
lsl = os.waitpid(p.pid, 0 )[ 1 ]

注意:

通常並不需要用shell來調用程序。用subprocess可以更方便地得到子程序的返回值。

其實,更真實的替換是:

?
1
2
3
4
5
6
7
8
try :
  retcode = call(“mycmd” + ” myarg”, shell = True )
if retcode < 0 :
  print >>sys.stderr, “Child was terminated by signal”, - retcode
else :
  print >>sys.stderr, “Child returned”, retcode
  except OSError, e:
  print >>sys.stderr, “Execution failed:”, e

代替os.spawn系列

P_NOWAIT的例子

?
1
pid = os.spawnlp(os.P_NOWAIT, “ / bin / mycmd”, “mycmd”, “myarg”)

等效於

?
1
pid = Popen([ "/bin/mycmd" , "myarg" ]).pid

P_WAIT的例子

?
1
retcode = os.spawnlp(os.P_WAIT, “ / bin / mycmd”, “mycmd”, “myarg”)

等效於

?
1
retcode = call([ "/bin/mycmd" , "myarg" ])

返回值處理:

?
1
2
3
4
5
pipe = os.popen(“cmd”, ‘w')
...
rc = pipe.close()
if rc ! = None and rc % 256 :
  print “There were some errors”

等效於

?
1
2
3
4
5
process = Popen(“cmd”, ‘w', shell = True , stdin = PIPE)
...
process.stdin.close()
if process.wait() ! = 0 :
  print “There were some errors”

以上這篇python中的subprocess.Popen()使用詳解

 

 

 

 

今天遇到的一個問題。簡單說就是,使用 subprocess 模塊的 Popen 調用外部程序,如果 stdout或 stderr 參數是 pipe,並且程序輸出超過操作系統的 pipe size時,如果使用 Popen.wait() 方式等待程序結束獲取返回值,會導致死鎖,程序卡在 wait() 調用上。

ulimit -a 看到的 pipe size 是 4KB,那只是每頁的大小,查詢得知 linux 默認的 pipe size 是 64KB

看例子:

  1.  
    #!/usr/bin/env python
  2.  
    # coding: utf-8
  3.  
    # yc@2013/04/28
  4.  
     
  5.  
    import subprocess
  6.  
     
  7.  
    def test(size):
  8.  
    print 'start'
  9.  
     
  10.  
    cmd = 'dd if=/dev/urandom bs=1 count=%d 2>/dev/null' % size
  11.  
    p = subprocess.Popen(args=cmd, shell= True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, close_fds=True)
  12.  
    #p.communicate()
  13.  
    p.wait()
  14.  
     
  15.  
    print 'end'
  16.  
     
  17.  
    # 64KB
  18.  
    test( 64 * 1024)
  19.  
     
  20.  
    # 64KB + 1B
  21.  
    test( 64 * 1024 + 1)

首先測試輸出為 64KB 大小的情況。使用 dd 產生了正好 64KB 的標准輸出,由 subprocess.Popen調用,然后使用 wait() 等待 dd 調用結束。可以看到正確的 start 和 end 輸出;然后測試比 64KB 多的情況,這種情況下只輸出了 start,也就是說程序執行卡在了 p.wait() 上,程序死鎖。具體輸出如下:

  1.  
    start
  2.  
    end
  3.  
    start

那死鎖問題如何避免呢?官方文檔里推薦使用 Popen.communicate()。這個方法會把輸出放在內存,而不是管道里,所以這時候上限就和內存大小有關了,一般不會有問題。而且如果要獲得程序返回值,可以在調用 Popen.communicate() 之后取 Popen.returncode 的值。

結論:如果使用 subprocess.Popen,就不使用 Popen.wait(),而使用 Popen.communicate() 來等待外部程序執行結束。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM