Есть ли промежуточная функция или альтернатива subprocess.Popen и subprocess.Run?

Я создаю терминал Python на компьютере с Windows с нуля и пытаюсь синхронизировать команды ошибок и передать их либо из командной строки, либо из Powershell, в зависимости от того, в каком коде находится код. Однако, когда я использую subprocess.run, определенные более длинные команды, такие как netstat, не работают. Однако когда я использую subprocess.Popen, сообщения об ошибках не передаются из командной строки правильно.

Это текущий код:

def run_os_command():
    while True:
        cwd = os.getcwd()
        current_time = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
        command = input(f"{cwd} {current_time} >>:>> ")
        if command.lower() == "exit":
            break
        if command.lower().startswith("cd"):
            try:
                path = command.split(' ', 1)[1]  # get the path from the command
                os.chdir(path)
            except (IndexError, FileNotFoundError, NotADirectoryError):
                print("Error: Invalid directory")
        else:
            command = 'powershell ' + command
            command = command.split()
            try:
                process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
                while True:
                    output = process.stdout.readline()
                    if output == '' and process.poll() is not None:
                        break
                    if output:
                        print(output.strip())
                process.poll()
            except FileNotFoundError:
                print("Error: Command does not exist")

Пробовал с subprocess.Popen

:>> л

Ожидал:

«L» не распознается как внешняя или пакетная команда....

Получено: следующее приглашение

Пробовал с subprocess.Run

:>> нетстат

Ожидал:

Активные соединения

Протолокальный адрес Внешний адрес Состояние

Получено: ничего, команда зависла на неопределённое время.

Каков ваш актуальный вопрос? Да, есть и другие способы запуска внешних программ, кроме subprocess.Popen и subprocess.Run, но какую именно проблему вы пытаетесь решить, которую они не смогут решить?

Grismar 07.06.2024 01:52

Я запускаю среду Python от имени администратора, я не знаю, если вы можете сказать, но команда run_os_command открывает «окно терминала», которое отформатировано как командная строка, за исключением того факта, что это просто входные данные, направляемые в подпроцесс. ни одна из тех команд, которые я упомянул, run и Popen не могут запускать все команды и отображать правильные ошибки, хотя следует ли мне вставлять полный код?

Austin H 07.06.2024 05:21

Включение полного кода будет полезно, если вы не уверены, что именно вызывает вашу проблему. Однако в основном неясно, в чем именно заключается ваш вопрос. В заголовке спрашивается, есть ли альтернативы, а в тексте говорится, что «более длинные команды [..] не работают», но неясно, чего именно вы пытаетесь достичь и как вы пытались это сделать. Пожалуйста, опишите проблему очень конкретно и поделитесь кодом, который позволит кому-нибудь воспроизвести вашу проблему.

Grismar 07.06.2024 06:52
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
0
3
65
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

subprocess.run() — это удобная функция, которая сначала вызывает subprocess.Popen() для создания подпроцесса, затем Popen.communicate() для ожидания его завершения и возврата кода возврата, stdout данных и stderr данных.

Тогда как вызовом subprocess.Popen() вы запускаете только подпроцесс, а остальное зависит от вас. Выполнение программы продолжается с момента создания процесса. Затем вы можете вызвать Poepen.wait(), чтобы дождаться завершения, вызвать Popen.communicate(), чтобы при необходимости отправить входные данные в подпроцесс и дождаться завершения, или написать асинхронный код (путем чтения из буферов stdout/stderr по мере доступности данных, используя poll(), чтобы определить, завершен ли подпроцесс, реализация многопоточности и т. д.). Обратитесь к документации библиотеки подпроцессов.

Выбор зависит от ваших потребностей, оба метода подойдут. Теперь перейдем к вашему коду...

Причина, по которой вы не получаете исключение FileNotFound для недопустимых команд, заключается в том, что когда вы создаете подпроцесс, вы передаете его args=['powershell', 'asdflkj'] - и 'powershell' ЯВЛЯЕТСЯ допустимой командой. subprocess анализирует args, где первый параметр — это команда/приложение, а остальная часть списка — аргументы для этого конкретного приложения/команды.

Это означает, что вам нужно дождаться завершения подпроцесса и проверить код возврата CompletedSubprocess. Любой код, отличный от 0, указывает на сбой. Текст ошибки будет в process.stderr

Но более того, вам даже не нужно запускать powershell; команды можно вызывать напрямую. Я провел несколько быстрых тестов, и на моей машине значительно быстрее напрямую вызывать команды, как действительные, так и недействительные.

Одно предостережение: если вы хотите иметь возможность запускать команды, встроенные в оболочку, например dir, для этого потребуется запустить оболочку. Для этого вы можете использовать аргумент shell=True для Popen()/run() вместо того, чтобы вызывать powershell самостоятельно. (В Интернете вы найдете людей, рекомендующих избегать shell=True из соображений безопасности, о чем, я полагаю, вам не стоит беспокоиться).

Вот реализация с использованием run():

from datetime import datetime
import os
import subprocess

while True:
    cwd = os.getcwd()
    command = input(f"{cwd} {datetime.now(): %Y-%m-%d %H:%M:%S} >>:>> ")
    if command.strip() == '':
        continue
    if command.lower() == 'exit':
        break

    cmd, *args = command.split()
    if cmd.lower() == 'cd':
        if not args:
            print(cwd) # mirrors functionality of actual cd
            continue
        try:
            path = command.split(maxsplit=1)[1] # get the path from the command
            os.chdir(path)
        except (FileNotFoundError, NotADirectoryError):
            print('Error: Invalid directory')
        continue

    process = subprocess.run([cmd, *args],
                             stderr=subprocess.PIPE,
                             text=True,
                             shell=True)
    if process.returncode != 0:
        print('Error: Command does not exist')

Этот подход не передает по конвейеру stdout, то есть выходные данные печатаются в режиме реального времени, но вы все равно можете отображать собственное сообщение об ошибке для недопустимых команд!

Несколько исправлений/улучшений:

  • Пустые команды (например, когда вы просто нажимаете Enter) будут печатать следующую строку приглашения вместо запуска подпроцесса без аргументов.
  • cd без аргументов отображает текущий каталог вместо того, чтобы вызывать неперехваченное исключение

Если вы хотите обрабатывать/фильтровать выходные данные построчно в режиме реального времени, вы можете использовать Popen() с конвейерным stdout следующим образом:

    process = subprocess.Popen([cmd, *args],
                               stdout=subprocess.PIPE,
                               stderr=subprocess.PIPE,
                               text=True,
                               shell=True)
    for line in process.stdout:
        # do something with output line
        print(line.strip())
    try:
        process.wait(timeout=10)
        if process.returncode != 0:
            print('Error: Command does not exist')
    except subprocess.TimeoutExpired:
        print('Error: command timed out')

Другие примечания...

  • если пользователь хочет завершить подпроцесс с помощью CTRL+C, вы можете заключить цикл в блок try и перехватить KeyboardInterrupt
  • если вы ищете другие ответы SO о subprocess/Popen, убедитесь, что вы просматриваете свежую информацию - есть много советов 10-летней давности, когда в библиотеке subprocess были ошибки и отсутствовала текущая функциональность.
  • В зависимости от масштаба вашего проекта вы можете рассмотреть возможность использования библиотеки cmd. Он выполняет за вас самые сложные задачи, чтобы вы могли сосредоточиться на реализации команд.

Другие вопросы по теме