Popen + Python общаться только возвращая первую строку стандартного вывода

Я пытаюсь использовать мой клиент git для командной строки и перенаправление ввода / вывода Python для автоматизации некоторых распространенных операций с большим количеством репозиториев git. (Да, это взлом. Я мог бы вернуться и использовать библиотеку Python, чтобы сделать это позже, но сейчас, похоже, все работает хорошо:))

Я хотел бы быть в состоянии захватить вывод вызова git. Сокрытие вывода будет выглядеть лучше, а захват его позволит мне записать его на случай, если это будет полезно.

Моя проблема в том, что я не могу получить больше, чем первая строка вывода, когда я запускаю команду 'git clone'. Как ни странно, тот же код с "git status", кажется, работает нормально.

Я использую Python 2.7 в Windows 7 и использую интерпретатор команд cmd.exe.

Мои спящие до сих пор:

  1. Когда я вызываю subprocess.call() с "git clone", он работает нормально, и я вижу вывод на консоли (который подтверждает, что git производит вывод, даже если я его не записываю). Этот код:

    dir = "E:\\Work\\etc\\etc"
    os.chdir(dir)
    git_cmd = "git clone git@192.168.56.101:Mike_VonP/bit142_assign_2.git"
    
    #print "SUBPROCESS.CALL" + "="*20
    #ret = subprocess.call(git_cmd.split(), shell=True) 
    

    выдаст этот вывод на консоль:

    SUBPROCESS.CALL====================
    Cloning into 'bit142_assign_2'...
    remote: Counting objects: 9, done.
    remote: Compressing objects: 100% (4/4), done.
    remote: Total 9 (delta 0), reused 0 (delta 0)
    Receiving objects: 100% (9/9), done.
    Checking connectivity... done.
    
  2. Если я делаю то же самое с POpen напрямую, я вижу тот же вывод на консоли (который также не фиксируется). Этот код:

    # (the dir = , os.chdir, and git_cmd= lines are still executed here)
    print "SUBPROCESS.POPEN" + "="*20
    p=subprocess.Popen(git_cmd.split(), shell=True)
    p.wait()
    

    будет производить этот (эффективно идентичный) вывод:

    SUBPROCESS.POPEN====================
    Cloning into 'bit142_assign_2'...
    remote: Counting objects: 9, done.
    remote: Compressing objects: 100% (4/4), done.
    remote: Total 9 (delta 0), reused 0 (delta 0)
    Receiving objects: 100% (9/9), done.
    Checking connectivity... done.
    

    (Очевидно, что я удаляю клонированный репо между прогонами, в противном случае я получу сообщение "Все актуально")

  3. Если я использую метод connect (), я ожидаю получить строку, содержащую весь вывод, который я вижу выше. Вместо этого я вижу только строкуCloning into 'bit142_assign_2'...,
    Этот код:

    print "SUBPROCESS.POPEN, COMMUNICATE" + "="*20
    p=subprocess.Popen(git_cmd.split(), shell=True,\
                bufsize = 1,\
                stderr=subprocess.PIPE,\
                stdout=subprocess.PIPE)
    tuple = p.communicate()
    p.wait()
    print "StdOut:\n" + tuple[0]
    print "StdErr:\n" + tuple[1]
    

    будет производить этот вывод:

    SUBPROCESS.POPEN, COMMUNICATE====================
    StdOut:
    
    StdErr:
    Cloning into 'bit142_assign_2'...
    

    С одной стороны, я перенаправил вывод (как вы можете видеть из того факта, что его нет в выводе), но я также только фиксирую эту первую строку.

Я много чего перепробовал (звонюcheck_outputвместо popen - использование каналов с subprocess.call, использование каналов с subprocess.popen и, возможно, другие вещи, о которых я забыл), но ничего не работает - я только фиксирую эту первую строку вывода.

Интересно,что точно такой же код работает правильно с "git status". После того как репозиторий был клонирован, вызов git status производит три строки вывода (которые в совокупности говорят "все актуально"), и этот третий пример (код связи POpen+) действительно захватывает все три строки вывода.

Если у кого-то есть какие-либо идеи о том, что я делаю неправильно, или какие-либо мысли о том, что я могу попробовать, чтобы лучше диагностировать эту проблему, я был бы очень признателен.

2 ответа

Решение

Попробуйте добавить --progress вариант вашей команды git. Это вынуждает git отправлять статус выполнения в stderr, даже если процесс git не подключен к терминалу, что имеет место при запуске git через subprocess функции.

git_cmd = "git clone --progress git@192.168.56.101:Mike_VonP/bit142_assign_2.git"

print "SUBPROCESS.POPEN, COMMUNICATE" + "="*20
p = subprocess.Popen(git_cmd.split(), stderr=subprocess.PIPE, stdout=subprocess.PIPE)
tuple = p.communicate()
p.wait()
print "StdOut:\n" + tuple[0]
print "StdErr:\n" + tuple[1]

NB Я не могу проверить это в Windows, но это эффективно в Linux.

Также не нужно указывать shell=True и это может быть проблемой безопасности, поэтому ее лучше избегать.

