從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。
看例子:
-
#!/usr/bin/env python
-
# coding: utf-8
-
# yc@2013/04/28
-
-
import subprocess
-
-
def test(size):
-
print 'start'
-
-
cmd = 'dd if=/dev/urandom bs=1 count=%d 2>/dev/null' % size
-
p = subprocess.Popen(args=cmd, shell= True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, close_fds=True)
-
#p.communicate()
-
p.wait()
-
-
print 'end'
-
-
# 64KB
-
test( 64 * 1024)
-
-
# 64KB + 1B
-
test( 64 * 1024 + 1)
首先測試輸出為 64KB 大小的情況。使用 dd 產生了正好 64KB 的標准輸出,由 subprocess.Popen
調用,然后使用 wait()
等待 dd
調用結束。可以看到正確的 start
和 end
輸出;然后測試比 64KB 多的情況,這種情況下只輸出了 start
,也就是說程序執行卡在了 p.wait()
上,程序死鎖。具體輸出如下:
-
start
-
end
-
start
那死鎖問題如何避免呢?官方文檔里推薦使用 Popen.communicate()
。這個方法會把輸出放在內存,而不是管道里,所以這時候上限就和內存大小有關了,一般不會有問題。而且如果要獲得程序返回值,可以在調用 Popen.communicate()
之后取 Popen.returncode
的值。
結論:如果使用 subprocess.Popen
,就不使用 Popen.wait()
,而使用 Popen.communicate()
來等待外部程序執行結束。