1. 程式人生 > >python的subprocess:子程式呼叫(呼叫執行其他命令);獲取子程式腳本當前路徑問題

python的subprocess:子程式呼叫(呼叫執行其他命令);獲取子程式腳本當前路徑問題

python當前程序可以呼叫子程序,子程序可以執行其他命令,如shell,python,java,c...

而呼叫子程序方法有

os模組

參見:http://blog.csdn.net/longshenlmj/article/details/8331526

而提高版是 subprocess模組,類似os的部分功能,可以說是優化的專項功能類.

python subprocess

用於程式執行時呼叫子程式,通過stdout,stdin和stderr進行互動。

Stdout子程式執行結果返回,如檔案、螢幕等
Stdin 子程式執行時的輸入,如檔案,檔案物件
Stderr錯誤輸出

常用的兩種方式(以shell程式為例):

1,subprocess.Popen('指令碼/shell', shell=True)   #無阻塞並行
2,subprocess.call('指令碼/shell', shell=True)   #等子程式結束再繼續

兩者的區別是前者無阻塞,會和主程式並行執行,後者必須等待命令執行完畢,如果想要前者程式設計阻塞加wait():

p = subprocess.Popen('指令碼/shell', shell=True)
a=p.wait() # 返回子程序結果
具體程式碼事例: 
        hadoop_cmd = "hadoop fs -ls %s"%(hive_tb_path)
        p = subprocess.Popen(hadoop_cmd, shell=True
, stdout=subprocess.PIPE, stderr=subprocess.PIPE) ret = p.wait() #wait()函式是等待模式的執行子程序,返回執行命令狀態,成功0,失敗1 print ret #執行成功返回0,失敗返回1。 #而命令的結果檢視通過 print p.stdout.read() #錯誤檢視通過 print p.stderr.read()

呼叫子程序程式碼例項:

方式一
import subprocess
p=subprocess.Popen('./test/dirtest.py'
,stdout=subprocess.PIPE,shell=True) print p.stdout.readlines() out,err = p.communicate() print out print err
##這是一次性互動,讀入是stdin,直接執行完畢後,返回給stdout,communicate通訊一次之後即關閉了管道。但如果需要多次互動,頻繁地和子執行緒通訊不能使用communicate(), 可以分步進行通訊,如下:
    p= subprocess.Popen(["ls","-l"], stdin=subprocess.PIPE,stdout=subprocess.PIPE,shell=False)  
    //輸入
    p.stdin.write('your command')  
    p.stdin.flush() 
    //檢視輸出
    p.stdout.readline() 
    p.stdout.read() 
方式二
    ret=subprocess.call('ping -c 1 %s' % ip,shell=True,stdout=open('/dev/null','w'),stderr=subprocess.STDOUT)  
    if ret==0:
        print '%s is alive!' %ip  
    elif ret==1:
        print '%s is down...'%ip  

引數shell的意義

    call()和Popen()都有shell引數,預設為False,可以賦值為True。
    引數shell(預設為False)指定是否使用shell來執行程式。如果shell為True,前面會自動加上/bin/sh命令,則建議傳遞一個字串(而不是序列)給args,如果為False就必須傳列表,分開儲存命令內容。比如
    subprocess.Popen("cat test.txt", shell=True)
相當於
    subprocess.Popen(["/bin/sh", "-c", "cat test.txt"])
原因具體是,
    在Linux下,shell=False時, Popen呼叫os.execvp()執行args指定的程式;
    在Windows下,Popen呼叫CreateProcess()執行args指定的外部程式,args傳入字元和序列都行,序列會自動list2cmdline()轉化為字串,但需要注意的是,並不是MS Windows下所有的程式都可以用list2cmdline來轉化為命令列字串。
    所以,windows下
        subprocess.Popen("notepad.exe test.txt" shell=True)
        等同於
        subprocess.Popen("cmd.exe /C "+"notepad.exe test.txt" shell=True)
shell=True可能引起問題
 傳遞shell=True在與不可信任的輸入繫結在一起時可能出現安全問題
