簡體   English   中英

如何從 python 中運行 bash 腳本並獲取所有輸出?

[英]How to run a bash script from within python and get all the output?

這是對這里答案的直接澄清問題,我認為它有效,但它沒有!

我有以下測試 bash 腳本 ( testbash.sh ),它只是創建一些輸出和許多用於測試目的的錯誤(在 Red Hat Enterprise Linux Server 7.6 (Maipo) 和 Ubuntu 16.04.6 LTS 上運行):

export MAX_SEED=2
echo "Start test"
pids=""

for seed in `seq 1 ${MAX_SEED}`
do
  python -c "raise ValueError('test')" &
  pids="${pids} $!"
done
echo "pids: ${pids}"
wait $pids
echo "End test"

如果我運行這個腳本,我會得到以下輸出:

Start test
pids:  68322 68323
Traceback (most recent call last):
  File "<string>", line 1, in <module>
ValueError: test
Traceback (most recent call last):
  File "<string>", line 1, in <module>
ValueError: test
[1]-  Exit 1                  python -c "raise ValueError('test')"
[2]+  Exit 1                  python -c "raise ValueError('test')"
End test

這就是預期的結果。 那沒關系。 我想得到錯誤!

現在這里是應該捕獲所有輸出的python代碼:

from __future__ import print_function

import sys
import time
from subprocess import PIPE, Popen, STDOUT
from threading  import Thread

try:
    from queue import Queue, Empty
except ImportError:
    from Queue import Queue, Empty  # python 2.x    

ON_POSIX = 'posix' in sys.builtin_module_names

def enqueue_output(out, queue):
    for line in iter(out.readline, b''):
        queue.put(line.decode('ascii'))
    out.close()

p = Popen(['. testbash.sh'], stdout=PIPE, stderr=STDOUT, bufsize=1, close_fds=ON_POSIX, shell=True)
q = Queue()
t = Thread(target=enqueue_output, args=(p.stdout, q))
t.daemon = True # thread dies with the program
t.start()

# read line without blocking
while t.is_alive():
    #time.sleep(1)
    try:
        line = q.get(timeout=.1)
    except Empty:
        print(line)
        pass
    else:
        # got line
        print(line, end='')

p.wait()
print('returncode = {}'.format(p.returncode))

但是當我運行這段代碼時,我只會得到以下輸出:

Start test
pids:  70191 70192
Traceback (most recent call last):
returncode = 0

或此輸出(沒有行End test ):

Start test
pids:  10180 10181
Traceback (most recent call last):
  File "<string>", line 1, in <module>
ValueError: test
Traceback (most recent call last):
  File "<string>", line 1, in <module>
ValueError: test
returncode = 0

上面的大部分輸出都丟失了! 我怎樣才能解決這個問題? 另外,我需要一些方法來檢查 bash 腳本中的任何命令是否失敗。 在示例中就是這種情況,但打印出的錯誤代碼仍然是 0。我期望錯誤代碼 != 0。

立即獲得輸出並不重要。 延遲幾秒鍾就可以了。 此外,如果輸出順序有點混亂,這也無關緊要。 重要的是獲取所有輸出( stdoutstderr )。

也許有一種更簡單的方法來獲取從 python 啟動的 bash 腳本的輸出?

與 python3 一起運行

from __future__ import print_function
import os
import stat
import sys
import time
from subprocess import PIPE, Popen, STDOUT
from threading  import Thread
try:
    from queue import Queue, Empty
except ImportError:
    from Queue import Queue, Empty  # python 2.x
ON_POSIX = 'posix' in sys.builtin_module_names
TESTBASH = '/tmp/testbash.sh'
def create_bashtest():
    with open(TESTBASH, 'wt') as file_desc:
        file_desc.write("""#!/usr/bin/env bash
export MAX_SEED=2
echo "Start test"
pids=""
for seed in `seq 1 ${MAX_SEED}`
do
  python -c "raise ValueError('test')" &
  pids="${pids} $!"
  sleep .1 # Wait so that error messages don't get out of order.
done
wait $pids; return_code=$?
sleep 0.2 # Wait for background messages to be processed.
echo "pids: ${pids}"
echo "End test"
sleep 1 # Wait for main process to handle all the output
exit $return_code
""")
    os.chmod(TESTBASH, stat.S_IEXEC|stat.S_IRUSR|stat.S_IWUSR)

def enqueue_output(queue):
    pipe = Popen([TESTBASH], stdout=PIPE, stderr=STDOUT,
                 bufsize=1, close_fds=ON_POSIX, shell=True)
    out = pipe.stdout
    while pipe.poll() is None:
        line = out.readline()
        if  line:
            queue.put(line.decode('ascii'))
        time.sleep(.1)
    print('returncode = {}'.format(pipe.returncode))

create_bashtest()
C_CHANNEL = Queue()

