将subprocess.call的输出传递给进度条

4 投票
4 回答
5104 浏览
提问于 2025-04-15 13:38

我正在使用growisofs通过我的Python应用程序来刻录一个iso文件。我有两个类,分别在两个不同的文件中;GUI()(main.py)和Boxblaze()(core.py)。GUI()负责构建窗口和处理所有事件,而Boxblaze()则包含GUI()调用的所有方法。

现在,当用户选择了要刻录的设备和文件后,我需要调用一个方法,这个方法会执行以下命令:

growisofs -use-the-force-luke=dao -use-the-force-luke=break:1913760 -dvd-compat -speed=2 -Z /burner/device=/full/path/to.iso

这个命令的输出应该类似于下面这样:

Executing 'builtin_dd if=/home/nevon/games/Xbox 360 isos/The Godfather 2/alls-tgod2.iso of=/dev/scd0 obs=32k seek=0'
/dev/scd0: "Current Write Speed" is 2.5x1352KBps.
#more of the lines below, indicating progress.
7798128640/7835492352 (99.5%) @3.8x, remaining 0:06 RBU 100.0% UBU  99.8%
7815495680/7835492352 (99.7%) @3.8x, remaining 0:03 RBU  59.7% UBU  99.8%
7832862720/7835492352 (100.0%) @3.8x, remaining 0:00 RBU   7.9% UBU  99.8%
builtin_dd: 3825936*2KB out @ average 3.9x1352KBps
/dev/burner: flushing cache
/dev/burner: closing track
/dev/burner: closing disc

这个命令是在Boxblaze()中的一个叫burn()的方法里运行的。它看起来就是这样:

def burn(self, file, device):
    subprocess.call(["growisofs", '-dry-run', "-use-the-force-luke=dao", "-use-the-force-luke=break:1913760", "-dvd-compat", "-speed=2", "-Z",  device +'='+ file])

现在我有以下几个问题:

  1. 我该如何从输出中获取进度(括号里的百分比),并让我的进度条“跟随”这个进度?我的进度条是在GUI()类中调用的,像这样:

    get = builder.get_object

    self.progress_window = get("progressWindow")

    self.progressbar = get("progressbar")

  2. 我是否需要在一个单独的线程中运行这个命令,以便让GUI保持响应(这样我才能更新进度条,并允许用户在需要时取消刻录)?如果是的话,我该如何做到这一点,同时还能将进度传递给进度条?


如果你感兴趣,完整的代码可以在Launchpad上找到。如果你安装了bazaar,只需运行:

bzr branch lp:boxblaze

哦,顺便说一下,这个应用程序只适用于Linux,所以不必担心跨平台兼容性。

4 个回答

0

你能给从子进程读取数据设置一个超时时间吗?我想应该可以,因为我的子进程模块就是为了这个设计的。你可以用这个模块来执行growiso.read(.1),然后从输出数据(或者错误数据)中解析并显示百分比。

1

要得到你想要的输出,你需要使用 subprocess.Popen 这个调用。记得加上 stdout = subprocess.PIPE

(第二个问题)

你可能需要一个单独的线程,除非你用的图形界面框架可以在正常循环中选择文件描述符。

你可以让一个后台线程读取这个管道,处理数据(提取进度),然后把结果传给图形界面线程。

## You might have to redirect stderr instead/as well
proc = sucprocess.Popen(command,stdout=subprocess.PIPE)
for line in proc.stdout.readlines():
    ## might not work - not sure about reading lines
    ## Parse the line to extract progress
    ## Pass progress to GUI thread

补充:

我不想浪费很多光盘来测试,所以还没运行这个代码,不过根据你的评论,看起来它并没有把信息输出到标准输出(stdout),而是输出到标准错误(stderr)。

我建议你直接在命令行运行一个示例命令,并把标准输出和标准错误重定向到不同的文件。

growisofs [options] >stdout 2>stderr

这样你就可以搞清楚哪些信息是从标准输出出来的,哪些是从标准错误出来的。

如果你想要的信息在标准错误里,那就把 stdout=subprocess.PIPE 改成 stderr=subprocess.PIPE,看看这样是否更有效。

补充2:

你使用线程的方式不太对——你应该是启动线程,而不是直接运行它。

还有:

gtk.gdk.threads_init()
threading.Thread.__init__(self)

这很奇怪——初始化调用应该放在初始化方法里,我觉得你不需要把它变成一个gtk线程。

你调用 run() 方法的方式本身也很奇怪:

core.Burning.run(self.burning, self.filechooser.get_filename(), self.listofdevices[self.combobox.get_active()])

应该通过对象来调用实例方法:

self.burning.run(self.filechooser.get_filename(), self.listofdevices[self.combobox.get_active()])

(不过你应该有一个 __init__() 方法)

我觉得你是在还不会走路的时候就想跑。先试着写一些简单的线程代码,然后写一些简单的代码来运行 growisofs 并解析输出,接着再写一些简单的 gtk+后台线程代码,最后再把它们结合在一起。其实,首先要写一些简单的面向对象的代码,这样你才能理解方法和对象。

例如,你在 Python 中创建的所有类都应该是新式类,你应该在初始化方法中调用超类的初始化器等等。

3

你可以使用 glib.io_add_watch() 来监控与子进程对象的标准输出(stdout)和标准错误(stderr)连接的管道。

proc = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
stdout_id = glib.io_add_watch(proc.stdout, glib.IO_IN|glib.IO_HUP, stdout_cb)
stderr_id = glib.io_add_watch(proc.stderr, glib.IO_IN|glib.IO_HUP, stderr_cb)

当回调函数被调用时,它应该检查条件,然后从管道中读取所有数据,并处理这些数据以更新进度条。如果应用程序对输入输出进行了缓冲,你可能需要使用伪终端(pty),让它误以为自己连接到了一个终端,这样它就会一次输出一行数据。

撰写回答