- Home »

Массивы в 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 (для ассоциативных массивов)
Начни с простых примеров, постепенно усложняй логику. Массивы — это мощный инструмент, который при правильном использовании может существенно упростить администрирование серверов и автоматизацию рутинных задач.
В этой статье собрана информация и материалы из различных интернет-источников. Мы признаем и ценим работу всех оригинальных авторов, издателей и веб-сайтов. Несмотря на то, что были приложены все усилия для надлежащего указания исходного материала, любая непреднамеренная оплошность или упущение не являются нарушением авторских прав. Все упомянутые товарные знаки, логотипы и изображения являются собственностью соответствующих владельцев. Если вы считаете, что какой-либо контент, использованный в этой статье, нарушает ваши авторские права, немедленно свяжитесь с нами для рассмотрения и принятия оперативных мер.
Данная статья предназначена исключительно для ознакомительных и образовательных целей и не ущемляет права правообладателей. Если какой-либо материал, защищенный авторским правом, был использован без должного упоминания или с нарушением законов об авторском праве, это непреднамеренно, и мы исправим это незамедлительно после уведомления. Обратите внимание, что переиздание, распространение или воспроизведение части или всего содержимого в любой форме запрещено без письменного разрешения автора и владельца веб-сайта. Для получения разрешений или дополнительных запросов, пожалуйста, свяжитесь с нами.