在Python中調用外部命令



Answers

以下是調用外部程序的方法以及每種方法的優缺點的總結:

  1. os.system("some_command with args")將命令和參數傳遞到系統的shell。 這很好,因為你可以用這種方式一次運行多個命令,並設置管道和輸入/輸出重定向。 例如:

    os.system("some_command < input_file | another_command > output_file")  
    

    但是,雖然這很方便,但您必須手動處理諸如空格等外殼字符的轉義。另一方面,這也可以讓您運行僅僅是shell命令的命令,而不是實際上的外部程序。 請參閱文檔

  2. stream = os.popen("some_command with args")將執行與os.system相同的操作,只不過它提供了一個類似文件的對象,您可以使用該對象訪問該進程的標準輸入/輸出。 還有另外3種不同的popen,它們處理I / O的方式略有不同。 如果你把所有東西都作為一個字符串傳遞,那麼你的命令被傳遞給shell; 如果你將它們作為列表傳遞給你,那麼你不需要擔心逃脫任何事情。 請參閱文檔

  3. subprocess模塊的Popen類。 這是為了替代os.popen但由於其如此全面而具有稍微複雜的缺點。 例如,你會說:

    print subprocess.Popen("echo Hello World", shell=True, stdout=subprocess.PIPE).stdout.read()
    

    代替:

    print os.popen("echo Hello World").read()
    

    但是在一個統一的類中有所有的選項,而不是4個不同的popen函數是很好的。 請參閱文檔

  4. 來自subprocess模塊的call函數。 這基本上和Popen類一樣,並且採用了所有相同的參數,但它只是等待命令完成並給出返回碼。 例如:

    return_code = subprocess.call("echo Hello World", shell=True)  
    

    請參閱文檔

  5. 如果您使用的是Python 3.5或更高版本,則可以使用新的subprocess.run函數,該函數與上述類似,但更加靈活,並在命令執行CompletedProcess時返回CompletedProcess對象。

  6. os模塊還具有C程序中所有的fork / exec / spawn函數,但我不建議直接使用它們。

subprocess模塊可能應該是你使用的。

最後請注意,對於所有通過shell以字符串形式執行的最終命令的方法,您負責轉義它。 如果您傳遞的字符串的任何部分無法完全信任,則存在嚴重的安全隱患 。 例如,如果用戶正在輸入字符串的某個/任何部分。 如果您不確定,請僅使用這些方法和常量。 為了給你一些暗示,請考慮下面的代碼:

print subprocess.Popen("echo %s " % user_input, stdout=PIPE).stdout.read()

並想像用戶輸入“我的媽媽沒有愛我&& rm -rf /”。

Question

我如何從Python腳本中調用一個外部命令(就像我在Unix shell或Windows命令提示符下鍵入的那樣)?




更新:

如果您的代碼不需要保持與早期Python版本的兼容性,那麼subprocess.runPython 3.5的推薦方法。 它更一致,並提供與Envoy類似的易用性。 (管道不是那麼簡單,請看這個問題 。)

以下是文檔中的一些示例。

運行一個進程:

>>> subprocess.run(["ls", "-l"])  # doesn't capture output
CompletedProcess(args=['ls', '-l'], returncode=0)

提升失敗運行:

>>> subprocess.run("exit 1", shell=True, check=True)
Traceback (most recent call last):
  ...
subprocess.CalledProcessError: Command 'exit 1' returned non-zero exit status 1

捕獲輸出:

>>> subprocess.run(["ls", "-l", "/dev/null"], stdout=subprocess.PIPE)
CompletedProcess(args=['ls', '-l', '/dev/null'], returncode=0,
stdout=b'crw-rw-rw- 1 root root 1, 3 Jan 23 16:23 /dev/null\n')

原始答案:

我建議嘗試https://github.com/kennethreitz/envoy 。 它是子進程的一個包裝,它反過來旨在替換舊的模塊和函數。 特使是人類的子進程。

