Python,同时运行命令行工具

18

我正在使用 Python 作为脚本语言进行一些数据处理和调用命令行工具进行数字计算。因为这些命令行工具是独立的,所以我希望并行运行它们。当一个命令行工具完成时,我可以从输出文件中收集其结果。因此,我还需要一些同步机制来通知我的主 Python 程序一个任务已经完成,以便结果可以被解析到我的主程序中。

目前,我使用 os.system(),这对单线程很好用,但不能并行化。

谢谢!


PythonеѓєthreadingеТМmultiprocessingжЬЙеЊИе•љзЪДжФѓжМБгАВдљ†еѓєеЃГдїђжЬЙдїАдєИеЕЈдљУзЪДйЧЃйҐШпЉЯ - Niklas B.
2个回答

15

如果您想将命令行工具作为单独的进程运行,只需使用os.system(或更好的:subprocess模块)异步地启动它们。在Unix/linux/macos上:

subprocess.call("command -flags arguments &", shell=True)

在Windows操作系统中:

subprocess.call("start command -flags arguments", shell=True)

关于如何判断一条命令何时完成:在Unix下,你可以使用wait等功能,但如果你正在编写命令行脚本,我建议让它们将信息写入文件,并从调用Python脚本监控该文件。

@James Youngman为您的第二个问题提出了一个解决方案:同步。如果您想从Python中控制进程,可以使用Popen以异步方式启动它们。

p1 = subprocess.Popen("command1 -flags arguments")
p2 = subprocess.Popen("command2 -flags arguments")

注意,如果您使用Popen并且您的进程向stdout写入大量数据,则程序将会死锁。请务必将所有输出重定向到日志文件中。

p1p2是您可以用来跟踪您的进程的对象。p1.poll()不会阻塞,但如果进程仍在运行,则会返回None。它将在进程完成时返回退出状态,因此您可以检查它是否为零。

while True:
    time.sleep(60)
    for proc in [p1, p2]:
        status = proc.poll()
        if status == None:
            continue
        elif status == 0:
            # harvest the answers
        else:
            print "command1 failed with status", status
上述代码只是一个模型:按照现有写法,它永远不会退出,并且会一直“收集”已完成进程的结果。但我相信你已经理解了其意义。

1
测试将更加通用和简洁,如 if any(p.wait() for p in [p1, p2]):… - Eric O. Lebigot
大家好,OP想异步运行计算并收集结果。他希望有一种方法“通知我的主Python程序一个任务已经完成”。wait()将会阻塞直到所有子进程都完成。 - alexis
@James Youngman:抱歉撤销了你的更改。我没有预料到它们会从历史记录中消失。我已经将它们包含在答案中(正如您所看到的,这是一个单独的解决方案,有着不同的问题)。 - alexis
我们不知道这个情况,并且@Yin谈到了“一个任务”的完成。我可以想象出很多使用情况:也许他是限制同时运行的作业数量,需要启动下一个作业;或者完成的作业会被输入到数据库中;或者这是一个打印服务器,当作业完成时应该弹出通知;或者其他任何情况。 - alexis

13
使用模块中的对象。您可以使用例如进行并行处理。一个示例是我的markphotos脚本(参见下文),其中一个函数被多次调用以并行处理每个图片。
#! /usr/bin/env python
# -*- coding: utf-8 -*-
# Adds my copyright notice to photos.
#
# Author: R.F. Smith <rsmith@xs4all.nl>
# $Date: 2012-10-28 17:00:24 +0100 $
#
# To the extent possible under law, Roland Smith has waived all copyright and
# related or neighboring rights to markphotos.py. This work is published from
# the Netherlands. See http://creativecommons.org/publicdomain/zero/1.0/

import sys
import subprocess
from multiprocessing import Pool, Lock
from os import utime, devnull
import os.path
from time import mktime

globallock = Lock() 

def processfile(name):
    """Adds copyright notice to the file.

    Arguments:
    name -- file to modify
    """
    args = ['exiftool', '-CreateDate', name]
    createdate = subprocess.check_output(args)
    fields = createdate.split(":") #pylint: disable=E1103
    year = int(fields[1])
    cr = "R.F. Smith <rsmith@xs4all.nl> http://rsmith.home.xs4all.nl/"
    cmt = "Copyright © {} {}".format(year, cr)
    args = ['exiftool', '-Copyright="Copyright (C) {} {}"'.format(year, cr),
            '-Comment="{}"'.format(cmt), '-overwrite_original', '-q', name]
    rv = subprocess.call(args)
    modtime = int(mktime((year, int(fields[2]), int(fields[3][:2]),
                          int(fields[3][3:]), int(fields[4]), int(fields[5]),
                          0,0,-1)))
    utime(name, (modtime, modtime))
    globallock.acquire()
    if rv == 0:
        print "File '{}' processed.".format(name)
    else:
        print "Error when processing file '{}'".format(name)
    globallock.release()

def checkfor(args):
    """Make sure that a program necessary for using this script is
    available.

    Arguments:
    args -- list of commands to pass to subprocess.call.
    """
    if isinstance(args, str):
        args = args.split()
    try:
        with open(devnull, 'w') as f:
            subprocess.call(args, stderr=subprocess.STDOUT, stdout=f)
    except:
        print "Required program '{}' not found! exiting.".format(args[0])
        sys.exit(1)

def main(argv):
    """Main program.

    Arguments:
    argv -- command line arguments
    """
    if len(argv) == 1:
        binary = os.path.basename(argv[0])
        print "Usage: {} [file ...]".format(binary)
        sys.exit(0)
    checkfor(['exiftool',  '-ver'])
    p = Pool()
    p.map(processfile, argv[1:])
    p.close()

if __name__ == '__main__':
    main(sys.argv)

谢谢!似乎在每个进程中,我可以使用 os.system 来调用我的命令行? - Yin Zhu
1
最好使用subprocess来实现。请参考链接中的示例。 - Roland Smith
@RolandSmith - 你能恢复到markphotos的链接或在这里添加来源吗? - keflavich

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接