python - 使用子进程从 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
最后一次尝试?
先感谢您 :)
解决方案
你只是在寻找这个吗?
#!/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
推荐阅读
- python - 反正有没有用 Python 从 HTML 中获取下一个文本元素?
- javascript - @typescript-eslint/parser parserOptions.ecmaVersion 被忽略/不 linting
- vue.js - 在 Vue JS 和 Firebase 中循环设置其值时不覆盖值
- graph - 从节点获取图中的所有路径,但只有终止的路径
- c++ - 分段错误(核心转储)
- java - 当我在 CUCUMBER 中运行 runner 类时,得到“courgette.runtime.CourgetteException: Unable to create the 'target\chrome' directory”
- java - 使用 intellij 中的 gridLayoutmanager 在 1 行上合并 2 列
- python - 如何在 act_window odoo 中添加域
- scala - 函数文字难题上的隐式参数
- redirect - 如何使用参数设置 OpenAPI 重定向响应?