Интерактивно запускать несколько команд из input () с подпроцессом - PullRequest
0 голосов
/ 04 сентября 2018

Я ищу способ создания своего рода терминального "клона" в 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 для последней попытки?

Заранее спасибо:)

Ответы [ 2 ]

0 голосов
/ 04 сентября 2018

Хорошо, так получается, что не так просто выполнить несколько команд в одном процессе, чтобы сохранить встроенные функции оболочки, такие как cd или присвоение переменной. Но они могут быть реализованы самостоятельно в Python. Итак, вот моя версия интерактивной оболочки на Python с использованием подпроцесса:

#!/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, переменные, а также строки, заключенные в двойные кавычки ("). Я надеюсь, что это поможет любому, у кого есть подобная проблема.

0 голосов
/ 04 сентября 2018

Вы ищете просто это?

#!/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
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...