Как выполнять несколько команд синхронно с одной командой subprocess.Popen?

Возможно ли выполнить произвольное количество команд в последовательности, используя ту же команду подпроцесса?

Мне нужно, чтобы каждая команда дождалась, пока предыдущая завершится до ее выполнения, и мне нужно, чтобы все они были выполнены в том же сеансе/оболочке. Мне также нужно это для работы в Python 2.6, Python 3.5. Мне также нужна команда subprocess для работы в Linux, Windows и macOS (вот почему я просто использую команды echo в качестве примеров здесь).

Смотрите неработающий код ниже:

import sys
import subprocess

cmds = ['echo start', 'echo mid', 'echo end']

p = subprocess.Popen(cmd=tuple([item for item in cmds]),
                     stdout=subprocess.PIPE, stderr=subprocess.STDOUT)

for line in iter(p.stdout.readline, b''):
    sys.stdout.flush()
    print(">>> " + line.rstrip())

Если это невозможно, какой подход следует принять для выполнения моих команд в синхронной последовательности внутри одного сеанса/оболочки?

Ответ 1

Если вы хотите выполнить много команд один за другим в одном и том же сеансе/оболочке, вы должны запустить оболочку и передать ее всем командам, по одному за ней, а затем по новой строке и закрыть канал на конец. Имеет смысл, если некоторые команды не являются истинными процессами, но командами оболочки, которые могут, например, изменить среду оболочки.

Пример использования Python 2.7 под Windows:

encoding = 'latin1'
p = subprocess.Popen('cmd.exe', stdin=subprocess.PIPE,
             stdout=subprocess.PIPE, stderr=subprocess.PIPE)
for cmd in cmds:
    p.stdin.write(cmd + "\n")
p.stdin.close()
print p.stdout.read()

Чтобы этот код работал под Linux, вам нужно заменить cmd.exe на /bin/bash и, возможно, изменить кодировку на utf8.

Для Python 3 вам нужно будет кодировать команды и, возможно, декодировать их вывод и использовать круглые скобки с печатью.

Остерегайтесь: это может работать только при небольшом выходе. Если было достаточно выхода для заполнения буфера для буфера перед закрытием трубы stdin, этот код был бы блокирован. Более надежным способом было бы иметь второй поток для чтения вывода команд, чтобы избежать этой проблемы.

Ответ 2

Одно из возможных решений, похоже, работает в той же оболочке:

subprocess.Popen('echo start;echo mid;echo end', shell=True)

Примечание. Если вы передаете команду в виде строки, то оболочка должна быть True Примечание. Это работает только с Linux, возможно, вам придется найти что-то подобное в окнах.

Надеюсь, это поможет.

Из документа python -

В Unix с оболочкой = True оболочка по умолчанию имеет значение /bin/sh. Если args является string, строка указывает команду для выполнения через оболочку. Это означает, что строка должна быть отформатирована точно так, как это было бы при вводе в командной строке.

Ответ 3

Это похоже на ответ сержа Баллеста, но не совсем. Используйте его для асинхронного выполнения, где вас не волнуют результаты. Используйте мой для синхронной обработки и сбора результатов. Как и его ответ, я покажу решение для Windows - запускайте процесс bash в Linux, а не cmd в Windows.

from subprocess import Popen, PIPE
process = Popen( "cmd.exe", shell=False, universal_newlines=True,
                  stdin=PIPE, stdout=PIPE, stderr=PIPE )                             
out, err = process.communicate( commands ) 

ДЕТАЛИ ИСПОЛЬЗОВАНИЯ: Аргумент commands, передаваемый здесь методу process.communicate, является строкой с разделителем новой строки. Например, если вы просто прочитали содержимое пакетного файла в строку, вы можете запустить его таким образом, потому что в нем уже будут символы новой строки.

Важно: ваша строка должна заканчиваться символом новой строки "\n". Если этого не произойдет, эта последняя команда не будет выполнена. Точно так же, как если бы вы ввели его в командную строку, но не нажали enter в конце. Однако вы увидите загадочную строку More? в конце возвращаемого стандартного вывода. (что причина, если вы столкнулись с этим).

process.communicate работает синхронно по определению и возвращает сообщения stdout и stderr (если вы направили их в subprocess.PIPE в конструкторе Popen).

Когда вы создадите процесс cmd.exe и передадите ему строку, результаты будут точно такими же, как если бы вы открывали окно командной строки и вводили команды. И я имею в виду это буквально. Если вы проверите это, вы увидите, что возвращаемый стандартный вывод содержит ваши команды. (Неважно, если вы добавите @echo off, как при выполнении командного файла).

