python matplotlib放大 - 來自subprocess命令的實時輸出



matplotlib文字 python用法 (10)

我使用Python腳本作為流體動力學代碼的驅動程序。 當運行模擬時,我使用subprocess.Popen來運行代碼,將stdout和stderr的輸出收集到subprocess.PIPE ---然後我可以打印(並保存到日誌文件)輸出信息並檢查是否有錯誤。 問題是,我不知道代碼是如何進行的。 如果我直接從命令行運行它,它會給出關於它在什麼時候迭代的信息,什麼時候,什麼是下一個時間步,等等。

有沒有一種方法可以存儲輸出(用於記錄和錯誤檢查),還可以生成實時流輸出?

我的代碼的相關部分:

ret_val = subprocess.Popen( run_command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True )
output, errors = ret_val.communicate()
log_file.write(output)
print output
if( ret_val.returncode ):
    print "RUN failed\n\n%s\n\n" % (errors)
    success = False

if( errors ): log_file.write("\n\n%s\n\n" % errors)

本來我是通過tee管道run_command ,以便副本直接進入日誌文件,並且流仍然直接輸出到終端 - 但這樣我就不能存儲任何錯誤(以我的知識)。

編輯:

臨時解決方案:

ret_val = subprocess.Popen( run_command, stdout=log_file, stderr=subprocess.PIPE, shell=True )
while not ret_val.poll():
    log_file.flush()

然後在另一個終端中運行tail -f log.txt (st log_file = 'log.txt' )。


Answers

它看起來像線緩衝輸出將適用於你,在這種情況下,類似下面的東西可能適合。 (注意:它未經測試。)這只會實時提供子進程的stdout。 如果你想同時實現stderr和stdout,你必須在select做更複雜的事情。

