Почему subprocess.Popen код возврата отличается для аналогичных команд с bash

Зачем

import subprocess

p = subprocess.Popen(["/bin/bash", "-c", "timeout -s KILL 1 sleep 5 2>/dev/null"])
p.wait()
print(p.returncode)

возвращается

[stderr:] /bin/bash: line 1: 963663 Killed                  timeout -s KILL 1 sleep 5 2> /dev/null
[stdout:] 137

когда

import subprocess

p = subprocess.Popen(["/bin/bash", "-c", "timeout -s KILL 1 sleep 5"])
p.wait()
print(p.returncode)

возвращается

[stdout:] -9

Если вы измените bash на dash, вы получите 137 в обоих случаях. Я знаю, что -9 - это код KILL, а 137 - 128 + 9. Но кажется странным, что подобный код получает другой код возврата.

Бывает на Python 2.7.12 и python 3.4.3

Похоже Popen.wait() не звонит Popen._handle_exitstatus https://github.com/python/cpython/blob/3.4/Lib/subprocess.py при использовании /bin/bash но я не мог понять почему.

1 ответ

Это связано с тем, как bash исполняет timeout с или без перенаправления / трубы или любые другие функции bash:

  • С перенаправлением

    1. python начинается bash
    2. bash начинается timeout, контролирует процесс и выполняет обработку труб.
    3. timeout переносит себя в новую группу процессов и запускает sleep
    4. Через одну секунду timeout посылает SIGKILL в свою группу процессов
    5. Когда группа процессов умерла, bash возвращается из ожидания timeoutвидит SIGKILL и печатает сообщение, вставленное выше, в stderr, Затем он устанавливает свой собственный статус выхода на 128+9 (поведение, моделируемое timeout).
  • Без перенаправления

    1. python начинается bash,
    2. bash видит, что он не имеет ничего общего и вызывает execve() эффективно заменить себя timeout,
    3. timeout действует как выше, вся группа процессов умирает с SIGKILL,
    4. python получить статус выхода 9 и делает некоторые искажения, чтобы превратить это в -9 (SIGKILL)

Другими словами, без перенаправления / трубы / и т. Д. bash выводит себя из колл-цепочки. Ваш второй пример выглядит subprocess.Popen() выполняет bashИ все же это не так. bash больше не там, когда timeout делает свое дело, именно поэтому вы не получаете никаких сообщений и незащищенный статус выхода.

Если вы хотите последовательного поведения, используйте timeout --foreground; вы получите статус выхода 124 в обоих случаях.

Я не знаю насчет тире; пока предположим, что это не делает execve() хитрость, чтобы эффективно заменить себя единственной программой, которую это выполняет. Поэтому вы всегда видите искаженное состояние выхода 128 + 9 в тире.

Обновление: zshпоказывает то же поведение, в то время как он выпадает даже для простых перенаправлений, таких как timeout -s KILL 1 sleep 5 >/tmp/foo и тому подобное, давая вам статус выхода -9. timeout -s KILL 1 sleep 5 && echo $? даст вам статус 137 в zsh также.

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