Home » Массивы в shell-скриптах — основы и примеры
Массивы в shell-скриптах — основы и примеры

Массивы в shell-скриптах — основы и примеры

Массивы в shell-скриптах — это та штука, которая может превратить твой беспорядочный скрипт в элегантное решение. Если ты когда-нибудь копался в логах, управлял списками серверов или автоматизировал рутинные задачи, то наверняка сталкивался с ситуацией, когда нужно обработать кучу данных. Вместо того чтобы городить переменные вроде server1, server2, server3, можно использовать массивы и сделать код читаемым и масштабируемым.

В этой статье разберём, как работают массивы в bash, как их быстро внедрить в свои скрипты и на каких подводных камнях можно споткнуться. Плюс покажу несколько реальных кейсов из практики серверного администрирования.

Как это работает: основы массивов в bash

Массив в bash — это переменная, которая может хранить множество значений. В отличие от обычных переменных, которые держат одно значение, массивы позволяют работать с коллекциями данных.

Основные типы массивов:

  • Индексные массивы — элементы нумеруются от 0
  • Ассоциативные массивы — элементы индексируются строками (доступно с bash 4.0+)

Создание массива:

# Способ 1: объявление и заполнение
servers=("web1.example.com" "web2.example.com" "db1.example.com")

# Способ 2: поэлементное заполнение
servers[0]="web1.example.com"
servers[1]="web2.example.com"
servers[2]="db1.example.com"

# Способ 3: объявление с declare
declare -a servers=("web1.example.com" "web2.example.com")

Доступ к элементам:

# Получить первый элемент
echo ${servers[0]}

# Получить все элементы
echo ${servers[@]}

# Получить все элементы (альтернативный синтаксис)
echo ${servers[*]}

