天天看點

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==:
        print '%s is alive!' %ip  
    elif ret==:
        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)
           
python的subprocess:子程式調用(調用執行其他指令);擷取子程式腳本目前路徑問題

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)或者不用管道,比如不做重定向,或者重定向到檔案,也可以避免死鎖。