警告 執行的shell命令如果來自不可信任的輸入源將使得程式容易受到shell注入攻擊,一個嚴重的安全缺陷可能導致執行任意的命令。因為這個原因,在命令字串是從外部輸入的情況下使用shell=True 是強烈不建議的:
    >>> from subprocess import call
    >>> filename = input("What file would you like to display?\n")
    What file would you like to display?
    non_existent; rm -rf / #
    >>> call("cat " + filename, shell=True) # Uh-oh. This will end badly...

shell=False禁用所有基於shell的功能,所以不會受此漏洞影響;參見Popen建構函式文件中的注意事項以得到如何使shell=False工作的有用提示。
當使用shell=True時,pipes.quote()可以用來正確地轉義字串中將用來構造shell命令的空白和shell元字元。 
幾個介紹subprocess比較詳細的網站:
http://python.usyiyi.cn/python_278/library/subprocess.html(英文https://docs.python.org/2/library/subprocess.html)
http://ipseek.blog.51cto.com/1041109/807513
https://blog.linuxeye.com/375.html
http://blog.csdn.net/imzoer/article/details/8678029     

子程式指令碼的當前路徑問題

不管用os還是subprocess呼叫子程式,都會遇到獲取當前路徑的問題。即子程式指令碼程式碼中想要獲取當前路徑,那麼獲取的路徑是主程式還是子程式的?
Python獲取指令碼路徑的方式主要有兩種:
    1)os.path.dirname(os.path.abspath("__file__"))
    2)sys.path[0]
參考http://blog.csdn.net/longshenlmj/article/details/25148935, 
    第一種會獲取主程式的路徑,也就是當前的__file__物件存的是主程式指令碼
    第二種才能獲取子程式指令碼的路徑
程式碼例項:
主程式指令碼callpy.py路徑為/home/wizad/lmj,
呼叫的子程式指令碼dirtest.py路徑為/home/wizad/lmj/test

[[email protected] lmj]$ cat callpy.py

import subprocess
p = subprocess.Popen('python ./test/dirtest.py',stdout=open('dirtest.txt','w'),shell=True)

[[email protected] test]$ cat dirtest.py

import os
import sys
file_path=os.path.dirname(os.path.abspath("__file__"))
print file_path+"11111"
cur_path = sys.path[0]
print cur_path+"22222"

執行python callpy.py結果輸出:cat dirtest.txt

/home/wizad/lmj11111
/home/wizad/lmj/test22222

輸出結果是放到檔案dirtest.txt中,可以看出方式1是主程式路徑,而方式2是子程式路徑。
另外,stdout的輸出方式還可以是PIPE,讀取的方式可以直接列印,
如,
1)

p = subprocess.Popen('python ./test/dirtest.py',stdout=subprocess.PIPE,shell=True)
out,err = p.communicate()
print out
print err

輸出:[[email protected] lmj]$ python callpy.py

/home/wizad/lmj11111
/home/wizad/lmj/test22222

None

2)

p = subprocess.Popen('python ./test/dirtest.py',stdout=subprocess.PIPE,shell=True)
print p.stdout.readlines()  
out,err = p.communicate()
print out
print err

輸出為

['/home/wizad/lmj11111\n', '/home/wizad/lmj/test22222\n']

None

這兩種讀取方式,是直接通過螢幕輸出結果。

有關subprocess模組其他知識,引用一些資料如下:

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)

這裡寫圖片描述
1)、args可以是字串或者序列型別(如:list,元組),用於指定程序的可執行檔案及其引數。如果是序列型別,第一個元素通常是可執行檔案的路徑。我們也可以顯式的使用executeable引數來指定可執行檔案的路徑。
2)、bufsize:指定緩衝。0 無緩衝,1 行緩衝,其他 緩衝區大小,負值 系統緩衝(全緩衝)
3)、stdin, stdout, stderr分別表示程式的標準輸入、輸出、錯誤控制代碼。他們可以是PIPE,檔案描述符或檔案物件,也可以設定為None,表示從父程序繼承。
4)、preexec_fn只在Unix平臺下有效,用於指定一個可執行物件(callable object),它將在子程序執行之前被呼叫。
5)、Close_sfs:在windows平臺下,如果close_fds被設定為True,則新建立的子程序將不會繼承父程序的輸入、輸出、錯誤管道。我們不能將close_fds設定為True同時重定向子程序的標準輸入、輸出與錯誤(stdin, stdout, stderr)。
6)、shell設為true,程式將通過shell來執行。
7)、cwd用於設定子程序的當前目錄
8)、env是字典型別,用於指定子程序的環境變數。如果env = None,子程序的環境變數將從父程序中繼承。Universal_newlines:不同作業系統下,文字的換行符是不一樣的。如:windows下用’/r/n’表示換,而Linux下用’/n’。如果將此引數設定為True,Python統一把這些換行符當作’/n’來處理。
9)、startupinfo與createionflags只在windows下有效,它們將被傳遞給底層的CreateProcess()函式,用於設定子程序的一些屬性,如:主視窗的外觀,程序的優先順序等等。

