2009-08-10 29 views
19

當使用subprocess.Popen(args, shell=True)運行「gcc --version」(就像一個例子),在Windows中,我們得到這樣的:爲什麼subprocess.Popen()在Linux上殼=真正的工作方式不同VS的Windows?

>>> from subprocess import Popen 
>>> Popen(['gcc', '--version'], shell=True) 
gcc (GCC) 3.4.5 (mingw-vista special r3) ... 

所以它很好地打印出的版本,如我所料。但在Linux上,我們得到這樣的:

>>> from subprocess import Popen 
>>> Popen(['gcc', '--version'], shell=True) 
gcc: no input files 

因爲GCC還沒有收到--version選項。

該文檔沒有明確指定Windows下的參數應該發生什麼,但它確實說在Unix上,「如果args是一個序列,第一個項目指定命令字符串,並且任何其他項目將視爲額外的shell參數「。恕我直言Windows的方式比較好,因爲它可以讓你把Popen(arglist)調用一樣Popen(arglist, shell=True)的。

爲什麼Windows和Linux的區別在這裏?

+0

通常包含您正在使用的Python版本或者至少如果是第2行或第3行是一個好主意。 – mloskot 2011-12-19 19:49:17

回答

14

其實在Windows上,它使用cmd.exeshell=True - 它預先考慮cmd.exe /c(它實際上在COMSPEC環境變量,但默認仰望cmd.exe如果不存在)的殼論點。 (在Windows 95/98上,它使用中間w9xpopen程序來實際啓動該命令)。

那麼奇怪實現實際上是UNIX之一,執行以下操作(其中每個空間分隔不同的參數):

/bin/sh -c gcc --version 

看起來正確執行(至少在Linux上)將是:

/bin/sh -c "gcc --version" gcc --version 

因爲這會從帶引號的參數中設置命令字符串,併成功傳遞其他參數。

sh手冊頁節-c

Read commands from the command_string operand instead of from the standard input. Special parameter 0 will be set from the command_name operand and the positional parameters ($1, $2, etc.) set from the remaining argument operands.

這個補丁似乎相當簡單的伎倆:

--- subprocess.py.orig 2009-04-19 04:43:42.000000000 +0200 
+++ subprocess.py  2009-08-10 13:08:48.000000000 +0200 
@@ -990,7 +990,7 @@ 
       args = list(args) 

      if shell: 
-    args = ["/bin/sh", "-c"] + args 
+    args = ["/bin/sh", "-c"] + [" ".join(args)] + args 

      if executable is None: 
       executable = args[0] 
+0

很好,謝謝大衛。我同意正確的實施和你的補丁看起來不錯。您是否處於比我更好的位置提交Python錯誤報告 - 換句話說,您是否曾經這麼做過,或者我應該研究一下? – 2009-08-10 21:59:05

+1

添加http://bugs.python.org/issue6689 - 如果你可以關注它,評論那裏等 – 2009-08-12 08:35:22

+0

謝謝!我已將自己添加到了這個討厭的名單中。 – 2009-08-12 23:20:55

5

從subprocess.py源:

在UNIX上,與殼=真:如果ARGS是一個字符串,它指定 命令串通過殼來執行。如果ARGS是一個序列, 第一個項目指定命令串,和任何其他項 將被視爲附加的殼參數。

在Windows上:Popen類使用CreateProcess()來執行對子字符串進行操作的子程序 。如果參數是一個序列,則將使用list2cmdline方法將其轉換爲字符串 。請注意, 並非所有MS Windows應用程序都將命令行解釋爲相同的 方式:list2cmdline是爲與MS C運行時使用相同 規則的應用程序而設計的。

這並不回答爲什麼,只是澄清,你看到了預期的行爲。

「爲什麼」可能是在類UNIX系統上,命令參數實際上作爲字符串數組傳遞給應用程序(使用調用系列)。換句話說,調用進程決定進入每個命令行參數的內容。當你告訴它使用一個外殼,而調用進程實際上只到達一個命令行參數傳遞給shell執行的機會:您要執行的,可執行文件名和參數的整個命令行,作爲一個字符串。

但是在Windows上,整個命令行(根據上述文檔)作爲單個字符串傳遞給子進程。如果你看一下CreateProcess API文檔,你會發現,它希望所有的命令行參數一起連接成一個大的字符串(因此調用list2cmdline)。

加有一個事實,即在類UNIX系統裏面居然殼可以做有益的事情,所以我懷疑是其他原因不同的是,在Windows上,shell=True什麼也不做,這是爲什麼它以你所看到的方式工作。使兩個系統的行爲完全一致的唯一方法是在Windows上shell=True時簡單地刪除所有命令行參數。

+1

Windows上也有一個shell(通常是'cmd.exe'),但是您引用的註釋上面指出,當shell = True時,Python實際上並沒有使用它(相反,它直接使用'CreateProcess()')。 – 2009-08-10 05:02:12

+1

謝謝 - 正如格雷格所說,Windows上肯定有一個shell(cmd.exe或COMSPEC中的一個)。它由Popen使用(儘管通過CreateProcess) - 請參閱subprocess.py源代碼。所以在我看來,子進程應該使它們以相同的方式工作,以避免可移植性的缺陷...... – 2009-08-10 05:11:36

+0

注意:[文檔自2010年以來已更新](https://docs.python.org/3/library /subprocess.html#popen-constructor) – jfs 2014-10-30 11:18:54

0

原因的UNIX行爲shell=True與引用有關。當我們寫一個shell命令,它會在空間進行分割,所以我們要引用一些參數:

cp "My File" "New Location" 

這導致了問題的時候我們的論點包含報價,這需要轉義:

grep -r "\"hello\"" . 

有時我們可以得到awful situations,其中\也必須逃脫!

當然,真正的問題是,我們正在嘗試使用一個字符串指定多個字符串。當調用系統命令,大多數編程語言避免這種情況使我們能夠在第一時間發送多個字符串,因此:

Popen(['cp', 'My File', 'New Location']) 
Popen(['grep', '-r', '"hello"']) 

有時它可以很好運行的「原始」 shell命令;例如,如果我們從shell腳本或Web站點複製粘貼內容,並且我們不想手動轉換所有可怕的轉義。這就是爲什麼shell=True選項存在:

Popen(['cp "My File" "New Location"'], shell=True) 
Popen(['grep -r "\"hello\"" .'], shell=True) 

我不熟悉Windows,所以我不知道如何或爲何表現不同。

相關問題