Я создал простую функцию Python:
import subprocess
from io import TextIOWrapper
def run_shell_command(command: list, debug: bool = False):
'''
Run shell command
:param command: Shell command
:param debug: Debug mode
:return: Result code and message
'''
try:
process = subprocess.run(
command, check=True, text=True, timeout=5,
stdout=subprocess.PIPE, stderr=subprocess.STDOUT
)
if debug:
for line in TextIOWrapper(process.stdout, encoding='utf-8'):
print(line)
message = 'Shell command executed sucessfully'
return ({'code': 200, 'msg': message, 'stdout': process.stdout})
except subprocess.CalledProcessError as e:
return ({'code': 500, 'msg': e.output})
if __name__ == "__main__":
command = run_shell_command(['ls', '-lah'], True)
print(command)
Когда я запускаю его в режиме отладки, я получаю следующую ошибку:
Traceback (most recent call last):
File "/tmp/command.py", line 28, in <module>
command = run_shell_command(['ls', '-lah'], True)
File "/tmp/command.py", line 19, in run_shell_command
for line in TextIOWrapper(process.stdout, encoding = "utf-8"):
AttributeError: 'str' object has no attribute 'readable'
Запуская Python 3.9 на сервере Linux, мне было интересно, можете ли вы рассказать, в чем может быть проблема. При отключенной отладке я получаю правильный текстовый вывод. Спасибо за помощь.
Обновлено: Судя по комментариям ниже, исправить это довольно просто:
if debug:
print(process.stdout.rstrip())
Однако принятое решение IMO лучше по сравнению с исходным кодом.
Можете ли вы объяснить, почему вы передаете process.stdout
, который здесь является str
(потому что вы использовали text=True
в subprocess.run
), на TextIOWrapper
, но он не принимает str
. почему ты используешь TextIOWrapper
?
Я думаю, возможно, вы ожидали, что subprocess.run
вернет объект Popen
, но это не так, он возвращает объект CompletedProcess
@juanpa.arrivillaga Изначально я смотрел этот комментарий: stackoverflow.com/a/34938743/14684366
@Флорен, верно, ты не работаешь с объектом Popen
Спасибо @MichaelButscher, я отредактировал вопрос с вашим простым исправлением.
@Floren Это будет распечатывать выходные данные только после завершения процесса, а не «построчно».
@AKX, меня это устраивает, я отредактирую заголовок вопроса. Спасибо, что дали мне знать.
К сожалению, «простой» подход не позволяет справиться со всеми крайними случаями — вам нужно будет прочитать подпроцесс stdout
во время его потоковой передачи, распечатать его и накопить в буфере, а также отслеживать время, чтобы вы могли тайм-аут правильно. Обратите внимание, что здесь тоже возможна ошибка (хотя и не очень опасная), если чтение 4096 байт заканчивается многострочным символом.
def run_shell_command(command: list, debug: bool = False, timeout: float = 5):
"""
Run shell command
:param command: Shell command
:param debug: Debug mode
:return: Result code and message
"""
with subprocess.Popen(
command,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
) as process:
start_time = time.time()
output = b""
while True:
buf = process.stdout.read(4096)
if debug:
# could fail if `buf` happens to end in a multi-byte character
print(buf.decode("utf-8", "ignore"))
output += buf
if time.time() - start_time > timeout:
process.kill()
message = "Shell command timed out"
return {"code": 500, "msg": message, "stdout": output}
if process.poll() is not None:
break
if process.returncode != 0:
message = "Shell command failed"
return {"code": 500, "msg": message, "stdout": output}
message = "Shell command executed successfully"
return {"code": 200, "msg": message, "stdout": output}
if __name__ == "__main__":
command = run_shell_command(["ls", "-lah"], True)
print(command)
Правильный способ обработки многобайтовых символов, разделенных по границам блоков, — это использование инкрементного декодера.
Абсолютно. Или просто продолжайте накапливать новые фрагменты, пока строка не станет декодируемой. Но поскольку это предназначено только для вывода отладки, я решил сделать его немного проще.
Спасибо @AKX за решение исходного вопроса. Я отмечу ваш ответ как принятое решение, как более полное.
@AKX, линтер возвращает это предупреждение: «Рассмотрите возможность использования 'with' для операций выделения ресурсов». на subprocess.Popen
. Мне было интересно, какая альтернатива коду могла бы это исправить?
@Флорен Конечно – дополнен ответ, чтобы использовать with
для управления ресурсами.
Спасибо @AKX, есть кое-что интересное при запуске команды git clone
или git branch
. Печатается не весь вывод, мне интересно, в чем основная причина?
Чего не хватает?
Если я запущу git clone repo someurl
, фактический результат клонирования не отобразится. Я думаю, именно так работает github.
Верно, это, по-видимому, git
просто делает разные вещи в зависимости от того, подключено ли оно к устройству TTY (а если вы перенаправляете вывод, то нет). См. git clone --progress может быть?
Это решило проблему, спасибо!
subprocess.run
выходит после завершения процесса.process.stdout
содержит строковый вывод (не канал или файл) команды. Простойprint(process.stdout)
вместо цикла for должен вывести весь результат.