使用 Python 子进程实时记录到文件



我希望这真的很简单,但我无法解决这个问题。

我正在尝试将DD成像子进程的输出实时写入日志文件 - 我正在使用DD v 8.25,您可以使用写入stderr'status=progress'选项从中获得定期进度更新。

我可以通过将文件对象传递给stderr来实时记录完整的输出,即

log_file = open('mylog.log', 'a')
p = subprocess.Popen['dd command...'], stdout=None, stderr=log_file)

。但我更愿意先从stderr中截取字符串,以便我可以在写入文件之前对其进行解析。

我已经尝试过线程,但我似乎无法让它写入,或者如果它这样做,它只在过程结束时而不是在过程中进行。

我是一个蟒蛇菜鸟,所以示例代码将不胜感激。谢谢!

更新 - 现在工作 (ISH)

我查看了 J.F. Sebastian 建议的链接并找到了有关使用线程的帖子,因此之后我使用"kill -USR1"技巧让 DD 将进度发布到 stderr,然后我可以拿起:

#! /usr/bin/env python
from subprocess import PIPE, Popen
from threading import Thread
from queue import Queue, Empty
import time
q = Queue()
def parsestring(mystr):
    newstring = mystr[0:mystr.find('bytes')]
    return newstring
def enqueue(out, q):
    for line in proc1.stderr:
        q.put(line)
    out.close()
def getstatus():
    while proc1.poll() == None:
        proc2 = Popen(["kill -USR1 $(pgrep ^dd)"], bufsize=1, shell=True)
        time.sleep(2)
with open("log_file.log", mode="a") as log_fh:
    start_time = time.time()
    #start the imaging
    proc1 = Popen(["dd if=/dev/sda1 of=image.dd bs=524288 count=3000"], bufsize=1, stderr=PIPE, shell=True)
    #define and start the queue function thread
    t = Thread(target=enqueue, args=(proc1.stderr, q))
    t.daemon = True
    t.start()
    #define and start the getstatus function thread
    t_getstatus = Thread(target=getstatus, args=())
    t_getstatus.daemon
    t_getstatus.start()
    #get the string from the queue
    while proc1.poll() == None:
        try: nline = q.get_nowait()
        except Empty:
            continue
        else:
            mystr = nline.decode('utf-8')           
            if mystr.find('bytes') > 0:
                log_fh.write(str(time.time()) + ' - ' + parsestring(mystr))
                log_fh.flush()
        #put in a delay
        #time.sleep(2)
    #print duration
    end_time=time.time()
    duration=end_time-start_time
    print('Took ' + str(duration) + ' seconds')     

唯一的问题是我无法弄清楚如何提高性能。我只需要它每 2 秒左右报告一次状态,但增加时间延迟会增加成像时间,这是我不想要的。不过,这是另一篇文章的问题...

感谢J.F.塞巴斯蒂安和阿里。

在这个例子中,可以(使用 python 3)从 stderr 流式传输到控制台:

#! /usr/bin/env python
from subprocess import Popen, PIPE
# emulate a program that write on stderr
proc = Popen(["/usr/bin/yes 1>&2 "],  bufsize=512, stdout=PIPE, stderr=PIPE, shell=True)
r = b""
for line in proc.stderr:
    r += line
    print("current line", line, flush=True)

要流式传输到文件:

#! /usr/bin/env python
from subprocess import Popen, PIPE
with open("log_file.log", mode="b",  encoding="utf8") as log_fh:
        proc = Popen(["/usr/bin/yes 1>&2 "],  bufsize=512, stdout=PIPE, stderr=PIPE, shell=True)
        r = b""
        # proc.stderr is an io.TextIOWrapper file-like obj
    # iter over line
        for line in proc.stderr:
                r += line
                # print("current line", line, flush=True)
                log_fh.write(line) # file open in binary mode
                # log_fh.write(line.decode("utf8")) # for text mode
                log_fh.flush() # flush the content

要在终端中显示dd的进度报告并将(解析)输出保存到日志文件中:

#!/usr/bin/env python3
import io
from subprocess import PIPE, Popen
from time import monotonic as timer
cmd = "dd if=/dev/sda1 of=image.dd bs=524288 count=3000 status=progress".split()
with Popen(cmd, stderr=PIPE) as process, 
        open("log_file.log", "a") as log_file:
    start_time = timer()
    for line in io.TextIOWrapper(process.stderr, newline=''):
        print(line, flush=True, end='')  # no newline ('n')
        if 'bytes' in line:
            # XXX parse line here, add flush=True if necessary
            print(line, file=log_file)
    # print duration
    print('Took {duration} seconds'.format(duration=timer() - start_time))

注意

  • 没有shell=True:这里不需要外壳。 Popen()可以直接运行dd
  • 没有线程,队列:这里不需要它们
  • 请不要在这里使用while proc1.poll() == None您不需要
  • 它(如果proc1.poll()不是 None,您将在 proc1.stderr 上看到 EOF)。您可能会丢失数据(即使进程已经退出,也可能有缓冲的内容)。无关:如果您需要与None进行比较;使用is None而不是== None
  • io.TextIOWrapper(newline='')启用文本模式(它使用 locale.getpreferredencoding(False) )和它也将'r'视为换行符
  • 使用默认bufsize=-1(请参阅io.DEFAULT_BUFFER_SIZE

最新更新