我正在寻找一种使用子进程在Python 3.6中创建某种终端“克隆”的方法。克隆的行为应类似于真实终端。我们的目标是要有一个python脚本来模拟一个外壳,该外壳的行为尽可能地类似于普通外壳。包括诸如cd
之类的命令或变量声明。
我的目标系统是带有gnome shell的Linux,但是我的问题可能与跨OS有关。刚开始我并不觉得太难,因为您可以使用子进程轻松运行终端命令,但是遇到了一些问题。
我不想做什么:
#!/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)
此尝试基于另一个类似于我的Interactive input/output using python
的stackoverflow问题。 >>> 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
?
预先感谢您:)
答案 0 :(得分:0)
您只是在寻找这个吗?
#!/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
答案 1 :(得分:0)
好吧,事实证明,要保持cd
之类的shell内建函数或变量赋值,在同一进程中运行多个命令并非易事。但是它们可以自己在python中实现。因此,这是我使用子流程的Python交互式Shell版本:
#!/usr/bin/env python
from subprocess import run
from os import path, curdir, chdir
home_dir = path.expanduser("~")
# Gets current directory and replaces your home directory with "~"
def current_dir():
return path.abspath(curdir).replace(home_dir, "~")
# Escapes a string by replacing spaces " " with "\s" between quotation marks
def escape_space(string):
out = ""
quote = False
for letter in string:
quote = (quote != (letter == "\"")) # quote <- quote XOR letter is "
if quote and letter == " ":
letter = "\s"
out += letter
return out
# Dictionary that holds all variables
var_dict = {}
# Handles Variables
def handle_vars(command_args):
for i in range(len(command_args)):
arg = command_args[i]
# Replace variables with their value
if arg[0] == "$":
if arg[1:] in var_dict:
command_args[i] = var_dict[arg[1:]]
else:
command_args[i] = ""
# Add new variable
elif "=" in arg:
arg_split = arg.split("=")
var_dict[arg_split[0]] = arg_split[1]
quit_flag = False
if __name__ == "__main__":
while True:
display_dir = "\033[34m{}\033[39m$ ".format(current_dir()) # The current directory with color
commands = input(display_dir).rstrip('\n').split(";")
# Repeat for all commands (multiple commands are possible with ";")
for cmd in commands:
cmd = escape_space(cmd)
command_args = cmd.split(" ")
handle_vars(command_args)
if command_args[0] == "quit":
quit_flag = True
break
elif command_args[0] == "cd":
chdir(command_args[1]) # change execution dir
else:
command = " ".join(command_args).replace("\s", " ")
run(command, shell=True)
if quit_flag:
break
print("Shell Terminated.")
这使我可以使用cd
,变量以及双引号(“)括起来的字符串。
希望这对遇到类似问题的人有所帮助。