【问题标题】:Python subprocess output blocking on application prompt应用程序提示符上的 Python 子进程输出阻塞
【发布时间】:2018-01-23 22:21:42
【问题描述】:

我正在使用子进程库在 python 脚本中运行 jirashell。我目前在实时打印输出时遇到问题。当我运行 jirashell 时,它会输出信息而不是提示用户(y/n)。在我输入“y”或“n”之前,子流程不会在提示之前打印出信息。

我使用的代码是

_consumer_key = "justin-git"
_cmd = "jirashell -s {0} -od -k {1} -ck {2} -pt".format(JIRA_SERVER,
    _rsa_private_key_path, _consumer_key)
p = subprocess.Popen(_cmd.split(" "), stdout=subprocess.PIPE, 
    stderr=subprocess.PIPE, bufsize=0)

out, err = p.communicate() # Blocks here

print out
print err

输出是这样的:

n # I enter a "n" before program will print output.
Output:
Request tokens received.
    Request token:        asqvavefafegagadggsgewgqqegqgqge
    Request token secret: asdbresbdfbrebsaerbbsbdabweabfbb
Please visit this URL to authorize the OAuth request:
        http://localhost:8000/plugins/servlet/oauth/authorize?oauth_token=zzzzzzzzzzzzzzzzzzzzzzzzzzzzz
Have you authorized this program to connect on your behalf to http://localhost:8000? (y/n)
Error:
Abandoning OAuth dance. Your partner faceplants. The audience boos. You feel shame.

有谁知道我如何让它在提示之前打印输出而不是等待输入 y/n?请注意,我还需要能够存储命令产生的输出,这样“os.system()”就不起作用了……

编辑:

看起来在 jirashell 内部有一部分代码正在等待输入,这导致了阻塞。在将某些内容传递到此输入之前,不会输出任何内容...仍在研究如何解决此问题。我正在尝试将我需要的部分代码移动到我的应用程序中。这个解决方案看起来并不优雅,但我现在看不到任何其他方式。

approved = input(
    'Have you authorized this program to connect on your behalf to {}? (y/n)'.format(server))

【问题讨论】:

    标签: python subprocess pipe output stdout


    【解决方案1】:

    打印和缓存标准输出的方法:

    你可以使用一个线程来读取你的子进程的标准输出,而主线程被阻塞直到子进程完成。下面的例子将运行程序other.py,它看起来像

    #!/usr/bin/env python3
    
    print("Hello")
    x = input("Type 'yes': ")
    

    例子:

    import threading
    import subprocess
    import sys
    
    class LivePrinter(threading.Thread):
        """
        Thread which reads byte-by-byte from the input stream and writes it to the
        standard out. 
        """
        def __init__(self, stream):
            self.stream = stream
            self.log = bytearray()
            super().__init__()
    
        def run(self):
            while True:
                # read one byte from the stream
                buf = self.stream.read(1)
    
                # break if end of file reached
                if len(buf) == 0:
                    break
    
                # save output to internal log
                self.log.extend(buf)
    
                # write and flush to main standard output
                sys.stdout.buffer.write(buf)
                sys.stdout.flush()
    
    # create subprocess
    p = subprocess.Popen('./other.py', stdout=subprocess.PIPE)
    
    # Create reader and start the thread
    r = LivePrinter(p.stdout)
    r.start()
    
    # Wait until subprocess is done
    p.wait()
    
    # Print summary
    print(" -- The process is done now -- ")
    print("The standard output was:")
    print(r.log.decode("utf-8"))
    

    LivePrinter 类从子进程读取每个字节并将其写入标准输出。 (我不得不承认,这不是最有效的方法,而是更大的缓冲区大小块,LiveReader 直到缓冲区已满,即使子进程正在等待提示的答案。)由于字节被写入sys.stdout.buffer,多字节utf-8字符应该没有问题。

    LiveReader 类还将子进程的完整输出存储在变量log 中以供以后使用。

    正如this 的回答总结的那样,与子进程分叉后启动一个线程是省事的。


    原答案有问题,提示行未结束时:

    输出延迟是因为communicate() 会阻止脚本的执行,直到子进程完成 (https://docs.python.org/3/library/subprocess.html#subprocess.Popen.communicate)。

    您可以读取和打印子进程的标准输出,同时使用stdout.readline 执行它。有一些关于缓冲的问题,需要这个相当复杂的iter(process.stdout.readline, b'') 构造。以下示例使用gpg2 --gen-key,因为此命令启动了一个交互式工具。

    import subprocess
    
    process = subprocess.Popen(["gpg2", "--gen-key"], stdout=subprocess.PIPE)
    
    for stdout_line in iter(process.stdout.readline, b''):
        print(stdout_line.rstrip())
    

    使用shell 且不缓存输出的替代答案:

    正如 Sam 所指出的,上述解决方案存在问题,当提示行没有结束该行时(提示他们通常不会)。另一种解决方案是使用shell 参数与子流程进行交互。

    import subprocess
    subprocess.call("gpg2 --gen-key", shell=True) 
    

    【讨论】:

    • 我试过了,但似乎没有用。我什至创建了两个 python 脚本来测试它。一个是您提到的代码,另一个是在两个打印语句中间有一个 raw_input 的脚本。在给出输入之前不输出任何内容。
    • 好的,我明白了。问题可能是,提示没有被读取,因为它没有结束行。如果另一个脚本类似于print("Hello"); x = input("Please type 'yes': "),则只打印Hello。我同意,我的回答并不能解决问题。
    • 有道理。我查看了源代码,似乎我正在调用的库也使用print("Hello"); x = input("Please type 'yes': ") 你知道解决这个问题的方法吗?
    • 我已经使用shell 参数更新了答案。
    • 谢谢。看起来它不是允许输出的外壳,而是使用 subprocess.call() 方法。它具有 stdout=None、stderr=None、stdin=None 默认值,因此输出不会重定向到 PIPE。不幸的是,我还需要能够存储命令产生的输出,但它看起来像 subprocess.call 只输出“1”或“0”以返回,这个调用让我想起了 os.system...你知道吗?我可以使用 subprocess.call 获取输出吗?
    猜你喜欢
    • 2012-01-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-03-14
    • 2016-06-05
    • 1970-01-01
    • 1970-01-01
    • 2014-03-23
    相关资源
    最近更新 更多