【问题标题】:subprocess popen.communicate() vs. stdin.write() and stdout.read()子进程 popen.communicate() 与 stdin.write() 和 stdout.read()
【发布时间】:2014-02-04 04:12:19
【问题描述】:

我注意到两种不同的行为,两种方法应该会产生相同的结果。

目标 - 使用 subprocess 模块执行外部程序,发送一些数据并读取结果。

外部程序为PLINK,平台为WindowsXP,Python 3.3版本。

主要思想-

execution=["C:\\Pr..\\...\\plink.exe", "-l", username, "-pw", "***", IP]
a=subprocess.Popen(execution, bufsize=0, stdout=PIPE, stdin=PIPE, stderr=STDOUT, shell=False)
con=a.stdout.readline()
if (con.decode("utf-8").count("FATAL ERROR: Network error: Connection timed out")==0):
   a.stdin.write(b"con rout 1\n")
   print(a.stdout.readline().decode("utf-8"))
   a.stdin.write(b"infodf\n")
   print(a.stdout.readline().decode("utf-8"))
else:
   print("ERROR")
a.kill()

到目前为止一切顺利。

现在,我希望能够执行一个循环(在每次写入子进程的标准输入之后),等待子进程的标准输出 EOF,打印它,然后是另一个标准输入命令,等等。

所以我首先尝试了之前关于同一主题的讨论(live output from subprocess commandread subprocess stdout line by linepython, subprocess: reading output from subprocess)。

它没有工作(它永远挂起),因为 PLINK 进程一直保持活动状态,直到我自己杀死它,所以等待子进程的标准输出到达 EOF 或在标准输出为时执行循环是没有用的真的,因为在我杀死它之前它永远都是真的。

所以我决定每次写入标准输入时都从标准输出读取两次(对我来说已经足够了)-

execution=["C:\\Pr..\\...\\plink.exe", "-l", username, "-pw", "***", IP]
a=subprocess.Popen(execution, bufsize=0, stdout=PIPE, stdin=PIPE, stderr=STDOUT, shell=False)
con=a.stdout.readline()
if (con.decode("utf-8").count("FATAL ERROR: Network error: Connection timed out")==0):
   a.stdin.write(b"con rout 1\n")
   print(a.stdout.readline().decode("utf-8"))
   print(a.stdout.readline().decode("utf-8"))   //the extra line [1]
   a.stdin.write(b"infodf\n")
   print(a.stdout.readline().decode("utf-8"))
   print(a.stdout.readline().decode("utf-8"))   //the extra line [2]
else:
   print("ERROR")
a.kill()

但据我所知,第一个额外的readline() 永远挂起,原因与我提到的相同。第一个额外的readline() 永远等待输出,因为在第一个readline() 中已经读取了唯一的输出,并且因为PLINK 是活动的,所以该函数只是“坐”在那里并等待获得新的输出行。

所以我尝试了这段代码,期待同样的挂起,因为 PLINK 在我杀死它之前永远不会死-

execution=["C:\\Pr..\\...\\plink.exe", "-l", username, "-pw", "***", IP]
a=subprocess.Popen(execution, bufsize=0, stdout=PIPE, stdin=PIPE, stderr=STDOUT, shell=False)
con=a.stdout.readline()
if (con.decode("utf-8").count("FATAL ERROR: Network error: Connection timed out")==0):
   a.stdin.write(b"con rout 1\n")
   print(a.stdout.readline().decode("utf-8"))
   a.stdin.write(b"infodf\n")
   print(a.stdout.readline().decode("utf-8"))
   print(a.communicate()[0].decode("utf-8"))     //Popen.communicate() function
else:
   print("ERROR")
a.kill()

我试过了,因为根据communicate() 的文档,函数会等到进程结束,然后才结束。此外,它从标准输出读取直到 EOF。 (与读写stdout和stdin一样)

但是communicate() 完成并且没有挂起,与前一个代码块相反。

我在这里缺少什么?为什么使用communicate()时PLINK结束,但使用readline()时却没有?

【问题讨论】:

    标签: python subprocess python-3.3


    【解决方案1】:

    没有communicate() 的程序会出现死锁,因为两个进程都在等待对方写一些东西,然后再自己写。

    communicate() 在您的示例中不会死锁,因为它会关闭流,就像命令 a.stdin.close() 一样。这会向您的子进程发送一个 EOF,让它知道没有更多输入,因此它可以关闭自身,进而关闭其输出,因此 a.stdout.read() 最终返回一个 EOF(空字符串)。

    没有特殊信号表明您的主进程将从您的子进程接收到,让您知道它已完成从一个命令写入结果,但已准备好执行另一个命令。

    这意味着要像您尝试那样与一个子进程来回通信,您必须读取子进程发送的确切行数。就像您看到的那样,如果您尝试读取行太多,你死锁了。您也许可以使用您所知道的,例如您发送的命令,以及您目前看到的输出,来确定要读取的确切行数。

    【讨论】:

    • 如果您不想让打开的进程出现死锁,请在输入后发送 EOF 信号,因为您总是使用键盘发送,也就是 Enter(\n)。因此,通过使用它,您可以避免死锁。 proc.stdin.write(b'2+2\n') , proc.stdin.flush() ,print(proc.stdout.readline().decode())
    • @babygame0ver '\n' 是行尾,不是文件尾。您可以在原始帖子中看到他们已经在发送换行符。看起来您正在考虑一个特定的过程,该过程总是每行输入回复一行输出。情况并非总是如此。
    【解决方案2】:

    您可以使用线程同时写入和读取,特别是如果输出只需要打印给用户:

    from threading import Thread
    
    def print_remaining(stream):
        for line in stream:
            print(line.decode("utf-8"))
    
    con = a.stdout.readline()
    if "FATAL ERROR" not in con.decode("utf-8"):
        Thread(target=print_remaining, args=[a.stdout]).start()
        for cmd in LIST_OF_COMMANDS_TO_SEND:
            a.stdin.write(cmd)
    

    【讨论】:

      【解决方案3】:

      问题是,当使用subprocess.Popen 时,即使在进程终止之前,您的代码也会继续被读取。尝试将.wait() 附加到您的 Popen 调用(请参阅documentation),

      a=subprocess.Popen(execution, bufsize=0, stdout=PIPE, stdin=PIPE, stderr=STDOUT, shell=False).wait()
      

      这将确保在继续执行其他任何操作之前完成执行。

      【讨论】:

        猜你喜欢
        • 2016-01-23
        • 1970-01-01
        • 2012-04-10
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多