В прошлом уроке вы освоили основы 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, который:
- Принимает один аргумент: путь к каталогу с логами.
- Проверяет, что этот путь существует и является каталогом. Если нет, выводит ошибку и завершает скрипт с ненулевым кодом.
- Создаёт подкаталог
backupsвнутри каталога с логами, если его ещё нет. - Находит все файлы с расширением
.logв исходном каталоге. - Копирует каждый найденный
.logфайл вbackups, добавляя к имени текущую дату и время в формате_YYYYMMDD_HHMMSS. Например,app.logстанетapp.log_20250123_143500. - После копирования каждого файла выводит сообщение об успехе или ошибке.
- В конце выводит общее количество скопированных файлов.
Пример использования:
./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 и использовать его базовые команды для отслеживания изменений в вашем коде.