自述文件中的示例用法:

>>> r = envoy.run('git config', data='data to pipe in', timeout=2)

>>> r.status_code
129
>>> r.std_out
'usage: git config [options]'
>>> r.std_err
''

管東西也是:

>>> r = envoy.run('uptime | pbcopy')

>>> r.command
'pbcopy'
>>> r.status_code
0

>>> r.history
[<Response 'uptime'>]



import os
cmd = 'ls -al'
os.system(cmd)

如果你想返回命令的結果,你可以使用os.popen 。 但是,從版本2.6開始,這被棄用,以支持子流程模塊 ,其他答案已經覆蓋得很好。




In Windows you can just import the subprocess module and run external commands by calling subprocess.Popen() , subprocess.Popen().communicate() and subprocess.Popen().wait() as below:

# Python script to run a command line
import subprocess

def execute(cmd):
    """
        Purpose  : To execute a command and return exit status
        Argument : cmd - command to execute
        Return   : exit_code
    """
    process = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    (result, error) = process.communicate()

    rc = process.wait()

    if rc != 0:
        print "Error: failed to execute command:", cmd
        print error
    return result
# def

command = "tasklist | grep python"
print "This process detail: \n", execute(command)

輸出:

This process detail:
python.exe                     604 RDP-Tcp#0                  4      5,660 K



os.system不允許你存儲結果,所以如果你想在一些列表中存儲結果或者subprocess.call有效。




如果你不想測試返回值, subprocess.check_call是很方便的。 它在任何錯誤上拋出異常。




在Python中調用外部命令

很簡單,使用subprocess.run ,它返回一個CompletedProcess對象:

>>> import subprocess
>>> completed_process = subprocess.run('python --version')
Python 3.6.1 :: Anaconda 4.4.0 (64-bit)
>>> completed_process
CompletedProcess(args='python --version', returncode=0)

為什麼?

從Python 3.5開始,文檔建議使用subprocess.run

調用子進程的推薦方法是對它可以處理的所有用例使用run()函數。 對於更高級的用例,底層的Popen接口可以直接使用。

這裡有一個最簡單的用法的例子 - 它的確如所問:

>>> import subprocess
>>> completed_process = subprocess.run('python --version')
Python 3.6.1 :: Anaconda 4.4.0 (64-bit)
>>> completed_process
CompletedProcess(args='python --version', returncode=0)

run等待命令成功完成,然後返回一個CompletedProcess對象。 它可能會提高TimeoutExpired (如果您給它一個timeout=參數)或CalledProcessError (如果它失敗並且您傳遞check=True )。

正如你從上面的例子中推斷的那樣,默認情況下,stdout和stderr都會被傳送到你自己的stdout和stderr。

我們可以檢查返回的對象並查看給出的命令和返回碼:

>>> completed_process.args
'python --version'
>>> completed_process.returncode
0

捕獲輸出

如果要捕獲輸出,可以將subprocess.PIPE傳遞給相應的stderrstdout

>>> cp = subprocess.run('python --version', 
                        stderr=subprocess.PIPE, 
                        stdout=subprocess.PIPE)
>>> cp.stderr
b'Python 3.6.1 :: Anaconda 4.4.0 (64-bit)\r\n'
>>> cp.stdout
b''

(我發現它有趣並且有點違反直覺,版本信息被置於stderr而不是stdout。)

傳遞一個命令列表

人們可以輕鬆地從手動提供命令字符串(如問題建議)移動到提供以編程方式構建的字符串。 不要以編程方式構建字符串。 這是一個潛在的安全問題。 最好假設你不相信輸入。

>>> import textwrap
>>> args = ['python', textwrap.__file__]
>>> cp = subprocess.run(args, stdout=subprocess.PIPE)
>>> cp.stdout
b'Hello there.\r\n  This is indented.\r\n'

請注意,只有args應該通過位置傳遞。