Popen方法
1)、Popen.poll():用於檢查子程序是否已經結束。設定並返回returncode屬性。
2)、Popen.wait():等待子程序結束。設定並返回returncode屬性。
3)、Popen.communicate(input=None):與子程序進行互動。向stdin傳送資料,或從stdout和stderr中讀取資料。可選引數input指定傳送到子程序的引數。Communicate()返回一個元組:(stdoutdata, stderrdata)。注意:如果希望通過程序的stdin向其傳送資料,在建立Popen物件的時候,引數stdin必須被設定為PIPE。同樣,如果希望從stdout和stderr獲取資料,必須將stdout和stderr設定為PIPE。
4)、Popen.send_signal(signal):向子程序傳送訊號。
5)、Popen.terminate():停止(stop)子程序。在windows平臺下,該方法將呼叫Windows API TerminateProcess()來結束子程序。
6)、Popen.kill():殺死子程序。
7)、Popen.stdin:如果在建立Popen物件是,引數stdin被設定為PIPE,Popen.stdin將返回一個檔案物件用於策子程序傳送指令。否則返回None。
8)、Popen.stdout:如果在建立Popen物件是,引數stdout被設定為PIPE,Popen.stdout將返回一個檔案物件用於策子程序傳送指令。否則返回None。
9)、Popen.stderr:如果在建立Popen物件是,引數stdout被設定為PIPE,Popen.stdout將返回一個檔案物件用於策子程序傳送指令。否則返回None。
10)、Popen.pid:獲取子程序的程序ID。
11)、Popen.returncode:獲取程序的返回值。如果程序還沒有結束,返回None。
12)、subprocess.call(*popenargs, **kwargs):執行命令。該函式將一直等待到子程序執行結束,並返回程序的returncode。文章一開始的例子就演示了call函式。如果子程序不需要進行互動,就可以使用該函式來建立。
13)、subprocess.check_call(*popenargs, **kwargs):與subprocess.call(*popenargs, **kwargs)功能一樣,只是如果子程序返回的returncode不為0的話,將觸發CalledProcessError異常。在異常物件中,包括程序的returncode資訊。

死鎖

使用管道時,不去處理管道的輸出,當   子程序輸出了大量資料到stdout或者stderr的管道,並達到了系統pipe的快取大小的話(作業系統快取無法獲取更多資訊),子程序會等待父程序讀取管道,而父程序此時正wait著的話,將會產生傳說中的死鎖。
可能引起死鎖的呼叫:
    subprocess.call()
    subprocess.check_call()
    subprocess.check_output()
    Popen.wait()
    可以看出,子程序使用管道互動,如果需要等待子程序完畢,就可能引起死鎖。比如下面的用法:
    p=subprocess.Popen("longprint", shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)  
    p.wait()  
longprint是一個假想的有大量輸出的程序,那麼在我的xp, Python2.5的環境下,當輸出達到4096時,死鎖就發生了。
避免subprocess的管道引起死鎖
1)使用Popen()和communicate()方法,可以避免死鎖。沒有等待,會自動清理快取。
2)如果用p.stdout.readline(或者p.communicate)去清理輸出,那麼無論輸出多少,死鎖都是不會發生的。
3)或者不用管道,比如不做重定向,或者重定向到檔案,也可以避免死鎖。