Работа с файлами и обработка ошибок в скриптах - DevOps с нуля: Путь к автоматизации и CI/CD - Qpel.AI

Работа с файлами и обработка ошибок в скриптах

В прошлом уроке вы освоили основы Bash: переменные, условия и циклы. Это отличный старт для автоматизации! Но в реальных задачах скриптам часто нужно работать с файлами и не падать от каждой ошибки. Сегодня мы сделаем ваши скрипты по-настоящему надёжными и функциональными.

Управляем файлами и каталогами в Bash

Bash — мощный инструмент для работы с файловой системой. Вы уже знакомы с ls, cp, mv, rm, mkdir, touch из командной строки. В скриптах эти команды работают точно так же, но их можно комбинировать с условиями и циклами, чтобы создавать сложную логику.

Проверяем файлы и каталоги

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

ОператорОписаниеПример
-eФайл или каталог существуетif [ -e "файл.txt" ]; then ...
-fСуществует и является обычным файломif [ -f "файл.txt" ]; then ...
-dСуществует и является каталогомif [ -d "мой_каталог" ]; then ...
-rФайл существует и доступен для чтенияif [ -r "файл.txt" ]; then ...
-wФайл существует и доступен для записиif [ -w "файл.txt" ]; then ...
-xФайл существует и доступен для выполненияif [ -x "скрипт.sh" ]; then ...

Пример: Скрипт проверит, есть ли report.log. Если нет — создаст его и запишет текущую дату.

#!/bin/bash

LOG_FILE="report.log"

if [ ! -f "$LOG_FILE" ]; then # Если файла НЕ существует
    echo "Файл $LOG_FILE не найден. Создаю..."
    touch "$LOG_FILE" # Создаём файл
    if [ $? -eq 0 ]; then # Проверяем, успешно ли создали
        echo "Файл $LOG_FILE успешно создан."
    else
        echo "Ошибка при создании файла $LOG_FILE."
        exit 1 # Выходим с ошибкой
    fi
fi

echo "Текущая дата: $(date)" >> "$LOG_FILE" # Добавляем дату в файл
echo "Запись добавлена в $LOG_FILE."

Важно: ! -f "$LOG_FILE" означает "если файл НЕ является обычным файлом". Восклицательный знак ! инвертирует условие.

Читаем и пишем в файлы

Вы уже умеете перенаправлять вывод команд в файлы с помощью > (перезаписать) и >> (добавить). Для чтения содержимого файлов обычно используют cat, а для построчного чтения в скриптах — цикл while с командой read.

Пример: Скрипт читает файл построчно и выводит каждую строку с номером.

#!/bin/bash

INPUT_FILE="my_list.txt"

# Создадим тестовый файл, если его нет
if [ ! -f "$INPUT_FILE" ]; then
    echo "Элемент 1" > "$INPUT_FILE"
    echo "Элемент 2" >> "$INPUT_FILE"
    echo "Элемент 3" >> "$INPUT_FILE"
fi

LINE_NUM=1
while IFS= read -r LINE; do # Читаем файл построчно
    echo "Строка $LINE_NUM: $LINE"
    LINE_NUM=$((LINE_NUM + 1))
done < "$INPUT_FILE"

Пояснение:

  • while IFS= read -r LINE; do ... done < "$INPUT_FILE": Это самый надёжный способ построчного чтения файла.
  • IFS=: Устанавливает Internal Field Separator (разделитель полей) в пустую строку. Это нужно, чтобы read не обрезал пробелы в начале и конце строки.
  • -r: Запрещает read интерпретировать обратные слеши как управляющие символы.

Обрабатываем ошибки и выходим из скрипта

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

Код возврата команды ($?)

Каждая команда в Linux после выполнения возвращает код выхода (exit code):

  • 0: Команда выполнилась успешно.
  • Любое другое число (обычно от 1 до 255): Команда завершилась с ошибкой.

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

Пример: Проверим, успешно ли скопировался файл.

#!/bin/bash

SOURCE_FILE="source.txt"
DEST_DIR="backup"

# Создадим тестовый файл
echo "Это исходный файл" > "$SOURCE_FILE"

# Создадим каталог для бэкапа
mkdir -p "$DEST_DIR"

cp "$SOURCE_FILE" "$DEST_DIR/" # Копируем файл