Здесь есть две части, представляющие интерес, одна из которых специфична для Python, а другая - для Git.

питон

При использовании subprocess модуль, вы можете выбрать управление тремя каналами ввода / вывода программы, которую вы запускаете: stdin, stdout и stderr. Это верно для subprocess.call а также subprocess.check_call так же как subprocess.Popen, но оба call а также check_call немедленно вызвать новый объект процесса wait метод, поэтому по разным причинам нецелесообразно поставлять subprocess.PIPE для stdout и / или stderr с этими двумя операциями. 1

Помимо этого, используя subprocess.call эквивалентно использованию subprocess.Popen, На самом деле, код для call это однострочник:

def call(*popenargs, **kwargs):
    return Popen(*popenargs, **kwargs).wait()

Если вы решите не перенаправлять какой-либо из каналов ввода-вывода, программы, которые читают ввод, получают его из того же места, что и Python, программы, которые записывают вывод в stdout, записывают его в то же место, в котором ваш собственный код Python 2, и программы, которые пишут вывод в stderr записать его в то же место, что и Python.

Вы можете, конечно, перенаправить stdout и / или stderr на реальные файлы, а также на subprocess.PIPE s. Файлы и каналы не являются интерактивными "терминальными" или "tty" устройствами (т. Е. Не рассматриваются как напрямую связанные с человеком). Это приводит нас к Git.

Гит

Программы Git обычно могут читать из stdin и / или записывать в stdout и / или stderr. Git может также вызывать дополнительные программы, которые могут делать то же самое, или могут обходить эти стандартные каналы ввода / вывода.

Особенно, git clone в основном пишет в свой stderr, как вы заметили. Более того, как ответил Мхавке, вы должны добавить --progress чтобы Git записывал сообщения о прогрессе в stderr, Git не разговаривает с интерактивным устройством tty.

Если Git нужен пароль или другая аутентификация при клонировании через https или же ssh Git запустит вспомогательную программу, чтобы получить это. Эти программы, по большей части, полностью обходят стандартный ввод (открывая /dev/tty в системах POSIX или аналог в Windows), чтобы взаимодействовать с пользователем. Насколько хорошо это будет работать, или будет ли оно работать вообще, в вашей автоматизированной среде - хороший вопрос (но опять же за рамками этого ответа). Но это возвращает нас к Python, потому что...

питон

Кроме subprocess модуль, есть несколько внешних библиотек, sh а также pexpect и некоторые средства, встроенные в сам Python через pty модуль, который может открыть псевдо-tty: интерактивное tty-устройство, которое вместо того, чтобы быть подключенным напрямую к человеку, подключается к вашей программе.

При использовании ptys вы можете заставить Git вести себя идентично тому, когда он говорит напрямую с человеком - фактически, "общение с человеком" сегодня фактически выполняется с помощью ptys (или его эквивалента), так как существуют программы, выполняющие различные оконные системы., Более того, программы, которые просят человека ввести пароль, теперь могут взаимодействовать с вашим собственным кодом Python. Это может быть хорошо или плохо (или даже оба), поэтому подумайте, хотите ли вы, чтобы это произошло.


1 В частности, точка communicate Метод заключается в управлении трафиком ввода-вывода между тремя-тремя потоками, если они есть PIPE, без клина подпроцесса. Представьте себе, если хотите, подпроцесс, который печатает 64 КБ текста в стандартный вывод, затем 64 КБ текста в стандартный вывод, затем еще 64 КБ текста в стандартный вывод и затем читает из стандартного ввода. Если вы попытаетесь прочитать или записать любой из них в каком-то определенном порядке, подпроцесс "застрянет", ожидая, чтобы вы очистили что-то еще, в то время как вы застрянете, ожидая завершения подпроцесса, который вы выбрали для завершения первым. Какие communicate Вместо этого он использует потоки или специфичные для ОС неблокирующие методы ввода-вывода для подачи входных данных подпроцесса при одновременном чтении его stdout и stderr.

Другими словами, он обрабатывал мультиплексирование. Таким образом, если вы не поставляете subprocess.PIPE по крайней мере, для двух из трех каналов ввода / вывода безопасно обойти communicate метод. Если да, это не так (если вы не реализуете свое собственное мультиплексирование).

Здесь есть несколько любопытный крайний случай: если вы поставите subprocess.STDOUT для вывода stderr это говорит Python направить два выхода подпроцесса в один канал связи. Это считается только одним каналом, поэтому, если вы объедините stdout и stderr подпроцесса и не будете вводить данные, вы можете обойти communicate метод.

2 Фактически, подпроцесс наследует процессы stdin, stdout и stderr, которые могут не соответствовать Python sys.stdin, sys.stdout, а также sys.stderr если ты переопределил это. Это, вероятно, лучше всего игнорировать здесь.:-)

3 Я говорю "может" вместо "будет", потому что /dev/tty получает доступ к управляющему терминалу, и не все ptys являются управляющими терминалами. Это также усложняется и зависит от ОС и также выходит за рамки этого ответа.

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