proc = subprocess.Popen(run_command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
while proc.poll() is None:
    line = proc.stdout.readline()
    print line
    log_file.write(line + '\n')
# Might still be data on stdout at this point.  Grab any
# remainder.
for line in proc.stdout.read().split('\n'):
    print line
    log_file.write(line + '\n')
# Do whatever you want with proc.stderr here...

我試過的所有上述解決方案都失敗了,要么將stderr和stdout輸出(多個管道)分開,要么在操作系統管道緩衝區已滿時發生永久性阻塞,這會在您運行的命令輸出速度過快時發生(有關python的警告poll()子過程手冊)。 我發現的唯一可靠方法是通過select,但這是一個posix-only解決方案:

import subprocess
import sys
import os
import select
# returns command exit status, stdout text, stderr text
# rtoutput: show realtime output while running
def run_script(cmd,rtoutput=0):
    p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    poller = select.poll()
    poller.register(p.stdout, select.POLLIN)
    poller.register(p.stderr, select.POLLIN)

    coutput=''
    cerror=''
    fdhup={}
    fdhup[p.stdout.fileno()]=0
    fdhup[p.stderr.fileno()]=0
    while sum(fdhup.values()) < len(fdhup):
        try:
            r = poller.poll(1)
        except select.error, err:
            if err.args[0] != EINTR:
                raise
            r=[]
        for fd, flags in r:
            if flags & (select.POLLIN | select.POLLPRI):
                c = os.read(fd, 1024)
                if rtoutput:
                    sys.stdout.write(c)
                    sys.stdout.flush()
                if fd == p.stderr.fileno():
                    cerror+=c
                else:
                    coutput+=c
            else:
                fdhup[fd]=1
    return p.poll(), coutput.strip(), cerror.strip()

如果你能夠使用第三方庫,你可能可以使用sarge東西(披露:我是它的維護者)。 該庫允許從子進程輸出流的非阻塞訪問 - 它在subprocess模塊上分層。


import sys
import subprocess

f = open("log.txt","w+")
process = subprocess.Popen(command, stdout=subprocess.PIPE)
for line in iter(process.stdout.readline, ''):
    sys.stdout.write(line)
    f.write(line.replace("\n",""))
f.close()

我們也可以使用默認的文件迭代器來讀取標準輸出,而不是使用帶readline()的iter結構。

import subprocess
import sys
process = subprocess.Popen(your_command, stdout=subprocess.PIPE)
for line in process.stdout:
    sys.stdout.write(line)

我會在這裡出去,建議使用check_call 。 根據文檔,它啟動了阻止操作,並且非常適合此目的。

帶參數運行命令。 等待命令完成。 如果返回碼為零,則返回,否則引發CalledProcessError。 CalledProcessError對象將在returncode屬性中具有返回碼。

cmd = "some_crazy_long_script.sh"
args = {
    'shell': True,
    'cwd': if_you_need_it
}

subprocess.check_call(cmd, **args)

一個好的但“重量級”的解決方案是使用扭曲 - 看到底部。

如果你願意只使用stdout,那麼沿著這些線應該工作:

import subprocess
import sys
popenobj = subprocess.Popen(["ls", "-Rl"], stdout=subprocess.PIPE)
while not popenobj.poll():
   stdoutdata = popenobj.stdout.readline()
   if stdoutdata:
      sys.stdout.write(stdoutdata)
   else:
      break
print "Return code", popenobj.returncode

(如果你使用read()它會嘗試讀取整個沒有用的“文件”,我們真正可以在這裡使用的是讀取管道中所有數據的東西)

人們也可以試著用線程來解決這個問題,例如:

import subprocess
import sys
import threading

popenobj = subprocess.Popen("ls", stdout=subprocess.PIPE, shell=True)

def stdoutprocess(o):
   while True:
      stdoutdata = o.stdout.readline()
      if stdoutdata:
         sys.stdout.write(stdoutdata)
      else:
         break

t = threading.Thread(target=stdoutprocess, args=(popenobj,))
t.start()
popenobj.wait()
t.join()
print "Return code", popenobj.returncode

現在我們可以添加stderr以及擁有兩個線程。

但是請注意,子進程文檔不鼓勵直接使用這些文件,並建議使用communicate() (主要涉及死鎖,我認為這不是上述問題),並且解決方案有點不靈活,所以看起來好像進程模塊不是完全可以勝任這項工作 (另見: http://www.python.org/dev/peps/pep-3145/ : http://www.python.org/dev/peps/pep-3145/ ),我們需要看看其他的東西。

更複雜的解決方案是使用Twisted ,如下所示: https://twistedmatrix.com/documents/11.1.0/core/howto/process.html : https://twistedmatrix.com/documents/11.1.0/core/howto/process.html

你用Twisted做這件事的方法是使用reactor.spawnprocess()創建你的進程,並提供一個ProcessProtocol ,然後異步處理輸出。 Twisted示例Python代碼位於: https://twistedmatrix.com/documents/11.1.0/core/howto/listings/process/process.py : https://twistedmatrix.com/documents/11.1.0/core/howto/listings/process/process.py


有兩種方法可以做到這一點,無論是通過readreadline函數創建一個迭代器並執行:

import subprocess
import sys
with open('test.log', 'w') as f:
    process = subprocess.Popen(your_command, stdout=subprocess.PIPE)
    for c in iter(lambda: process.stdout.read(1), ''):
        sys.stdout.write(c)
        f.write(c)

要么

import subprocess
import sys
with open('test.log', 'w') as f:
    process = subprocess.Popen(your_command, stdout=subprocess.PIPE)
    for line in iter(process.stdout.readline, ''):
        sys.stdout.write(line)
        f.write(line)

或者你可以創建一個reader和一個writer文件。 將writer傳給Popen並從reader那裡reader

import io
import time
import subprocess
import sys

filename = 'test.log'
with io.open(filename, 'wb') as writer, io.open(filename, 'rb', 1) as reader:
    process = subprocess.Popen(command, stdout=writer)
    while process.poll() is None:
        sys.stdout.write(reader.read())
        time.sleep(0.5)
    # Read the remaining
    sys.stdout.write(reader.read())

這樣你就可以將數據寫入test.log以及標準輸出。

文件方法的唯一好處是你的代碼不會被阻塞。 因此,您可以隨時做所需的任何事情,並以非阻塞的方式隨時reader 。 當使用PIPEreadread函數將被阻塞,直到任一個字符被寫入管道或一條線被分別寫入管道。


執行摘要(或“tl; dr”版本):最多只有一個subprocess.PIPE很簡單,否則很難。

這可能是時間來解釋subprocess.Popen是怎麼做的。

(注意:這是為了Python 2.x,儘管3.x很相似,而且我對Windows的變體很模糊,我理解POSIX的東西要好得多。)

Popen函數需要同時處理零到三個I / O流。 像往常一樣,這些被標記為stdinstdoutstderr

您可以提供:

  • None ,表示您不想重定向流。 它將像往常一樣繼承這些。 請注意,至少在POSIX系統上,這並不意味著它會使用Python的sys.stdout ,只是Python的實際標準輸出; 最後看演示。
  • 一個int值。 這是一個“原始”文件描述符(至少在POSIX中)。 (注意: PIPESTDOUT在內部實際上是int ,但是“不可能”描述符,-1和-2。)
  • 流 - 真的,任何帶有fileno方法的對象。 Popen將使用stream.fileno()找到該流的描述符,然後繼續處理int值。
  • subprocess.PIPE ,表明Python應該創建一個管道。
  • subprocess.STDOUT (僅適用於stderr ):告訴Python使用與stdout相同的描述符。 如果您為stdout提供了(非None )值,則這是唯一有意義的,即使如此,只有在您設置stdout=subprocess.PIPE時才需要 。 (否則,你可以只提供你提供給stdout參數,例如, Popen(..., stdout=stream, stderr=stream) 。)

最簡單的情況(無管道)

如果你什麼都不重定向(把所有三個都作為默認的None值或者提供顯式None ), Pipe很容易。 它只需要分離子流程並讓它運行。 或者,如果您重定向到非PIPE - int或流的fileno() - 它仍然很容易,因為操作系統完成所有工作。 Python只需要分離子進程,將stdin,stdout和/或stderr連接到提供的文件描述符。

仍然簡單的情況:一個管道

如果只重定向一個流, Pipe仍然很容易。 我們一次選擇一個流並觀看。

假設你想提供一些stdin ,但讓stdoutstderr不重定向,或者轉到一個文件描述符。 作為父進程,您的Python程序只需使用write()將數據發送到管道。 你可以自己做這個,例如:

proc = subprocess.Popen(cmd, stdin=subprocess.PIPE)
proc.stdin.write('here, have some data\n') # etc

或者您可以將stdin數據傳遞給proc.communicate() ,然後執行stdin.write顯示的stdin.write 。 沒有輸出回來所以communicate()只有一個其他真正的工作:它也關閉你的管道。 (如果你不調用proc.communicate()你必須調用proc.stdin.close()來關閉管道,這樣子proc.stdin.close()就知道沒有更多的數據通過了。)

假設你想捕獲stdout但保留stdinstderr 。 再次,這很容易:只需調用proc.stdout.read() (或等價的),直到沒有更多的輸出。 由於proc.stdout()是一個普通的Python I / O流,所以你可以使用它的所有常規結構,比如:

for line in proc.stdout:

或者,您可以再次使用proc.communicate() ,它只是為您執行read()

如果你只想捕獲stderr ,它和stdout

事情變得困難之前還有一個竅門。 假設你想捕獲stdout ,並捕獲stderr但是在stdout的同一個管道上:

proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)

在這種情況下, subprocess “作弊”! 嗯,它必須這樣做,所以它並不是真正的作弊:它將stdout和stderr都引導到(單個)管道描述符(它反饋到其父進程(Python)進程)的子進程中。 在父級方面,再次只有一個管道描述符用於讀取輸出。 所有“stderr”輸出都顯示在proc.stdout ,如果調用proc.communicate() ,則stderr結果(元組中的第二個值)將為None ,而不是字符串。

困難的情況:兩個或更多的管道

當你想使用至少兩個管道時,所有問題都會出現。 實際上, subprocess進程代碼本身就有這麼一點:

def communicate(self, input=None):
    ...
    # Optimization: If we are only using one pipe, or no pipe at
    # all, using select() or threads is unnecessary.
    if [self.stdin, self.stdout, self.stderr].count(None) >= 2:

但是,唉,在這裡我們至少已經製作了兩個,也許是三個不同的管道,所以count(None)返回1或0.我們必須以艱難的方式做事。

在Windows上,它使用threading.Thread來累積self.stdoutself.stderr結果,並且父線程傳遞self.stdin輸入數據(然後關閉管道)。

在POSIX上,這將使用poll如果可用),否則select ,以累積輸出並提供stdin輸入。 所有這些都在(單個)父進程/線程中運行。

