Разработка Bash-скриптов — суперважный навык для инженеров. Будь то автоматизация повторяющихся задач, объединение инструментов или управление системами, Bash всегда рядом, простой, но мощный.

Но, как и любой эффективный инструмент, он требует мастерства. В этой статье вы узнаете о 10 важнейших конструкциях Bash, используемых в реальном сценарии.

Сценарий

Представьте, что перед вами стоит задача проанализировать логовые записи сервера из нескольких файлов, извлечь неудачные попытки входа и создать отчет. Это рутинная задача, но Bash позволяет найти элегантное и многократно используемое решение.

1. Создание сцены с помощью скрипта

Начинаем выполнение поставленной задачи с написания скелета скрипта:

#!/bin/bash

set -e # Выход при условии ошибок 
trap 'echo "Error on line $LINENO"; exit 1' ERR

Объяснение:

  • set -e гарантирует остановку скрипта при первых признаках проблемы;
  • trap перехватывает ошибки, предоставляя полезную отладочную информацию.

2. Обеспечение модульной организации с помощью функций

Эффективные скрипты имеют модульную организацию. Определим функцию для парсинга лог-файлов:

parse_logs() {

  local file="$1"
  local output="$2"

  while read -r line; do
    if [[ "$line" == *"FAILED LOGIN"* ]]; then
        echo "$line" >> "$output"
    fi

  done < "$file"
}

Объяснение:

  • функции делают скрипты многократно используемыми и удобными в обслуживании;
  • переменные local предотвращают случайную перезапись.

3. Массивы: управление несколькими логами

Нам необходимо обрабатывать логи с нескольких серверов:

log_files=("server1.log" "server2.log" "server3.log")
results=()

for file in "${log_files[@]}"; do
    output="${file%.log}_failed.log"
    parse_logs "$file" "$output"
    results+=("$output")
done

Объяснение

  • массивы помогают эффективно управлять списками элементов;
  • добавляем обработанные результаты в массив для последующих действий.

4. Подстановка команд: добавление временных меток

Добавим временные метки в выходные файлы с помощью команды date:

timestamp=$(date "+%Y-%m-%d")
final_report="failed_logins_$timestamp.txt"

Объяснение:

  • подстановка команд позволяет легко интегрировать динамические значения в скрипты.

5. Манипулирование строками

Перед объединением логов нужно очистить имена выходных файлов:

for file in "${results[@]}"; do
    sanitized_name="${file// /_}"  # Замените пробелы на символы подчеркивания
    mv "$file" "$sanitized_name"
done

Объяснение:

  • расширение параметров в Bash упрощает преобразование строк без использования внешних инструментов.

6. Подстановка процессов: объединение файлов

Чтобы эффективно объединить логи, выполним следующее:

cat "${results[@]}" > "$final_report"

Объяснение:

  • подстановка процессов и расширение массивов позволяют быстро и эффективно работать с несколькими файлами.

7. Условная логика: настройка отчета

Настроим итоговый отчет в зависимости от его содержания:

if [[ -s "$final_report" ]]; then
    echo "Report generated: $final_report"
else
    echo "No failed logins found."
    rm "$final_report"
fi

Объяснение:

  • if обеспечивает выполнение действий, зависящих от контекста, например, от того, пуст ли отчет.

8. Оператор case: порты по умолчанию

Представьте, что вам нужно определить порты SSH и HTTPS по умолчанию в зависимости от типа сервера:

get_port() {
    local server="$1"
    case "$server" in
        "prod"*) echo 22 ;;
        "staging"*) echo 2222 ;;
        *) echo 80 ;;
    esac
}

Объяснение:

  • case идеально подходит для элегантной работы с несколькими конкретными шаблонами.

9. Отладка с помощью set -x

Прежде чем развернуть скрипт, отладим его:

set -x # Включение отладки
# Здесь происходит запуск главного скрипта
set +x # Отключение отладки

Объяснение:

  • такие инструменты отладки, как set -x, позволяют легко отслеживать и исправлять ошибки.

10. Дескрипторы файлов для расширенного ввода-вывода

Представим, что вы читаете и обрабатываете логи из специального входного потока:

exec 3<"$final_report"

while read -u3 line; do
    echo "Processed: $line"
done

exec 3<&-

Объяснение:

  • дескрипторы файлов обеспечивают точный контроль над входами и выходами, позволяя выполнять параллельную обработку.

Окончательный вариант скрипта

Вот как может выглядеть готовый скрипт:

#!/bin/bash