# Получить количество элементов
echo ${#servers[@]}

# Получить индексы всех элементов
echo ${!servers[@]}

Быстрая настройка: пошаговое руководство

Давайте создадим практический скрипт для мониторинга серверов. Это классический кейс, где массивы показывают свою силу.

Шаг 1: Создаём базовый скрипт

#!/bin/bash

# Объявляем массив серверов
declare -a servers=(
    "192.168.1.10"
    "192.168.1.11"
    "192.168.1.12"
    "web.example.com"
)

# Массив портов для проверки
declare -a ports=(22 80 443 3306)

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

Шаг 2: Добавляем логику проверки

#!/bin/bash

declare -a servers=("192.168.1.10" "192.168.1.11" "web.example.com")
declare -a ports=(22 80 443)

# Функция проверки доступности
check_server() {
    local server=$1
    local port=$2
    
    if timeout 3 bash -c "echo >/dev/tcp/$server/$port" 2>/dev/null; then
        echo "✓ $server:$port - доступен"
        return 0
    else
        echo "✗ $server:$port - недоступен"
        return 1
    fi
}

# Проверяем каждый сервер на каждом порту
for server in "${servers[@]}"; do
    echo "Проверяем сервер: $server"
    for port in "${ports[@]}"; do
        check_server "$server" "$port"
    done
    echo "---"
done

Шаг 3: Добавляем статистику

#!/bin/bash

declare -a servers=("192.168.1.10" "192.168.1.11" "web.example.com")
declare -a ports=(22 80 443)
declare -a failed_checks=()

check_server() {
    local server=$1
    local port=$2
    
    if timeout 3 bash -c "echo >/dev/tcp/$server/$port" 2>/dev/null; then
        echo "✓ $server:$port - доступен"
        return 0
    else
        echo "✗ $server:$port - недоступен"
        failed_checks+=("$server:$port")
        return 1
    fi
}

# Основная логика проверки
for server in "${servers[@]}"; do
    echo "Проверяем сервер: $server"
    for port in "${ports[@]}"; do
        check_server "$server" "$port"
    done
    echo "---"
done

# Выводим статистику
echo "Общее количество проверок: $((${#servers[@]} * ${#ports[@]}))"
echo "Неудачных проверок: ${#failed_checks[@]}"

if [ ${#failed_checks[@]} -gt 0 ]; then
    echo "Проблемные сервисы:"
    for failed in "${failed_checks[@]}"; do
        echo "  - $failed"
    done
fi

Ассоциативные массивы: продвинутый уровень

Ассоциативные массивы — это мощный инструмент для работы с парами ключ-значение. Идеально подходят для конфигураций и маппингов.

#!/bin/bash

# Объявляем ассоциативный массив
declare -A server_config=(
    ["web1"]="192.168.1.10:80"
    ["web2"]="192.168.1.11:80"
    ["db1"]="192.168.1.20:3306"
    ["cache"]="192.168.1.30:6379"
)

# Функция для парсинга конфигурации
parse_server_config() {
    local config=$1
    local host=$(echo "$config" | cut -d':' -f1)
    local port=$(echo "$config" | cut -d':' -f2)
    echo "$host $port"
}

# Проверяем все сервисы
for service in "${!server_config[@]}"; do
    config="${server_config[$service]}"
    read -r host port <<< $(parse_server_config "$config")
    
    echo "Проверяем сервис: $service ($host:$port)"
    
    if timeout 3 bash -c "echo >/dev/tcp/$host/$port" 2>/dev/null; then
        echo "✓ $service работает"
    else
        echo "✗ $service недоступен"
    fi
done

Практические кейсы и примеры

Вот несколько реальных сценариев, где массивы в shell-скриптах просто незаменимы:

Массовое обновление пакетов на серверах

#!/bin/bash

declare -a servers=("web1.example.com" "web2.example.com" "db1.example.com")
declare -a packages=("nginx" "mysql-server" "php-fpm")

# Функция для выполнения команды на удалённом сервере
execute_remote() {
    local server=$1
    local command=$2
    ssh -o ConnectTimeout=10 "$server" "$command"
}

# Обновляем пакеты на всех серверах
for server in "${servers[@]}"; do
    echo "Обновляем пакеты на $server..."
    
    # Обновляем список пакетов
    execute_remote "$server" "sudo apt update"
    
    # Устанавливаем/обновляем нужные пакеты
    for package in "${packages[@]}"; do
        echo "  Устанавливаем $package..."
        execute_remote "$server" "sudo apt install -y $package"
    done
    
    echo "✓ Сервер $server обновлён"
done

Анализ логов с группировкой по статусам

#!/bin/bash

declare -A status_counts=()
declare -a log_files=("/var/log/nginx/access.log" "/var/log/apache2/access.log")

# Функция для анализа лог-файла
analyze_log() {
    local log_file=$1
    
    if [ ! -f "$log_file" ]; then
        echo "Файл $log_file не найден"
        return 1
    fi
    
    echo "Анализируем $log_file..."
    
    # Извлекаем HTTP статус коды (для nginx/apache)
    awk '{print $9}' "$log_file" | while read -r status; do
        if [[ "$status" =~ ^[0-9]+$ ]]; then
            status_counts["$status"]=$((${status_counts["$status"]} + 1))
        fi
    done
}

# Анализируем все лог-файлы
for log_file in "${log_files[@]}"; do
    analyze_log "$log_file"
done

# Выводим статистику
echo "Статистика HTTP статусов:"
for status in "${!status_counts[@]}"; do
    echo "  $status: ${status_counts[$status]} запросов"
done

Сравнение подходов: с массивами и без

Критерий Без массивов С массивами
Читаемость кода Низкая (много переменных) Высокая (структурированные данные)
Масштабируемость Плохая (нужно добавлять переменные) Отличная (просто добавить элемент)
Обработка в циклах Сложная (нужны условия) Простая (итерация по массиву)
Управление данными Ручное для каждой переменной Единый подход для всех элементов
Поддержка кода Сложная Простая

Подводные камни и рекомендации

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

  • Пробелы в элементах: Всегда используй кавычки при обращении к массиву: "${array[@]}"
  • Проверка существования: Перед работой с массивом проверяй, что он не пустой
  • Ассоциативные массивы: Доступны только в bash 4.0+, проверяй версию
  • Производительность: Для больших массивов (1000+ элементов) рассмотри альтернативы

Пример безопасной работы с массивами:

#!/bin/bash

# Проверяем версию bash для ассоциативных массивов
if [ "${BASH_VERSION%%.*}" -lt 4 ]; then
    echo "Для ассоциативных массивов нужен bash 4.0+"
    exit 1
fi

declare -a servers=("web 1.example.com" "web 2.example.com")

# Правильный способ итерации (с кавычками)
for server in "${servers[@]}"; do
    echo "Сервер: $server"
done

# Неправильный способ (без кавычек)
# for server in ${servers[@]}; do
#     echo "Сервер: $server"  # Разобьёт "web 1.example.com" на два элемента
# done

# Проверяем, что массив не пустой
if [ ${#servers[@]} -eq 0 ]; then
    echo "Массив серверов пуст"
    exit 1
fi

Интеграция с другими утилитами

Массивы отлично работают в связке с системными утилитами:

Работа с find и массивами

#!/bin/bash

# Находим все .log файлы и помещаем в массив
declare -a log_files=()
while IFS= read -r -d '' file; do
    log_files+=("$file")
done < <(find /var/log -name "*.log" -type f -print0)

echo "Найдено ${#log_files[@]} лог-файлов"

# Обрабатываем каждый файл
for log_file in "${log_files[@]}"; do
    echo "Обрабатываем: $log_file"
    # Здесь твоя логика обработки
done

Интеграция с systemctl

#!/bin/bash

declare -a services=("nginx" "mysql" "redis-server" "php7.4-fpm")
declare -a failed_services=()

# Проверяем статус всех сервисов
for service in "${services[@]}"; do
    if systemctl is-active --quiet "$service"; then
        echo "✓ $service работает"
    else
        echo "✗ $service не запущен"
        failed_services+=("$service")
    fi
done

# Пытаемся запустить упавшие сервисы
if [ ${#failed_services[@]} -gt 0 ]; then
    echo "Пытаемся запустить упавшие сервисы..."
    for service in "${failed_services[@]}"; do
        echo "Запускаем $service..."
        sudo systemctl start "$service"
    done
fi

Автоматизация и новые возможности

Массивы открывают широкие возможности для автоматизации серверных задач:

  • Мониторинг множества сервисов — один скрипт для всех серверов
  • Массовые операции — обновления, бэкапы, конфигурирование
  • Обработка логов — анализ множества файлов одновременно
  • Управление конфигурациями — шаблоны для разных окружений

Для серьёзных проектов рекомендую рассмотреть VPS-сервер с достаточными ресурсами или выделенный сервер для критичных задач.

Альтернативные решения

Если bash-массивы не подходят для твоих задач, рассмотри:

  • Python — для сложной обработки данных
  • jq — для работы с JSON-структурами
  • AWK — для обработки структурированного текста
  • Ansible — для управления конфигурациями множества серверов

Полезные ссылки:

Заключение и рекомендации

Массивы в shell-скриптах — это не просто удобство, это необходимость для любого серьёзного автоматизатора. Они делают код чище, логику понятнее, а сопровождение проще.

Где использовать:

  • Мониторинг множества серверов или сервисов
  • Массовые операции (обновления, бэкапы, развёртывание)
  • Обработка файлов и логов
  • Управление конфигурациями

Когда НЕ использовать:

  • Для простых скриптов с 1-2 значениями
  • Когда нужна сложная обработка данных (лучше Python)
  • В системах с bash версии меньше 4.0 (для ассоциативных массивов)

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


В этой статье собрана информация и материалы из различных интернет-источников. Мы признаем и ценим работу всех оригинальных авторов, издателей и веб-сайтов. Несмотря на то, что были приложены все усилия для надлежащего указания исходного материала, любая непреднамеренная оплошность или упущение не являются нарушением авторских прав. Все упомянутые товарные знаки, логотипы и изображения являются собственностью соответствующих владельцев. Если вы считаете, что какой-либо контент, использованный в этой статье, нарушает ваши авторские права, немедленно свяжитесь с нами для рассмотрения и принятия оперативных мер.

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

Leave a reply

Your email address will not be published. Required fields are marked