全額簽名

以下是源代碼中的實際簽名,如help(run)

def run(*popenargs, input=None, timeout=None, check=False, **kwargs):

popenargskwargs被賦予了Popen構造函數。 input可以是一個字符串(或者unicode,如果指定encoding或universal_newlines=True ),它將被傳遞給子進程的stdin。

該文檔比我更好地描述了timeout=check=True

超時參數傳遞給Popen.communicate()。 如果超時過期,子進程將被終止並等待。 TimeoutExpired異常將在子進程終止後重新生成。

如果檢查為真,並且該進程以非零退出代碼退出,則會引發CalledProcessError異常。 該異常的屬性包含參數,退出代碼以及stdout和stderr(如果它們被捕獲)。

這個check=True例子比我能想到的更好:

>>> subprocess.run("exit 1", shell=True, check=True)
Traceback (most recent call last):
  ...
subprocess.CalledProcessError: Command 'exit 1' returned non-zero exit status 1

擴展簽名

以下是文檔中給出的擴展簽名:

subprocess.run(args, *, stdin=None, input=None, stdout=None, stderr=None, 
shell=False, cwd=None, timeout=None, check=False, encoding=None, 
errors=None)

請注意,這表示只有參數列表應該通過位置傳遞。 因此將其餘參數作為關鍵字參數傳遞。

POPEN

當使用Popen來代替? 我很難僅根據論據找到用例。 但是,直接使用Popen將允許您訪問其方法,包括poll ,“send_signal”,“終止”和“等待”。

這是來源中給出的Popen簽名。 我認為這是信息的最精確封裝(而不是help(Popen) ):

def __init__(self, args, bufsize=-1, executable=None,
             stdin=None, stdout=None, stderr=None,
             preexec_fn=None, close_fds=_PLATFORM_DEFAULT_CLOSE_FDS,
             shell=False, cwd=None, env=None, universal_newlines=False,
             startupinfo=None, creationflags=0,
             restore_signals=True, start_new_session=False,
             pass_fds=(), *, encoding=None, errors=None):

但更多的信息是Popen文件

subprocess.Popen(args, bufsize=-1, executable=None, stdin=None,
                 stdout=None, stderr=None, preexec_fn=None, close_fds=True,
                 shell=False, cwd=None, env=None, universal_newlines=False,
                 startupinfo=None, creationflags=0, restore_signals=True,
                 start_new_session=False, pass_fds=(), *, encoding=None, errors=None)

在新過程中執行子程序。 在POSIX上,該類使用類似os.execvp()的行為來執行子程序。 在Windows上,該類使用Windows的CreateProcess()函數。 Popen的論據如下。

了解Popen的剩余文檔將留給讀者作為練習。




還有Plumbum

>>> from plumbum import local
>>> ls = local["ls"]
>>> ls
LocalCommand(<LocalPath /bin/ls>)
>>> ls()
u'build.py\ndist\ndocs\nLICENSE\nplumbum\nREADME.rst\nsetup.py\ntests\ntodo.txt\n'
>>> notepad = local["c:\\windows\\notepad.exe"]
>>> notepad()                                   # Notepad window pops up
u''                                             # Notepad window is closed by user, command returns



如果你需要的是你正在調用的命令的輸出,
那麼你可以使用subprocess.check_output(Python 2.7+)。

>>> subprocess.check_output(["ls", "-l", "/dev/null"])
'crw-rw-rw- 1 root root 1, 3 Oct 18  2007 /dev/null\n'

另請注意shell參數。

