Я создаю терминал 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
:>> нетстат
Ожидал:
Активные соединения
Протолокальный адрес Внешний адрес Состояние
Получено: ничего, команда зависла на неопределённое время.
Я запускаю среду Python от имени администратора, я не знаю, если вы можете сказать, но команда run_os_command открывает «окно терминала», которое отформатировано как командная строка, за исключением того факта, что это просто входные данные, направляемые в подпроцесс. ни одна из тех команд, которые я упомянул, run и Popen не могут запускать все команды и отображать правильные ошибки, хотя следует ли мне вставлять полный код?
Включение полного кода будет полезно, если вы не уверены, что именно вызывает вашу проблему. Однако в основном неясно, в чем именно заключается ваш вопрос. В заголовке спрашивается, есть ли альтернативы, а в тексте говорится, что «более длинные команды [..] не работают», но неясно, чего именно вы пытаетесь достичь и как вы пытались это сделать. Пожалуйста, опишите проблему очень конкретно и поделитесь кодом, который позволит кому-нибудь воспроизвести вашу проблему.






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, то есть выходные данные печатаются в режиме реального времени, но вы все равно можете отображать собственное сообщение об ошибке для недопустимых команд!
Несколько исправлений/улучшений:
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 и перехватить KeyboardInterruptsubprocess/Popen, убедитесь, что вы просматриваете свежую информацию - есть много советов 10-летней давности, когда в библиотеке subprocess были ошибки и отсутствовала текущая функциональность.
Каков ваш актуальный вопрос? Да, есть и другие способы запуска внешних программ, кроме
subprocess.Popenиsubprocess.Run, но какую именно проблему вы пытаетесь решить, которую они не смогут решить?