此處需要線程或輪詢/選擇以避免死鎖。 例如,假設我們已將所有三個流重定向到三個單獨的管道。 進一步假設在寫入過程暫停之前可以將多少數據填充到管道中有一個小的限制,等待讀取過程從另一端“清理”管道。 我們將這個小限制設置為單個字節,僅用於說明。 (事實上這是事情的方式,除了限制比一個字節大得多。)

如果父(Python)進程嘗試寫入幾個字節 - 比如說'go\n'proc.stdin ,則第一個字節進入,然後第二個字節導致Python進程掛起,等待子進程讀取第一個字節,清空管道。

同時,假設子進程決定打印友好的“你好!不要驚慌!” 問候。 H進入它的stdout管道,但是e導致它暫停,等待它的父節點讀取H ,清空stdout管道。

現在我們陷入了困境:Python進程處於睡眠狀態,等待完成說“去”,並且子進程還在睡夢中,等待完成說“Hello!Do not Panic!”。

subprocess.Popen代碼避免了線程或選擇/輪詢的這個問題。 當字節可以通過管道時,他們走。 當它們不能時,只有一個線程(不是整個進程)必須睡眠 - 或者在select / poll的情況下,Python進程同時等待“可寫入”或“可用數據”,寫入進程的stdin只有當有空間時,並且只有在數據準備就緒時才讀取它的stdout和/或stderr。 proc.communicate()代碼(實際上_communicate情況下處理的位置)返回一旦所有標準輸入數據(如果有)已發送和所有標準輸出和/或標準錯誤數據已累積。