Советы для тех, кто заботится о "чистых" результатах стандартного вывода:

  • @echo off не будет препятствовать тому, чтобы ваши команды появлялись в этой возвращенной строке, но он удаляет лишние переводы строк, которые иначе попадают туда. (universal_newlines = True удаляет другой набор из них)

  • Включение префикса символа @ в ваши команды позволяет их выполнять. В "нормальном" пакетном процессе это построчный способ "скрыть" ваши команды. В этом контексте это безопасный легкий маркер, с помощью которого вы можете найти строки stdout, которые вы хотите удалить. (если бы кто-то был так склонен)

  • "Заголовок" cmd.exe появится в вашем выводе (где указана версия Windows и т.д.). Поскольку вы, вероятно, хотите запустить свой набор команд с @echo off, чтобы вырезать лишние новые строки, это также отличный способ узнать, где остановились строки заголовка и начались ваши команды/результаты.

Наконец, чтобы решить проблемы с "большим" выходом, заполняющим каналы и вызывающие у вас проблемы - во-первых, я думаю, что вам нужно ОГРОМНОЕ количество данных, возвращающихся для того, чтобы это стало проблемой, - больше, чем большинство людей встретит в своих случаях использования. Во-вторых, если это действительно проблема, просто откройте файл для записи и передайте этот дескриптор файла (ссылку на объект файла) в stdout/err вместо PIPE. Затем делайте что хотите с файлом, который вы создали.

Ответ 4

Этот работает в python 2.7 и должен работать и в окнах. Вероятно, для python > 3 требуется небольшое уточнение.

Полученный результат (с использованием даты и сна, легко видеть, что команды выполняются в строке):

>>>Die Sep 27 12:47:52 CEST 2016
>>>
>>>Die Sep 27 12:47:54 CEST 2016

Как вы видите, команды выполняются в строке.

    import sys
    import subprocess
    import shlex

    cmds = ['date', 'sleep 2', 'date']

    cmds = [shlex.split(x) for x in cmds]

    outputs =[]
    for cmd in cmds:
        outputs.append(subprocess.Popen(cmd,
                                   stdout=subprocess.PIPE, stderr=subprocess.STDOUT).communicate())


    for line in outputs:
        print ">>>" + line[0].strip()

Это то, что я получаю слиянием с ответом @Marichyasana:

import sys
import os


def run_win_cmds(cmds):

    @Marichyasana code (+/-)

def run_unix_cmds(cmds):

    import subprocess
    import shlex


    cmds = [shlex.split(x) for x in cmds]

    outputs =[]
    for cmd in cmds:
        outputs.append(subprocess.Popen(cmd,
                                        stdout=subprocess.PIPE, stderr=subprocess.STDOUT).communicate())


    rc = ''
    for line in outputs:
        rc +=  line[0].strip()+'\n'

    return rc


cmds = ['date', 'sleep 2', 'date']

if os.name == 'nt':
     run_win_cmds(cmds)
elif os.name == 'posix':
    run_unix_cmds(cmds)

Спросите, что это не соответствует вашим потребностям!;)

Ответ 5

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

# processJobsInAList.py
# 2016-09-27   7:00:00 AM   Central Daylight Time 

import win32process, win32event

def CreateMyProcess2(cmd):
    ''' create process width no window that runs a command with arguments
    and returns the process handle'''
    si   = win32process.STARTUPINFO()
    info = win32process.CreateProcess(
        None,      # AppName
        cmd,       # Command line
        None,      # Process Security
        None,      # Thread Security
        0,         # inherit Handles?
        win32process.NORMAL_PRIORITY_CLASS,
        None,      # New environment
        None,      # Current directory
        si)        # startup info
    # info is tuple (hProcess, hThread, processId, threadId)
    return info[0]

if __name__ == '__main__' :
    ''' create/run a process for each list element in "cmds"
    output may be out of order because processes run concurrently '''

    cmds=["echo my","echo heart","echo belongs","echo to","echo daddy"]
    handles    = []
    for i in range(len(cmds)):
        cmd    = 'cmd /c ' + cmds[i]
        handle = CreateMyProcess2(cmd)
        handles.append(handle)

    rc = win32event.WaitForMultipleObjects( handles, 1, -1)  # 1 wait for all, -1 wait infinite
    print 'return code ',rc

Выход:
сердце
мой
принадлежит
для изображения папа
код возврата 0

UPDATE: если вы хотите запустить тот же процесс, который будет сериализовать для вас вещи:
1) Удалить строку: handle.append(handle)
2) Замените переменную "handle" вместо списка "handle" в строке "WaitFor"
3) Замените WaitForSingleObject вместо WaitForMultipleObjects

Ответ 6

Я пытаюсь решить подобную проблему. но не смог найти решение в этой теме. Я хотел бы взаимодействовать с процессом CMD через Python. например, 1. открыть оболочку cmd. 2. записать в cmd "dir" 3. прочитать результаты 4. записать (в той же оболочке) cd.. 5. dir еще раз, чтобы увидеть содержимое предыдущей папки 6. прочитать результаты еще раз. однако я либо попадаю в тупик при попытке прочитать, либо открываю разные процессы cmd. Пример того, как это можно сделать, будет оценен по достоинству. Работа в Windows с Python 3.6 (хотя это и не важно для примера)