Получаем список имён установленных в Debian пакетов с Python3
newbie
Опубликован: | 2020-05-02T08:01:20.892044Z |
Отредактирован: | 2020-05-02T08:01:20.892044Z |
Питон, и в частности Python3, - великолепный инструмент для автоматизации рутины, и поэтому он достаточно часто используется системными администраторами для решения некоторых мелких, сиюминутных, повторяющихся задач на десктопе или сервере. В этом обзоре рассмотрим одну маленькую, но интересную задачу системного администратора Debian и её решение.
В одном из выпусков соседнего блога предложена команда, которая выводит на экран терминала последовательно имена всех установленных в Debian пакетов. Всё бы ничего, да вот команда получилась слишком длинная, сложная и запутанная. Сейчас я разработаю микроскопический скрипт, который автоматизирует эту задачу и даст возможность выполнить аналогичные действия простым и лаконичным вызовом в консоли.
Для начала сформулируем задачу.
На компьютере установлена операционная система Debian buster, необходимо получить список имён всех установленных в этой системе пакетов, с помощью которого впоследствии можно без труда установить все пакеты из списка на другой машине одной командой. Искомый список должен отвечать следующим требованиям:
- список должен содержать только имена пакетов без указания их версий;
- имя каждого пакета должно занимать одну строчку в списке;
- список нужно вывести на экран терминала или сохранить в файл.
Приступим... У меня есть тестовая машина с Debian buster на борту, на её базе я и буду решать эту весьма интересную для новичков в программировании задачу. Из инструментов мне потребуется терминал и текстовый редактор. Программировать на Python3 удобней всего в PyCharm, его и буду использовать, создаю файл dpkglist.py
.
У задачи есть два варианта решения:
dpkg -l
;sudo apt list --installed
.
Первый вариант мне нравится больше, потому что он возможен для любого пользователя системы, тогда как вариант с apt требует прав суперпользователя и, как следствие, поставит меня перед необходимостью автоматизированного ввода пароля. Кроме этого, выхлоп dpkg сложней, и его интересней парсить, поэтому попробуем решить дилемму без необходимости вводить пароль.
Для начала, давайте посмотрим на выхлоп команды dpkg -l
в терминале.
dpkg -l
Начало выхлопа выглядит так:
Выхлоп сам по себе достаточно длинный, в моей системе установлено более 1500 пакетов, в терминале его можно прокручивать клавишами со стрелками, PgUp
, PgDn
или Home
, End
. Давайте посмотрим на конец выхлопа.
Здесь следует обратить внимание, что список пакетов начинается с пакета adduser
и заканчивается пакетом zlib1g-dev:amd64
. Имена первого и последнего пакета в списке будет полезно знать и помнить в процессе отладки программы. Обращаю внимание, что имя последнего пакета в списке содержит наименование архитектуры, которое следует через двоеточие сразу после имени пакета, и от которого впоследствии надо бы избавиться.
В исходном состоянии полученный список не отвечает требованиям задачи, поскольку содержит изрядное количество лишней информации и не соответствует условиям задачи по форме.
Обращаю внимание, что в самом начале выхлопа dpkg, перед именем первого пакета следует так называемая шапка таблицы:
|||/ Имя Версия Архитектура Описание +++-=============================================-===============================-============-======================================================================================================
В этой таблице меня интересует только поле Имя
, и ограничено это поле двумя знаками -
, между которыми следуют знаки =
. Таким образом, в каждой новой строчке выхлопа ширина поля с именем пакета ограничена количеством знаков "равно" в шапке, и зная позицию первого и второго знаков -
в шапке таблицы, можно легко и просто извлечь имя пакета из каждой новой строчки выхлопа. Эту закономерность я и буду использовать далее.
Запускаю интерактивную сессию Python3, начальную пошаговую отладку возможного решения удобно сделать прямо в ней.
python3
В сессии Python3 я могу исполнить любую консольную программу с помощью модуля стандартной библиотеки subprocess и его инструментов. Исполняемую команду я приведу в заданные кондиции с помощью модуля стандартной библиотеки shlex, импортирую:
>>> import shlex >>> from subprocess import Popen, PIPE >>>
Облачаем dkpg -l
в список с помощью shlex.split
.
>>> cmd = shlex.split('dpkg -l') >>>
Такую команду достаточно просто исполнить, для этого создаю экземпляр класса Popen.
>>> with Popen(cmd, stdout=PIPE, stderr=PIPE) as dpkg: ... res = dpkg.communicate() ... >>>
Здесь следует обратить внимание, что с помощью PIPE я сохраняю содержимое стандартного потока вывода (stdout) и стандартного потока ошибок (stderr) в полученном объекте res
. Успешность выполнения команды можно легко проверить.
>>> dpkg.returncode 0 >>>
Рассмотрим детальнее полученную в итоге переменную res
.
>>> type(res) <class 'tuple'> >>> len(res) 2 >>> type(res[0]) <class 'bytes'> >>>
Как видно, res
- это tuple, в котором хранится два элемента - две байт-строки, первый элемент - это содержимое stdout, а второй - stderr. Поскольку выше мы увидели, что процесс dpkg вернул код 0
- успешное завершение процесса, второй элемент в res
меня не особо интересует, потому что он будет пустой байт-строкой. А вот на первый элемент res
следует посмотреть, выведу первые 50 символов этой строки.
>>> res[0].decode('utf-8')[:50] 'Желаемый=неизвестно[u]/установить[i]/удалить[r]/вы' >>>
Эту строку мне и предстоит последовательно распарсить для решения своей задачи. Первым делом преобразую эту строку в список, разделив её по символу окончания строки.
>>> pack = res[0].decode('utf-8').strip().split('\n') >>>
Вывожу первый и последний элемент этого списка.
>>> pack[0] 'Желаемый=неизвестно[u]/установить[i]/удалить[r]/вычистить[p]/зафиксировать[h]' >>> pack[-1] 'ii zlib1g-dev:amd64 1:1.2.11.dfsg-2 amd64 compression library - development' >>>
Теперь мне необходимо найти элемент списка, в котором хранится строка с именем первого пакета, напоминаю его имя - adduser
.
>>> i = 0 >>> while not pack[i].startswith('ii'): ... i += 1 ... >>> pack[i] 'ii adduser 3.118 all add and remove users and groups' >>>
Зная индекс этого элемента списка - i
, я легко могу получить предыдущий элемент списка, который является объектом моего пристального интереса и внимания, поэтому сохраню его в переменную block
.
>>> block = pack[i-1] >>> block '+++-=============================================-===============================-============-======================================================================================================' >>>
В этой строке меня интересуют первый и второй знаки -
и их индексы. Определяю их.
>>> first = block.find('-') >>> second = block.find('-', first+1) >>> first, second (3, 49) >>> block[first] '-' >>> block[second] '-' >>>
Теперь, имея все эти переменные - ключевые для этой задачи, я могу достаточно просто отфильтровать из списка pack
только интересующие меня данные.
>>> pack = [item[first:second].strip().split(':')[0] for item in pack[i:]] >>>
Что я имею в итоге?
>>> pack[0] 'adduser' >>> pack[-1] 'zlib1g-dev' >>> len(pack) 1583 >>>
А в итоге я получил список из 1583 имён установленных в моей системе пакетов. Вывести этот список на экран или сохранить в файл совершенно не составит никакого труда. Покидаю интерактивную сессию Питона.
>>> exit()
Перемещаюсь в тестовый редактор и пишу код программы.
import shlex from subprocess import Popen, PIPE def get_names(): with Popen(shlex.split('dpkg -l'), stdout=PIPE, stderr=PIPE) as dpkg: res = dpkg.communicate()[0] if not dpkg.returncode: pack = res.decode('utf-8').strip().split('\n') i = 0 while not pack[i].startswith('ii'): i += 1 block = pack[i-1] first = block.find('-') second = block.find('-', first + 1) if first != -1 and second != -1: return [item[first:second].strip().split(':')[0] for item in pack[i:]] if __name__ == '__main__': packs = get_names() if packs: for name in packs: print(name)
Сохраняю изменения в файл и пробую запустить программу в терминале.
python3 dpkglist.py | less
Посмотрим на конец выхлопа.
При желании этот список совершенно нетрудно сохранить в файл, а имея такой файл можно восстановить полноценную систему на другой машине с минимальными затратами времени и усилий оператора. Задача решена.
Метки: | linux, papa-debian, python3x, dpkg, subprocess, terminal |