Основы Python для Linux: Быстрый старт

Вводный курс по использованию Python в качестве инструмента системного администрирования. Вы научитесь писать базовые скрипты для взаимодействия с операционной системой и автоматизации рутинных задач в среде Linux.

1. Интерпретатор Python в экосистеме Linux и создание первого исполняемого скрипта

Когда вы обновляете пакеты в Red Hat (команда dnf) или настраиваете брандмауэр через firewalld, вы неявно запускаете Python-скрипты. В современных дистрибутивах Linux Python — это не просто язык программирования, установленный «сбоку», а фундаментальная часть операционной системы. Базовые системные утилиты написаны на нём, потому что он предлагает идеальный баланс: читаемость и скорость разработки, недоступные в Bash, при наличии прямого доступа к системным вызовам ядра (syscalls), как в C.

Чтобы писать надежные инструменты автоматизации, недостаточно знать синтаксис языка. Необходимо понимать, как текстовый файл с кодом взаимодействует с ядром Linux, файловой системой и переменными окружения.

Системный интерпретатор и правило изоляции

Компьютер не понимает исходный код напрямую. Python является интерпретируемым языком, а точнее — компилируемо-интерпретируемым. Когда вы запускаете скрипт, специальная программа (CPython) сначала транслирует ваш читаемый текст в промежуточный байт-код, а затем виртуальная машина Python выполняет этот байт-код, преобразуя его в команды для процессора и вызовы к ядру Linux.

В большинстве дистрибутивов главный бинарный файл интерпретатора находится по пути /usr/bin/python3. Это так называемый системный Python.

!Взаимодействие скрипта, интерпретатора и ядра Linux

Главное правило системного администратора и DevOps-инженера: никогда не модифицируйте системное окружение Python напрямую. Если вы установите сторонние библиотеки глобально (например, выполнив команду sudo pip install requests), вы рискуете перезаписать зависимости, которые требуются системным утилитам (тому же пакетному менеджеру apt или dnf). Это приведет к поломке самой операционной системы — классическая ситуация «dependency hell» (ад зависимостей).

Для изоляции проектов в Linux используются виртуальные окружения (virtual environments), которые создают локальные копии интерпретатора и библиотек для каждого конкретного скрипта или сервиса. Понимание того, какой именно интерпретатор сейчас выполняет код, критически важно для отладки.

Анатомия исполняемого скрипта

В операционной системе Windows тип файла и способ его запуска определяются расширением (например, .exe или .bat). В Linux расширение файла (включая .py) — это просто часть имени, удобная для человека. Ядро Linux не смотрит на расширение, чтобы понять, как запустить программу.

Для превращения обычного текстового файла в самостоятельную утилиту требуются два компонента: права на исполнение и корректный shebang.

Магия первой строки (Shebang)

