首页 > 解决方案 > 使用子进程从 input() 交互式运行多个命令

问题描述

我正在寻找一种使用子进程在 Python 3.6 中创建某种终端“克隆”的方法。克隆的行为应该与真实终端一样。目标是拥有一个模拟 shell 的 python 脚本,它的行为尽可能与普通 shell 一样。包括命令cd或变量声明。

我的目标系统是带有 gnome shell 的 Linux,但我的问题可能是跨操作系统相关的。起初我认为这并不难,因为您可以使用子进程轻松运行终端命令,但我遇到了一些问题。

我不想做的事情:

#!/usr/bin/env python
import subprocess

while True:
    command = input(" >>> ").rstrip('\n')
    if command == 'quit':
        break
    subprocess.run(command, shell=True)

将有一种非常简单的方法来一个接一个地运行命令。问题在于,这将为每个命令启动一个新进程。因此,如果我执行以下命令,它将无法正常工作:

 >>> ls
stackoverflow_subprocess.py
 >>> cd ..
 >>> ls
stackoverflow_subprocess.py

因为我们每次都启动一个新进程,所以像 cd 这样的命令没有任何作用。这就是为什么我想在同一个进程中运行所有命令。

第一次尝试:

#!/usr/bin/env python
from subprocess import PIPE, Popen

pipe = Popen("/bin/bash", stdin=PIPE, stdout=PIPE, stderr=PIPE)

quit_command = "quit"
while True:
    command = input(" >>> ")
    if command == quit_command:
        break
    command = str.encode(command + "\n")
    out, err = pipe.communicate(command)
    print(out,err)

这是我第一次尝试解决我的问题。这就是我得到的:

 >>> echo hi
b'hi\n' b''
 >>> echo hello
Traceback (most recent call last):
  File "/home/user/Python/Stackoverflow/subprocess.py", line 11, in <module>
    out, err = pipe.communicate(command)
  File "/usr/lib/python3.6/subprocess.py", line 818, in communicate
    raise ValueError("Cannot send input after starting communication")
ValueError: Cannot send input after starting communication

Process finished with exit code 1

所以我不能像这样写多个命令。

第二次尝试:

#!/usr/bin/env python
from subprocess import PIPE, Popen

fw = open("tmpout", "wb")
fr = open("tmpout", "r")

pipe = Popen("/bin/bash", stdin=PIPE, stdout=fw, stderr=fw, bufsize=1)

quit_command = "quit"
while True:
    command = input(" >>> ")
    if command == quit_command:
        break
    command = str.encode(command + "\n")
    pipe.stdin.write(command)
    out = fr.read()
    print(out)

此尝试基于另一个类似于我的 stackoverflow 问题:Interactive input/output using python

 >>> echo hi

 >>> echo hello 

 >>> quit

Process finished with exit code 0

然而,这也没有奏效。out只是一个空字符串。当我查看它时,我意识到,tmpout直到程序完成,内容才会写入文件。即使您在每次迭代之间关闭并重新打开,它仍然只是在程序完成后fw写入。tmpout

程序结束tmpout 的内容:

hi
hello

第三次尝试:

#!/usr/bin/env python
from subprocess import PIPE, Popen

import os
import fcntl
from subprocess import Popen, PIPE


def setNonBlocking(fd):
    """
    Set the file description of the given file descriptor to non-blocking.
    """
    flags = fcntl.fcntl(fd, fcntl.F_GETFL)
    flags = flags | os.O_NONBLOCK
    fcntl.fcntl(fd, fcntl.F_SETFL, flags)


p = Popen("/bin/bash", stdin=PIPE, stdout=PIPE, stderr=PIPE, bufsize=1)
setNonBlocking(p.stdout)
setNonBlocking(p.stderr)

quit_command = "quit"
while True:
    command = input(" >>> ")
    if command == quit_command:
        break
    command = str.encode(command + "\n")

    p.stdin.write(command)
    while True:
        try:
            out = p.stdout.read()
        except IOError:
            continue
        else:
            break
    out = p.stdout.read()
    print(out)

最后我尝试了上面提到的 Stackoverflow 问题的第二个解决方案。这并不奏效,因为它总是返回None

 >>> echo hi
None
 >>> echo hello
None
 >>> quit

Process finished with exit code 0

问题: 有人知道解决这些问题的方法吗?即使在通信开始后也可以通信更多命令吗?或者是否可以在程序结束之前写入文件?或者有谁知道如何获得实际输出而不仅仅是None最后一次尝试?

先感谢您 :)

标签: pythonsubprocess

解决方案


你只是在寻找这个吗?

#!/usr/bin/env python

import subprocess

while True:
    command = input(" >>> ").rstrip('\n')
    if command == 'quit':
        break
    subprocess.run(command, shell=True)

正如下面的简短演示所示,它有一些明显的缺陷;但它确实展示了如何通过单独留下子进程的 stdout 和 stderr 来简单地摆脱困境。

 >>> echo hello
hello
 >>> foo=bar
 >>> echo "$foo"

 >>> # each command runs in a separate subshell
 >>> ^D
Traceback (most recent call last):
  File "/tmp/psh", line 6, in <module>
    command = input(" >>> ").rstrip('\n')
EOFError

推荐阅读