if [ $? -eq 0 ]; then # Проверяем код выхода: 0 = успех
    echo "Файл $SOURCE_FILE успешно скопирован в $DEST_DIR."
else
    echo "Ошибка при копировании файла $SOURCE_FILE."
    exit 1 # Завершаем скрипт с кодом ошибки
fi

# Попробуем скопировать несуществующий файл
cp "non_existent_file.txt" "$DEST_DIR/"

if [ $? -ne 0 ]; then # Проверяем, что код выхода НЕ равен 0 (то есть ошибка)
    echo "Ожидаемая ошибка: файл 'non_existent_file.txt' не найден."
else
    echo "Неожиданный успех при копировании несуществующего файла!"
fi

Завершаем скрипт с exit

Команда exit позволяет завершить скрипт и вернуть нужный код выхода. Это очень важно для автоматизированных систем, таких как CI/CD пайплайны, которые используют этот код для определения успешности шага.

#!/bin/bash

# Пример использования exit
read -p "Введите число: " NUMBER

if ! [[ "$NUMBER" =~ ^[0-9]+$ ]]; then # Проверяем, что введено число
    echo "Ошибка: Введено не число."
    exit 100 # Завершаем скрипт с кодом ошибки 100
else
    echo "Вы ввели число: $NUMBER"
    exit 0 # Завершаем скрипт успешно
fi

Автоматическая остановка при ошибке (set -e)

Для более надёжных скриптов часто используют set -e. Эта команда говорит Bash: "Если любая команда завершится с ненулевым кодом выхода, немедленно останови скрипт". Это предотвращает выполнение следующих команд, которые могли бы работать с некорректными данными из-за предыдущей ошибки.

#!/bin/bash
set -e # Включаем режим немедленного выхода при ошибке

echo "Начинаем выполнение скрипта..."

# Эта команда выполнится успешно
mkdir -p "temp_dir"

# Эта команда завершится с ошибкой, так как файл не существует.
# Скрипт остановится здесь из-за 'set -e'.
cp "non_existent_file.txt" "temp_dir/"

echo "Эта строка не будет выполнена, если предыдущая команда завершится с ошибкой."

# Если бы cp не выдал ошибку, скрипт продолжил бы выполнение
rmdir "temp_dir"

echo "Скрипт завершен успешно (если бы не было ошибки)."

Совет: set -e — очень полезная опция для большинства DevOps-скриптов. Она делает их предсказуемыми и безопасными. Но иногда нужно обработать ошибку, не останавливая скрипт. В таких случаях можно временно отключать set -e или использовать command || true (чтобы команда всегда возвращала 0).

Практическое задание: бэкап логов 💾

Создайте Bash-скрипт backup_logs.sh, который:

  1. Принимает один аргумент: путь к каталогу с логами.
  2. Проверяет, что этот путь существует и является каталогом. Если нет, выводит ошибку и завершает скрипт с ненулевым кодом.
  3. Создаёт подкаталог backups внутри каталога с логами, если его ещё нет.
  4. Находит все файлы с расширением .log в исходном каталоге.
  5. Копирует каждый найденный .log файл в backups, добавляя к имени текущую дату и время в формате _YYYYMMDD_HHMMSS. Например, app.log станет app.log_20250123_143500.
  6. После копирования каждого файла выводит сообщение об успехе или ошибке.
  7. В конце выводит общее количество скопированных файлов.

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

./backup_logs.sh /var/log/myapp

Ожидаемый вывод (пример):

Проверяем каталог: /var/log/myapp
Каталог /var/log/myapp существует.
Создаем каталог для бэкапов: /var/log/myapp/backups
Копируем app.log -> /var/log/myapp/backups/app.log_20250123_143500
Успешно скопировано: app.log
Копируем auth.log -> /var/log/myapp/backups/auth.log_20250123_143501
Успешно скопировано: auth.log
Всего скопировано файлов: 2

Теперь вы не просто пишете логику, но и делаете скрипты устойчивыми к ошибкам и способными работать с файловой системой. Это критически важные навыки для любого DevOps-инженера, ведь автоматизация часто связана с перемещением, изменением и анализом файлов.

В следующем разделе мы погрузимся в Git — систему контроля версий, которая является основой для совместной разработки и неотъемлемой частью любого CI/CD процесса. Вы узнаете, как установить Git и использовать его базовые команды для отслеживания изменений в вашем коде.