Когда вы пытаетесь запустить текстовый файл как программу, ядро Linux вызывает системную функцию execve. Эта функция читает первые два байта файла. Если она видит там шестнадцатеричные значения 0x23 и 0x21 (что в кодировке ASCII соответствует символам #!), ядро понимает: перед ним скрипт, а весь остаток первой строки — это путь к программе-интерпретатору, которой нужно передать этот файл на выполнение.

Эта конструкция называется shebang (от слов hash и bang).

Существует два основных способа написания shebang для Python-скриптов.

Жесткое указание пути:

Этот вариант говорит ядру: «Возьми бинарный файл ровно по этому пути и передай ему мой код». Проблема такого подхода в его негибкости. Если интерпретатор установлен в другом месте (например, в /usr/local/bin/python3 на FreeBSD или macOS), скрипт выдаст ошибку. Кроме того, жесткий путь полностью игнорирует виртуальные окружения.

Динамическое разрешение через env:

Это стандарт индустрии (best practice). Утилита env — это стандартная программа Linux, которая умеет искать исполняемые файлы в директориях, перечисленных в переменной окружения PATH текущего пользователя (которая представляет собой список директорий, разделенных двоеточием, например: /home/user/myenv/bin:/usr/local/bin:/usr/bin).

  • env ищет файл с именем python3 по очереди в каждой директории из списка.
  • Как только файл найден, именно он используется для выполнения скрипта.
  • Если вы активировали виртуальное окружение, оно добавляет свой путь в самое начало переменной PATH.

    Если бы текущая директория была в PATH, возьми конкретный файл прямо отсюда».

    Альтернативный способ запуска — явный вызов интерпретатора с передачей ему файла в качестве аргумента:

    В этом случае shebang игнорируется, а права на исполнение (+x) для самого файла не требуются, так как вы запускаете бинарный файл python3 (к которому у вас есть доступ), а ваш скрипт выступает лишь как текстовый документ, который интерпретатор читает. Однако в DevOps-практике скрипты принято оформлять как самостоятельные утилиты с shebang и правами на запуск.

    Скрытая угроза: проблема символов переноса строки

    Один из самых частых и неочевидных сбоев при работе со скриптами в Linux возникает, если код был написан в среде Windows, а затем перенесен на сервер.

    При попытке запуска такого скрипта вы можете увидеть странную ошибку:

    Текст ошибки сбивает с толку: файл на месте, env существует, python3 установлен. Проблема кроется в невидимых символах переноса строки.

    Исторически сложилось так, что разные операционные системы используют разные символы для обозначения конца строки:

  • Linux и macOS используют один символ: LF (Line Feed, в коде обозначается как \n).
  • Windows использует два символа: CR (Carriage Return) и LF (\r\n).
  • Если вы сохранили файл в Windows, его первая строка выглядит для ядра Linux не как #!/usr/bin/env python3, а как #!/usr/bin/env python3\r.

    Утилита env добросовестно берет строку python3\r и пытается найти в системе исполняемый файл, в имени которого на конце есть невидимый символ возврата каретки. Разумеется, такого файла нет, о чем система и сообщает.

    Для решения этой проблемы в Linux существует утилита dos2unix, которая очищает файл от лишних символов \r:

    Если утилиты нет под рукой, можно использовать потоковый редактор sed, который присутствует в любой Unix-системе: ``bash sed -i 's/\r) и заменяет их на пустоту, сохраняя результат прямо в файл (флаг -i). Понимание таких низкоуровневых нюансов отличает уверенного инженера: скрипт — это не просто логика, это набор байтов, который операционная система должна корректно интерпретировать на каждом этапе от чтения заголовка файла до выделения памяти под процесс.

    2. Переменные и базовые типы данных для хранения системной информации

    Переменные и базовые типы данных для хранения системной информации

    Когда администратор выполняет команду cat /proc/loadavg или читает конфигурационный файл, операционная система Linux отдает ему сплошной текст. В классических bash-скриптах всё является строкой: числа, пути, IP-адреса и логические флаги обрабатываются как текст, который приходится резать утилитами awk, grep или cut. Переход к Python требует смены парадигмы: здесь данные имеют строгую природу. Текст остается текстом, но PID процесса становится числом, с которым можно производить математические операции, а список смонтированных дисков превращается в структурированный массив, где каждый элемент доступен по индексу.

    В Python переменные работают иначе, чем в командной оболочке. В bash переменная — это контейнер, куда складывается значение, и при присваивании категорически запрещены пробелы вокруг знака равенства (PID=1234). В Python переменная — это просто ярлык (ссылка), который привязывается к объекту в оперативной памяти. Пробелы вокруг = не только разрешены, но и требуются стандартом оформления кода PEP 8 для читаемости.

    !Модель переменных в Python: ярлыки, указывающие на объекты в памяти

    Python использует динамическую типизацию. Это означает, что при создании переменной не нужно указывать, какой тип данных она будет хранить. Интерпретатор сам определяет тип объекта в момент выполнения. Более того, один и тот же ярлык можно перевесить на объект совершенно другого типа:

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

    Строки (str): работа с путями и логами

    Строковый тип данных (str) используется для хранения текста: путей к файлам, имен пользователей, вывода команд и содержимого логов. Строки в Python можно заключать как в одинарные, так и в двойные кавычки. Разницы между ними нет, что отличает Python от bash, где двойные кавычки допускают интерполяцию переменных, а одинарные — нет.

    Для подстановки значений переменных внутрь строк в современном Python используются f-строки (форматированные строки). Перед кавычками ставится символ f, а переменные помещаются в фигурные скобки:

    При чтении системных файлов скрипт неизбежно сталкивается с невидимыми символами, такими как перенос строки \n. Если прочитать файл конфигурации построчно, каждая строка будет заканчиваться этим символом, что сломает логику сравнения. Для очистки строк используется метод .strip(), который удаляет пробельные символы и переносы на концах строки.

    Другой важнейший для системного администрирования метод — .split(). Он разбивает строку на части по указанному разделителю и возвращает список. Это идеальный инструмент для разбора файлов с четкой структурой, таких как /etc/passwd:

    Числа (int и float): подсчет ресурсов

    Системные метрики, такие как идентификаторы процессов (PID), идентификаторы пользователей (UID), объем оперативной памяти в байтах или коды возврата команд, хранятся в виде целых чисел — тип int.

    Для метрик, требующих дробных значений (например, средняя загрузка процессора load average, температура датчиков или точное время выполнения скрипта), используется тип float (числа с плавающей точкой).

    В Python встроены стандартные математические операторы, но особое внимание при работе с системными ресурсами стоит уделить делению. Обычное деление / всегда возвращает float, даже если числа делятся без остатка. Если нужно перевести байты в мегабайты и получить целое число, используется целочисленное деление //:

    Частая ошибка начинающих — попытка выполнить математическую операцию над строкой, полученной из файла. Если прочитать значение 1024 из текстового файла, для Python это будет строка "1024". Чтобы использовать её в вычислениях, необходимо явное преобразование типов (кастование) с помощью функции int():

    Логический тип (bool): флаги состояний

    Логический тип bool принимает только два значения: True (истина) и False (ложь). В Python они пишутся строго с заглавной буквы. Логические переменные выступают в роли флагов, сигнализирующих о состоянии системы: запущен ли сервис, существует ли директория, обладает ли пользователь правами root.

    Логические значения часто получаются в результате операций сравнения. Например, проверка того, является ли текущий пользователь суперпользователем, сводится к проверке равенства его UID нулю:

    Списки (list): упорядоченные коллекции

    Когда нужно сохранить набор однотипных данных — список IP-адресов из лога фаервола, аргументы командной строки или перечень активных сетевых интерфейсов — используется тип list. Список создается с помощью квадратных скобок, а элементы разделяются запятыми.

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

    Мощный механизм работы со списками — срезы (slices). Они позволяют извлечь часть списка. Синтаксис среза выглядит как [start:stop], где элемент start включается в результат, а stop — нет. Если нужно получить последние десять строк из массива логов, используется конструкция logs[-10:].

    Для добавления новых элементов в конец существующего списка применяется метод .append(). Это классический паттерн при сборе данных: скрипт инициализирует пустой список, в цикле обходит систему и добавляет найденные аномалии в этот список.

    Словари (dict): структурирование конфигураций

    Если список — это просто пронумерованный ряд элементов, то словарь (dict) — это структура, где каждое значение привязано к уникальному имени (ключу). Словари создаются с помощью фигурных скобок. Это самый важный тип данных для DevOps-инженера, так как он идеально отражает структуру конфигурационных файлов, JSON-ответов от API и переменных окружения.

    !Структура словаря в Python: ключи и значения

    Ключами в словаре могут быть только неизменяемые типы данных (чаще всего строки), а значениями — любые объекты, включая другие списки и словари.

    Если попытаться обратиться к ключу, которого нет в словаре (например, server_info["uptime"]), скрипт аварийно завершит работу с ошибкой KeyError. В системном администрировании конфигурационные файлы часто бывают неполными, и некоторые параметры могут отсутствовать. Чтобы избежать падения скрипта, используется метод .get(). Он запрашивает значение по ключу, но если ключа нет, возвращает специальное значение None (или любое другое значение по умолчанию, если его указать).

    Словари позволяют выстраивать сложные иерархии данных. Например, инвентаризация серверов может быть представлена как словарь, где ключами выступают имена хостов, а значениями — словари с их характеристиками. Такая структура позволяет мгновенно извлекать нужные данные по именам, не перебирая массивы информации в поисках совпадений, что делает скрипты быстрыми и логичными.

    3. Управляющие конструкции и логика обработки конфигурационных файлов

    Управляющие конструкции и логика обработки конфигурационных файлов

    Любая Linux-система управляется текстовыми файлами. Конфигурации сервисов, сетевые настройки, логи — всё это потоки символов. Однако в стандартном файле sshd_config из тысячи строк реально активными могут быть всего двадцать. Остальное — закомментированные дефолтные значения, пустые строки для читаемости и пояснительный текст. Чтобы скрипт мог принимать решения на основе этих данных, ему нужен механизм отделения полезного сигнала от информационного шума.

    Безопасный доступ к файлам: контекстный менеджер

    Прежде чем анализировать текст, файл необходимо открыть, передав его от операционной системы в интерпретатор Python. Базовый способ сделать это — функция open(). Но работа с файловыми дескрипторами в Linux требует строгой дисциплины: каждый открытый файл забирает системные ресурсы, и если скрипт завершится с ошибкой или просто «забудет» закрыть файл, дескриптор останется висеть в памяти (утечка ресурсов).

    Для решения этой проблемы в Python применяется конструкция with.

    !Механика работы контекстного менеджера

    Контекстный менеджер with создает изолированную область (блок кода), внутри которой файл гарантированно открыт и доступен. Как только выполнение программы выходит за пределы этого блока — неважно, естественным путем или из-за критической ошибки — Python автоматически вызывает метод закрытия файла.

    В функции open() передаются три важных аргумента:

  • Путь к файлу.
  • Режим работы: "r" (read) для чтения, "w" (write) для перезаписи, "a" (append) для добавления в конец.
  • Кодировка encoding="utf-8". В Linux UTF-8 является стандартом де-факто, но явное указание защищает скрипт от непредсказуемого поведения при запуске в нестандартных локалях.
  • Итерация: стратегии чтения данных

    Получив доступ к файлу, скрипт должен извлечь из него текст. Выбор метода чтения критически важен для стабильности серверов.

    | Метод | Как работает | Потребление памяти | Применение | | :--- | :--- | :--- | :--- | | file.read() | Загружает весь файл целиком в одну большую строку. | Равно размеру файла. | Чтение мелких файлов (до нескольких мегабайт), таких как /etc/hostname или /etc/timezone. | | file.readlines() | Загружает весь файл, разбивает его на строки и помещает в список. | Выше размера файла (накладные расходы на структуру списка). | Редко оправдано. Опасно для логов. | | for line in file: | Читает файл строго по одной строке за раз, сдвигая внутренний указатель. | Минимальное (размер самой длинной строки). | Золотой стандарт для парсинга логов и объемных конфигураций. |

    Цикл for в сочетании с файловым объектом реализует концепцию «ленивого» (lazy) чтения. Если скрипт анализирует лог-файл веб-сервера размером 10 ГБ, цикл for будет держать в оперативной памяти ровно одну текущую строку.

    В Python границы циклов и логических блоков определяются исключительно отступами (обычно 4 пробела). Здесь нет операторов вроде do...done или фигурных скобок. Сдвиг вправо означает погружение внутрь конструкции, возврат влево — выход из нее.

    Ветвление: логика фильтрации

    Чтение строки — это только начало. Далее вступает в работу условная конструкция if/elif/else. В контексте автоматизации Linux её главная задача — нормализация данных.

    Рассмотрим типичную задачу: чтение пользовательского конфигурационного файла, где строки могут быть пустыми или начинаться с символа # (комментарий).

    В Python логические выражения оцениваются на «истинность» (truthiness). Пустая строка, пустой список или число ноль воспринимаются интерпретатором как ложь (False). Строка, содержащая хотя бы один символ (даже пробел) — как истина (True).

    !Построчная фильтрация файла

    Для управления потоком выполнения внутри цикла используются операторы прерывания:

  • continue — немедленно прерывает текущую итерацию и заставляет цикл перейти к следующей строке. Идеально подходит для отсеивания мусора.
  • break — полностью останавливает цикл. Полезно, если скрипт ищет конкретное значение и, найдя его, не имеет смысла читать оставшиеся тысячи строк.
  • Конструкция not clean_line элегантно проверяет строку на пустоту. Если после применения метода .strip() от строки ничего не осталось (длина стала равна нулю), она оценивается как False. Оператор not инвертирует это значение в True, и срабатывает continue.

    Обработка исключений: защита от падений

    Скрипт автоматизации работает в агрессивной среде. Файла может не существовать, у пользователя может не быть прав на его чтение, или диск может оказаться поврежден. Если просто использовать open(), любая из этих ситуаций вызовет аварийную остановку (Traceback) и прервет выполнение всего пайплайна.

    Для перехвата и обработки таких ситуаций применяется блок try/except.

    Логика try/except работает как система перехвата. Интерпретатор пытается выполнить код в блоке try. Если возникает ошибка, он немедленно прекращает выполнение этого блока и начинает искать подходящий блок except, соответствующий типу возникшей ошибки. Указание конкретных типов ошибок (FileNotFoundError, PermissionError) позволяет скрипту реагировать осмысленно: например, запросить повышение привилегий или создать конфигурацию по умолчанию, вместо того чтобы просто упасть.

    Практический сценарий: сбор данных из /etc/os-release

    Объединим изученные конструкции для решения реальной задачи. Файл /etc/os-release присутствует в большинстве современных дистрибутивов Linux и содержит информацию о версии ОС в формате КЛЮЧ=ЗНАЧЕНИЕ.

    Особенность этого файла в том, что значения часто заключены в двойные кавычки (например, PRETTY_NAME="Ubuntu 22.04.3 LTS"). Наша задача — прочитать файл, отфильтровать лишнее, разделить ключи и значения, очистить значения от кавычек и сохранить всё в удобный словарь.

    В этом сценарии цикл for обеспечивает безопасное чтение файла любого размера. Конструкция if берет на себя роль сита, отсеивая мусор. Метод .split("=", 1) гарантирует, что даже если в самом значении встретится знак равенства, строка будет разрезана только по первому из них. Блок try/except делает скрипт отказоустойчивым на случай запуска в нестандартной среде (например, в минималистичном Docker-контейнере без os-release).

    Управляющие конструкции превращают линейный набор команд в интеллектуальный инструмент. Они позволяют скрипту динамически адаптироваться к содержимому файлов, безопасно обходить ограничения среды и формировать структурированные структуры данных, готовые к дальнейшей передаче в системы мониторинга или API.

    4. Взаимодействие с окружением через стандартные модули os и sys

    Взаимодействие с окружением через стандартные модули os и sys

    Скрипт, который идеально работает на ноутбуке разработчика, часто падает при первом же запуске в CI/CD пайплайне или через cron. Причина редко кроется в синтаксических ошибках. Чаще всего скрипт оказывается «слепым»: он не знает, с какими флагами его запустили, где лежат конфигурационные файлы на новом сервере и как сообщить операционной системе, что произошел сбой. В Linux программа не существует в вакууме — она общается с оболочкой (shell) и ядром. В Python роль моста между вашим кодом и операционной системой выполняют встроенные модули sys и os.

    Чтение аргументов командной строки через sys.argv

    Любая стандартная утилита Linux принимает аргументы. Когда вы пишете tail -n 50 /var/log/syslog, утилита tail понимает, что нужно вывести именно 50 строк из конкретного файла. Чтобы ваши Python-скрипты могли работать так же гибко, используется модуль sys.

    После импорта модуля (import sys) вам становится доступен объект sys.argv. С точки зрения Python, это самый обычный список (list), в котором хранятся слова, переданные в терминале при запуске скрипта.

    !Маппинг команды оболочки в список sys.argv

    Главная особенность, о которой часто забывают: нулевой индекс sys.argv[0] всегда содержит имя самого запущенного скрипта. Реальные аргументы, переданные пользователем, начинаются с индекса 1.

    Рассмотрим скрипт check_service.py, который должен проверять статус конкретного демона:

    Если запустить его командой python3 check_service.py nginx, список sys.argv будет состоять из двух элементов: ['check_service.py', 'nginx'].

    При работе с sys.argv важно помнить два правила. Во-первых, оболочка Linux разбивает аргументы по пробелам. Если вы хотите передать строку с пробелами как один аргумент, в терминале её нужно взять в кавычки: python3 script.py "my long argument". Во-вторых, все элементы в sys.argv — это строки. Если вы передаете число (например, порт 8080), внутри скрипта оно будет строкой '8080'. Для математических операций или использования в качестве порта для сетевого соединения его придется явно преобразовать в целое число с помощью int().

    Для сложных CLI-инструментов с десятками флагов (вида --verbose, --port 8080) парсить sys.argv вручную становится неудобно. В таких случаях используют модуль argparse, но под капотом он всё равно опирается на сырой список sys.argv.

    Диалог с оболочкой: коды возврата и sys.exit()

    В философии UNIX каждая завершенная программа оставляет после себя след — код возврата (exit status). Это целое число от 0 до 255. Ноль означает «всё прошло успешно», любое другое число означает ошибку. Оболочка Linux (например, bash) использует эти коды для управления логикой.

    Если вы пишете в терминале apt update && apt upgrade, вторая команда (upgrade) запустится только в том случае, если первая (update) вернет код 0. Если Python-скрипт просто доходит до последней строчки кода и завершается, он автоматически возвращает 0. Но если внутри скрипта мы обнаружили проблему (например, нет нужного файла конфигурации), мы обязаны сообщить Linux об ошибке, иначе bash сочтет выполнение успешным и продолжит пайплайн.

    !Влияние кода возврата Python на bash-пайплайн

    Для явного завершения программы с нужным кодом используется функция sys.exit().

    У функции sys.exit() есть крайне полезная для DevOps-инженеров особенность. Если передать в неё не число, а строку текста, Python сделает две вещи одновременно:

  • Выведет эту строку в стандартный поток ошибок (stderr), а не в обычный вывод (stdout).
  • Завершит программу с кодом 1.
  • Это позволяет сократить код обработки критических ошибок до одной строки:

    Разделение на stdout и stderr критически важно при автоматизации. Если ваш скрипт генерирует полезные данные (например, JSON с метриками), их обычно перенаправляют в другой инструмент: python3 get_metrics.py > metrics.json. Если бы сообщение об ошибке печаталось обычным print(), оно бы попало внутрь файла metrics.json, сломав его структуру. Использование sys.exit("Ошибка") гарантирует, что текст ошибки появится на экране терминала, а файл останется пустым, при этом пайплайн прервется.

    Чтение переменных окружения через os.environ

    Переменные окружения — это стандартный способ передачи секретов (токенов, паролей) и настроек в контейнерах Docker и системах CI/CD. Хардкодить пути или пароли прямо в коде Python — плохая практика. Скрипт должен адаптироваться к среде, в которой запущен.

    Модуль os предоставляет объект os.environ. По поведению он очень похож на обычный Python-словарь, где ключи — это имена переменных окружения, а значения — их содержимое (всегда в виде строк).

    Однако, если переменная DB_HOST не была задана в системе, этот код вызовет исключение KeyError и скрипт упадет. В инфраструктурных задачах часто требуется использовать значения по умолчанию, если переменная не задана. Для этого лучше использовать метод os.getenv().

    Важный нюанс, на котором часто спотыкаются новички: изменение os.environ внутри Python-скрипта (например, os.environ["NEW_VAR"] = "123") влияет только на сам этот скрипт и на дочерние процессы, которые он может запустить. Оно никак не изменит переменные окружения в родительской оболочке bash, из которой скрипт был вызван. Процесс не может модифицировать окружение своего родителя на уровне ядра Linux.

    Навигация по файловой системе с os.path

    При написании автоматизации постоянной задачей является работа с путями к файлам. В Linux разделителем директорий является прямой слеш (/). Можно просто склеивать строки: dir + "/" + filename. Но этот подход хрупок — если переменная dir уже содержит слеш на конце, вы получите двойной слеш (/var/log//syslog), что иногда приводит к неочевидному поведению утилит.

    Подмодуль os.path содержит инструменты для безопасной работы с путями. Самый часто используемый метод — os.path.join(). Он берет на себя заботу о правильном количестве разделителей.

    Перед тем как пытаться открыть файл на чтение, хорошей практикой является проверка его существования. Метод os.path.exists() возвращает True, если путь существует (неважно, файл это, директория или симлинк). Если нужно убедиться, что по указанному пути находится именно обычный файл, а не директория, используется os.path.isfile().

    Также в арсенале os.path есть методы os.path.basename() и os.path.dirname(). Если вам на вход поступил полный путь /etc/nginx/nginx.conf, basename вернет только имя файла (nginx.conf), а dirname — только путь к директории (/etc/nginx). Это избавляет от необходимости вручную резать строки по символу слеша.

    Используя sys.argv для получения команд, os.environ для чтения конфигурации среды, os.path для безопасной работы с путями и sys.exit для правильного информирования системы об ошибках, скрипт превращается из изолированного куска кода в полноценного участника экосистемы Linux. Он корректно встраивается в bash-пайплайны, безопасно принимает секреты через переменные окружения и предсказуемо реагирует на отсутствие нужных файлов.

    5. Практическая сборка скрипта для инвентаризации системных ресурсов

    Практическая сборка скрипта для инвентаризации системных ресурсов

    Представьте парк из полусотни серверов. На одних крутятся базы данных, на других — веб-приложения, третьи простаивают. Внезапно поступает задача: составить актуальную таблицу с указанием моделей процессоров, объема оперативной памяти и свободного места на корневых разделах всех машин. Заходить на каждый сервер по SSH и вручную копировать вывод утилит htop или df -h — прямой путь к ошибкам и потраченному дню. Намного эффективнее написать собственный инструмент, который соберет эти метрики и выдаст их в строгом, машиночитаемом формате.

    В Linux всё есть файл. Эта фундаментальная парадигма операционной системы позволяет нам использовать базовые возможности Python для чтения текста, чтобы получить глубокий доступ к состоянию ядра и железа. Мы соберем единый скрипт инвентаризации, опираясь исключительно на стандартную библиотеку языка.

    Иллюзия файлов: чтение данных из ядра через procfs

    Когда администратор вводит в терминале команду free -m или lscpu, эти утилиты не измеряют напряжение на контактах процессора и не сканируют чипы памяти напрямую. Они обращаются к директории /proc.

    Директория /proc содержит виртуальную файловую систему (procfs). Файлов, которые вы там видите, физически не существует на жестком диске. Это иллюзия, интерфейс, через который ядро Linux экспортирует свои внутренние структуры данных в пользовательское пространство. Когда Python открывает файл на чтение из /proc, ядро «на лету» генерирует текстовый ответ, отражающий состояние системы ровно в ту миллисекунду, когда произошел вызов.

    !Архитектура виртуальной файловой системы procfs

    Поскольку это обычный текст, мы можем применять к нему стандартные строковые методы. Начнем со сбора информации о центральном процессоре.

    Извлечение характеристик процессора

    Вся информация о CPU хранится в виртуальном файле /proc/cpuinfo. Если вывести его содержимое, мы увидим блоки текста, разделенные пустыми строками — по одному блоку на каждое логическое ядро.

    Нас интересуют два параметра: точное название модели процессора и общее количество ядер. Название модели записано в строках, начинающихся с model name, например: model name : Intel(R) Core(TM) i7-10700K CPU @ 3.80GHz.

    Напишем функцию, которая извлечет эти данные.

    Обратите внимание на использование split(":", 1). Второй аргумент 1 указывает интерпретатору сделать ровно одно разделение. Это критически важно при парсинге системных логов и конфигураций: если в самом названии модели процессора случайно окажется символ двоеточия, строка не разобьется на лишние фрагменты, и данные не исказятся.

    Парсинг состояния оперативной памяти

    Следующая цель — оперативная память. За нее отвечает виртуальный файл /proc/meminfo. Его структура проще: это плоский список параметров. Нас интересуют строки MemTotal (общий объем физической памяти) и MemAvailable (память, доступная для запуска новых процессов без ухода в своп).

    Формат строк в этом файле выглядит так: MemTotal: 16393452 kB. Единица измерения жестко зафиксирована в килобайтах. Для инвентаризации нам удобнее оперировать гигабайтами.

    Здесь метод .split() вызывается без аргументов. В таком режиме Python автоматически схлопывает любое количество идущих подряд пробелов и табуляций, разбивая строку на чистые слова. Нам не нужно высчитывать точное количество пробелов между двоеточием и числом.

    Системные вызовы для файловой системы: os.statvfs

    Если информацию о процессоре и памяти легко прочитать из текстовых файлов, то с дисковым пространством ситуация иная. Данные о смонтированных разделах разбросаны по системе, и парсить их вручную ненадежно. Здесь на помощь приходит модуль os и его функция statvfs(), которая выполняет прямой системный вызов к ядру для получения статистики файловой системы.

    В операционных системах семейства POSIX (включая Linux) дисковое пространство измеряется не байтами, а блоками. Блок — это минимальная порция данных, которую файловая система может выделить для хранения файла. Чтобы узнать реальный объем диска, нужно умножить количество блоков на размер одного блока.

    Функция os.statvfs("/") возвращает специальный объект с атрибутами. Нас интересуют три из них:

  • f_frsize — размер одного блока во фрагментах (в байтах).
  • f_blocks — общее количество блоков в файловой системе.
  • f_bavail — количество свободных блоков, доступных обычному (непривилегированному) пользователю.
  • Существует также атрибут f_bfree (абсолютно все свободные блоки), но в Linux файловые системы ext4 по умолчанию резервируют около 5% места исключительно для пользователя root. Если скрипт будет запущен от имени обычного пользователя и попытается записать данные, опираясь на f_bfree, он может получить ошибку нехватки места. Поэтому для мониторинга всегда используется f_bavail.

    Формула расчета доступного места в байтах выглядит так:

    Реализуем это в виде функции, переведя байты в гигабайты.

    Эта функция универсальна: передав в неё путь /var/log или /mnt/data, можно точечно проверять состояние конкретных точек монтирования.

    Сборка единого инструмента и сериализация

    Теперь у нас есть три независимые функции, собирающие метрики. Чтобы превратить их в полноценный инструмент инвентаризации, нужно объединить их вызовы и сформировать итоговую структуру данных.

    Для передачи данных между системами (например, отправки в систему мониторинга Zabbix, Prometheus или просто сохранения в лог) стандартом де-факто является формат JSON. В стандартной библиотеке Python есть встроенный модуль json, который умеет превращать словари в отформатированные строки. Этот процесс называется сериализацией.

    Соберем финальный скрипт inventory.py:

    В этом коде мы использовали os.uname().nodename — еще один системный вызов, возвращающий сетевое имя узла (hostname). Конструкция if __name__ == "__main__": гарантирует, что функция main() запустится только при прямом исполнении скрипта, а не при его импорте как модуля в другой файл.

    Запустив этот скрипт на сервере, мы получим строго структурированный ответ:

    Такой скрипт уже не просто выводит текст на экран. Он генерирует объект, который можно перенаправить в файл (./inventory.py > server_03.json), передать по сети через curl в базу данных или использовать как входные данные для инструментов автоматической настройки инфраструктуры. Мы скрыли хаос текстовых файлов ядра под аккуратным, предсказуемым интерфейсом, используя только встроенные возможности языка.