THREAD = Thread(target=enqueue_output, args=(C_CHANNEL,))
THREAD.daemon = True
THREAD.start()

while THREAD.is_alive():
    time.sleep(0.1)
    try:
        line = C_CHANNEL.get_nowait()
    except Empty:
        pass # print("no output")
    else:
        print(line, end='')

希望這可以幫助 :

首先,看起來緩沖區沒有被刷新。 將 stdout/stderr 重定向(並且,為了安全起見,附加)到文件而不是終端,可能會有所幫助。 如果你真的想要兩者,你總是可以使用tee (或tee -a )。 使用上下文管理器“可能”有幫助。

至於零返回碼, $! https://unix.stackexchange.com/questions/386196/doesnt-work-on-command-line ! 可能正在調用歷史調用歷史,因此$! 導致空值。

如果您以某種方式最終只是wait ,返回碼將為零。 無論如何,返回代碼可能很棘手,您可能會從其他地方選擇一個成功的返回代碼。

查看 stdbuf 命令以更改 stdout 和 stderr 的緩沖區大小: 是否有辦法刷新正在運行的進程的 stdout這也可能有助於獲得其余的預期輸出。

以這種方式重寫while塊:

# read line without blocking
while t.is_alive():
    try:
        line = q.get(block=False)
    except Empty:
        # print(line)
        pass
    else:
        # got line
        print(line, end='')

當沒有Queue時,您不想阻塞從Queue獲取一行,並且在這種情況下您不需要超時,因為它僅在需要阻塞線程時使用。 因此,如果Queue.get()拋出Empty ,則沒有要打印的行,我們只需pass

===

另外,讓我們澄清一下腳本執行邏輯。

由於您使用的是 Bash 表達式,並且Popen使用的默認 shell 是/bin/sh ,您可能希望以這種方式重寫調用行:

p = Popen(['/usr/bin/bash','-c', './testbash.sh'], stdout=PIPE, stderr=STDOUT, bufsize=1, close_fds=ON_POSIX)

在你的 shell 腳本中添加一個 shebang 也沒有什么壞處:

#!/usr/bin/env bash
<... rest of the script ...>

如果您正在尋找這些行:

[1]-  Exit 1                  python -c "raise ValueError('test')"
[2]+  Exit 1                  python -c "raise ValueError('test')"

這是 bash shell 的一項功能,通常僅在interactive模式下可用,即當您在終端中鍵入命令時。 如果您檢查bash 源代碼,您可以看到它在打印到 stdout/stderr 之前顯式檢查了模式。

在較新版本的 bash 中,您無法在腳本中進行設置:請參閱https://unix.stackexchange.com/a/364618 但是,您可以在啟動腳本時自行設置:

p = Popen(['/bin/bash -i ./testbash.sh'], stdout=PIPE, stderr=STDOUT, bufsize=1, close_fds=ON_POSIX, shell=True)

我會注意到這僅在 Python3 上對我有用 - Python2 只獲得部分輸出。 您正在使用的 Python 版本尚不清楚,但考慮到 Python2 現在已經結束,我們可能都應該嘗試切換到 Python3。

至於 bash 腳本,即使設置了交互模式,您似乎也必須更改wait獲得該輸出的方式:

#!/bin/bash
export MAX_SEED=2
echo "Start test"
pids=""

for seed in `seq 1 ${MAX_SEED}`
do
    python -c "raise ValueError('test')" &
    pids="${pids} $!"
done
echo "pids: ${pids}"
wait -n $pids
wait -n $pids
ret=$?
echo "End test"
exit $ret

正常wait對我不起作用(Ubuntu 18.04),但wait -n似乎有效 - 但由於它只等待下一個工作完成,我只調用一次就得到了不一致的輸出。 為每個啟動的作業調用wait -n似乎可以解決問題,但可能應該重構程序流以循環等待與啟動作業相同的次數。

另請注意,要更改腳本的返回碼,Philippe 的答案有正確的方法 - $? 變量具有失敗的最新命令的返回代碼,然后您可以將其傳遞給exit (Python 版本的另一個區別:Python2 返回127而 Python3 為我返回1 )如果您需要每個作業的返回值,一種方法可能是解析交互式作業退出行中的值。

只是猜測 - 以空字符/空格開頭的行是否不會被您的邏輯識別為行。

也許這個縮進是問題所在。 另一種選擇是,有一個選項卡或類似的東西,ascii 解碼可能會失敗。

這就是我通常使用子流程的方式:

import subprocess

with subprocess.Popen(["./test.sh"], shell=True, stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.PIPE) as p:
    error = p.stderr.read().decode()
    std_out = p.stdout.read().decode()
    if std_out:
        print(std_out)
    if error:
        print("Error message: {}".format(error))

在這里,您可以解碼和讀取標准輸出和標准錯誤。 你得到所有東西,但順序不同,如果這是一個問題,我不知道。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM