Я ищу способ создания своего рода терминального "клона" в Python 3.6 с использованием подпроцесса. Клон должен вести себя так же, как настоящий терминал. Цель состоит в том, чтобы иметь скрипт на Python, имитирующий оболочку, которая ведет себя как можно лучше обычной оболочки. Включая такие команды, как cd
или объявления переменных.
Моя целевая система - Linux с оболочкой gnome, но мои проблемы, вероятно, связаны с ОС. Сначала я не думаю, что это было слишком сложно, так как вы можете легко запускать команды терминала, используя подпроцесс, но я столкнулся с некоторыми проблемами.
Что я не хочу делать:
#!/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)
Эта попытка была основана на другом вопросе стекового потока, который был похож на мой: Интерактивный ввод / вывод с использованием 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
для последней попытки?
Заранее спасибо:)