如果shell為True ,則指定的命令將通過shell執行。 如果您主要將Python用於其在大多數係統shell中提供的增強控制流,並且仍然想要方便地訪問其他shell功能(如shell管道,文件名通配符,環境變量擴展以及將〜擴展到用戶的home目錄。 但是,請注意,Python本身提供了很多類似shell的功能(特別是globfnmatchos.walk()os.path.expandvars()os.path.expanduser()shutil )的實現。




os.system ,但有點過時。 這也不是很安全。 相反,嘗試subprocesssubprocess os.system不直接調用sh,因此比os.system更安全。

here獲取更多信息。




關於從調用者中分離子進程的一些提示(在後台啟動子進程)。

假設你想從一個CGI腳本開始一個長任務,那就是子進程應該比CGI腳本的執行過程長。

子流程模塊文檔中的經典示例是:

import subprocess
import sys

# some code here

pid = subprocess.Popen([sys.executable, "longtask.py"]) # call subprocess

# some more code here

這裡的想法是,你不想在'call subprocess'行中等待,直到longtask.py完成。 但是不清楚在這個例子中'這裡有更多的代碼'之後會發生什麼。

我的目標平台是freebsd,但是開發是在Windows上進行的,所以我首先面對Windows上的問題。

在Windows(win xp)上,父進程將不會完成,直到longtask.py完成其工作。 這不是你想要的CGI腳本。 這個問題不是特定於Python,在PHP社區中,問題是相同的。

解決方案是在win API中將DETACHED_PROCESS 進程創建標誌傳遞給底層的CreateProcess函數。 如果你碰巧安裝了pywin32,你可以從win32process模塊導入標誌,否則你應該自己定義它:

DETACHED_PROCESS = 0x00000008

pid = subprocess.Popen([sys.executable, "longtask.py"],
                       creationflags=DETACHED_PROCESS).pid

/ * UPD 2015.10.27 @eryksun在下面的註釋中註意到,語義上正確的標誌是CREATE_NEW_CONSOLE(0x00000010)* /

在freebsd上,我們遇到了另一個問題:當父進程完成時,它也完成了子進程。 這並不是你想要的CGI腳本。 一些實驗表明,這個問題似乎是在共享sys.stdout。 工作解決方案如下:

pid = subprocess.Popen([sys.executable, "longtask.py"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE)

我沒有檢查過其他平台上的代碼,也不知道freebsd上的行為原因。 如果有人知道,請分享您的想法。 在Python中開始後台進程的搜索並沒有任何亮點。




您可以使用Popen,然後您可以檢查過程的狀態:

from subprocess import Popen

proc = Popen(['ls', '-l'])
if proc.poll() is None:
    proc.kill()

檢查出subprocess.Popen




從openstack中子獲取網絡ID:

#!/usr/bin/python
import os
netid= "nova net-list | awk '/ External / { print $2 }'"
temp=os.popen(netid).read()  /* here temp also contains new line (\n) */
networkId=temp.rstrip()
print(networkId)

星網表的輸出

+--------------------------------------+------------+------+
| ID                                   | Label      | CIDR |
+--------------------------------------+------------+------+
| 431c9014-5b5d-4b51-a357-66020ffbb123 | test1      | None |
| 27a74fcd-37c0-4789-9414-9531b7e3f126 | External   | None |
| 5a2712e9-70dc-4b0e-9281-17e02f4684c9 | management | None |
| 7aa697f5-0e60-4c15-b4cc-9cb659698512 | Internal   | None |
+--------------------------------------+------------+------+

打印輸出(networkId)

27a74fcd-37c0-4789-9414-9531b7e3f126



也檢查“pexpect”Python庫。

它允許外部程序/命令的交互式控制,甚至ssh,ftp,telnet等。你可以輸入如下內容:

child = pexpect.spawn('ftp 192.168.0.24')

child.expect('(?i)name .*: ')

child.sendline('anonymous')

child.expect('(?i)password')



這是我如何運行我的命令。 這段代碼有你需要的一切

from subprocess import Popen, PIPE
cmd = "ls -l ~/"
p = Popen(cmd , shell=True, stdout=PIPE, stderr=PIPE)
out, err = p.communicate()
print "Return code: ", p.returncode
print out.rstrip(), err.rstrip()



Links