set -e
trap 'echo "Error on line $LINENO"; exit 1' ERR
parse_logs() {
    local file="$1"
    local output="$2"
    while read -r line; do
        if [[ "$line" == *"FAILED LOGIN"* ]]; then
            echo "$line" >> "$output"
        fi
    done < "$file"
}
log_files=("server1.log" "server2.log" "server3.log")
results=()
for file in "${log_files[@]}"; do
    output="${file%.log}_failed.log"
    parse_logs "$file" "$output"
    results+=("$output")
done
timestamp=$(date "+%Y-%m-%d")
final_report="failed_logins_$timestamp.txt"
cat "${results[@]}" > "$final_report"
if [[ -s "$final_report" ]]; then
    echo "Report generated: $final_report"
else
    echo "No failed logins found."
    rm "$final_report"
fi

Выводы

В этом скрипте собрано почти все, что нужно инженеру для профессионального написания Bash-скриптов: модульность, обработка ошибок, эффективная обработка данных и инструменты отладки.

Освоив эти конструкции, вы сможете не только писать лучшие скрипты, но и использовать элегантные решения для выполнения рутинных задач.

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

5 дополнительных конструкций Bash

Ознакомьтесь с еще 5 Bash-конструкциями, которые должен знать каждый инженер.

11. Ассоциативные массивы

Что это такое: ассоциативные массивы — пары «ключ-значение» в Bash, доступные начиная с Bash 4 и позволяющие эффективно искать и организовывать данные.

Пример: сопоставление имен серверов с их IP-адресами:

declare -A servers

servers=( ["web"]="192.168.1.10" ["db"]="192.168.1.20" ["cache"]="192.168.1.30" )

# Доступ к свойствам
echo "Web server IP: ${servers[web]}"
# Итерация по ключам
for key in "${!servers[@]}"; do
    echo "$key -> ${servers[$key]}"
done

Зачем использовать ассоциативные массивы:

  • для обеспечения естественного способа работы со структурированными данными без использования внешних инструментов, таких как awk или sed;
  • для конфигураций, поиска и динамической организации данных в скриптах.

12. Heredoc-синтаксис для многострочного ввода

Что это такое: Heredoc-синтаксис позволяет вводить многострочные строки (multi-line strings) непосредственно в скрипты, улучшая читабельность при работе с шаблонами или большими объемами данных.

Пример: динамическая генерация шаблона электронной почты:

email_body=$(cat <<EOF
Hello Team,

This is a reminder for the upcoming deployment at midnight.

Regards,
DevOps
EOF)

echo "$email_body" | mail -s "Deployment Reminder" [email protected]

Зачем использовать Heredoc-синтаксис:

  • для устранения необходимости в сложных конкатенациях строк или внешних файлах;
  • для упрощения работы с многострочным контентом, таким как логи, шаблоны или команды, непосредственно в скрипте.

13. Команда eval для динамического выполнения команд

Что это такое: команда eval позволяет выполнить динамически созданную строку в качестве команды Bash.

Пример 1-й: выполнение команды, хранящейся в переменной:

cmd="ls -l"
eval "$cmd"

Пример 2-й: динамическое установление переменных:

var_name="greeting"
eval "$var_name='Hello, World!'"
echo "$greeting"

Зачем использовать eval:

  • для обеспечения гибкости при работе с динамически генерируемыми командами или вводом;

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

14. Подоболочки для изолированного выполнения

Что это такое: подоболочка (subshell) — дочерний процесс, в котором можно выполнять команды, не затрагивая родительскую оболочку.

Пример: выполнение команд при временном изменении каталогов:

(current_dir=$(pwd)
cd /tmp
echo "Now in $(pwd)"
)
echo "Back in $current_dir"

Зачем использовать подоболочки:

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

15. Именованные конвейеры (FIFO)

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

Пример: создание именованного конвейера для передачи данных между процессами:

mkfifo my_pipe

# В одном терминале: запись в конвейер
echo "Hello from process 1" > my_pipe

# В другом терминале: чтение из конвейера
cat < my_pipe

# Очистка
rm my_pipe

Зачем использовать именованные конвейеры:

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

Заключение

Дополнительные конструкции — ассоциативные массивы, heredoc-синтаксис, команда eval, подоболочки и именованные конвейеры — расширят ваш набор инструментов для написания Bash-скриптов, позволив решать более сложные задачи.

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

Читайте также:

Читайте нас в Telegram, VK и Дзен


Перевод статьи Amine Raji: 10 Bash Scripting Constructs Every Engineer Should Know

Предыдущая статья18 полезных скриптов автоматизации на Python. Часть 2
Следующая статьяis-A против has-A