![](/img/trans.png)
[英]How to run python script within the python interpreter in 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。
立即獲得輸出並不重要。 延遲幾秒鍾就可以了。 此外,如果輸出順序有點混亂,這也無關緊要。 重要的是獲取所有輸出( stdout
和stderr
)。
也許有一種更簡單的方法來獲取從 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.