В этой статье мы продолжим начатое ранее и разберемся с работой с командной строкой в операционной системе из python-скрипта. Сделаем это в виде доработки скрипта из вышеописанной статьи - заменим стандартный архиватор библиотеки zipfile на 7-zip, отдельно установленный в системе. Для реализации взаимодействия с cmd будем использовать модуль subprocess.
Subprocess
Для взаимодействия с командной строкой будем использовать еще один встроенный модуль языка - subprocess. Существует несколько способов передачи команд в системную консоль как при помощи этого модуля, так и при помощи библиотеки os. В данной статье я буду рассказывать о способе запуска командной строки в отдельном процессе при помощи класса subprocess.Popen, так как считаю, что именно его и нужно стараться использовать.
Итак, начнём.
Основы
Для начала предлагаю потренироваться в отдельном скрипте. Сделаем что-то совсем простое, например - запуск всеми "любимого" графического редактора Paint. В самом простом случае, это будет выглядеть так:
Однако, всё, что мы увидим после запуска - лишь ненадолго отобразившееся окно редактора, сразу исчезающее после завершения выполнения скрипта. Скрипт, выполняющийся интерпретатором в одном процессе, порождает другой процесс, который является для него дочерним, и при завершении основного, дочерний также завершается. Это не то, что нам нужно, поэтому немного доработаем скрипт, добавим ожидание завершения процесса, при помощи метода wait():
Всё, что делает этот метод - дожидается завершения порожденного процесса и возвращает его код завершения. В случае успешного выполнения возвращается 0, любое другое значение, как правило, является ошибкой. Это более функционально и даже подходит для выполнения нашей задачи, но что если порожденный процесс захочет нам сообщить что-то, помимо кода завершения?
Взаимодействие и коммуникации
Здесь нам на помощь приходит метод communicate(). Попрактикуемся с ним, вызвав команду ping с набором параметров. Заменим имя команды на список фраз, которые нужно передать в командную строку:
Пока что ничего не изменилось, вывод отобразился в консоли (как это было бы и для метода wait), однако для скрипта он все еще недоступен, да и не всегда хотелось бы его видеть, честно говоря. Но все не бесполезно - теперь мы научились передавать в командную строку помимо имени команды, еще и набор параметров к ней. Здесь, например, мы передаем имя сервера для пинга и количество отправляемых запросов.
Единственное, что изменилось по сравнению с использованием метода wait() - значение переменной result. Посмотрите, она теперь возвращает кортеж из двух значений - содержимого stdout и stderr. Оба значения пусты, так как в них мы пока ничего не записывали.
STDOUT - стандартный поток вывода. Здесь будет всё, что процесс выводит при своей обычной работе.
STDERR - стандартный поток вывода ошибок. Здесь будет информация об ошибках, возникших при выполнении.
STDIN - стандартный поток ввода. Сюда могут передаваться данные, необходимые для работы процесса. В данном примере не используются
PIPE - канал межпроцессного взаимодействия. Используется для обмена данными между разными процессами.
Так давайте же их заполним! Укажем явно, куда их нужно передавать при объявлении экземпляра класса Popen:
Также переделаем вывод результата. Разделим на две строки, STDOUT и STDERR соответственно:
М-да, что-то явно не то с выводом. Хотя, если всмотреться, то STDERR пуст - это уже радует, а среди содержимого STDOUT мелькают цифры и буквы латиницы. Это может значить только одно - у Вас русифицированная винда есть некоторые проблемы с кодировками. Значит, нужно указать в скрипте явно, какую кодировку использовать для преобразования. Как все мы знаем (а если не знаем, то поисковик подскажет), для кодирования символов кириллицы в командной строке Windows по умолчанию используется кодировка cp866, её и нужно указать при вызове метода decode для строки байтов:
Вот и отлично, мы многое узнали. Теперь можно и вернуться к изначальной задачи - заменить архиватор в скрипте предыдущей статьи на 7-zip.
Во имя экономии места
Мы изучили всё, что нам нужно - пишем функцию! Предварительно разместим файл 7z.exe в директорию со скриптом.
Для реализации архивации через 7-zip, важно чтобы он у вас был либо установлен, либо в наличии была портативная версия :)
Функцию пишем максимально схожей по входным и выходным параметрам с функцией архивации через zipfile. Обратите внимание на строку 10, здесь использован однострочный генератор списка для формирования пути к файлу относительно файла скрипта, вкратце, он добавляет к каждому имени файла в списке files_list имя директории, в которой он расположен (наглядно: ['file1', 'file2'] -> ['archive\file1', 'archive\file2']). Можно использовать и обычную итерацию по списку в цикле for, но это уже вопрос вкуса.
В строках 13-15 уже знакомая конструкция для настройки и запуска процесса. Параметр архиватора 7zip -sdel удаляет исходные файлы после архивации, звездочка перед именем списка файлов осуществляет его распаковку и передачу в виде отдельных элементов по порядку (наглядно: ['archive\file1', 'archive\file2'] -> 'archive\file1', 'archive\file2').
Закомментируем вызов предыдущей версии функции и добавим вызов новой. Запускаем скрипт и получаем следующий вывод:
Отображен стандартный вывод утилиты 7zip, в котором есть общая информация по архивации. Также, вы можете увидеть, что вывод STDERR не пуст, но там содержится информация об успешности. Не знаю, для чего это сделано, вероятно в этом есть какой-то скрытый смысл.
Итоги
В этой статье мы научились базово взаимодействовать с командной строкой Windows через Python. В целом, аналогично происходит и взаимодействие с оболочкой в Linux, за исключением некоторой специфики данной ОС в отношении путей к файлам, командам и т.д. Используя эти знания, можно автоматизировать часть рутинных задач дома и на работе не потратив на это большое количество времени.