如果你想在兩個不同的管道上讀取stdoutstderr (不管任何stdin重定向),你也需要避免死鎖。 這裡的死鎖情況是不同的 - 當你從stdout提取數據時,子進程寫入長的stderr ,反之亦然 - 但它仍然存在。

演示

我承諾說明,未重定向的Python subprocess進程將寫入底層的stdout,而不是sys.stdout 。 所以,這是一些代碼:

from cStringIO import StringIO
import os
import subprocess
import sys

def show1():
    print 'start show1'
    save = sys.stdout
    sys.stdout = StringIO()
    print 'sys.stdout being buffered'
    proc = subprocess.Popen(['echo', 'hello'])
    proc.wait()
    in_stdout = sys.stdout.getvalue()
    sys.stdout = save
    print 'in buffer:', in_stdout

def show2():
    print 'start show2'
    save = sys.stdout
    sys.stdout = open(os.devnull, 'w')
    print 'after redirect sys.stdout'
    proc = subprocess.Popen(['echo', 'hello'])
    proc.wait()
    sys.stdout = save

show1()
show2()

運行時:

$ python out.py
start show1
hello
in buffer: sys.stdout being buffered

start show2
hello

請注意,如果添加stdout=sys.stdout ,則第一個例程將失敗,因為StringIO像沒有fileno 。 如果添加stdout=sys.stdout那麼第二個會省略hello ,因為sys.stdout已被重定向到os.devnull

(如果重定向Python的文件描述符-1,則子open(os.devnull, 'w') 遵循該重定向open(os.devnull, 'w')調用將生成fileno()大於2的流。)


import os
os.system("your command")

請注意,這是危險的,因為未清除該命令。 我把它留給你去谷歌搜索關於'os'和'sys'模塊的相關文檔。 有許多函數(exec *和spawn *)可以執行類似的操作。





python shell logging error-handling subprocess