- Home »

Выполнение команд в shell-скриптах — запуск внешних программ
Каждый админ рано или поздно сталкивается с необходимостью запускать внешние программы из shell-скриптов. Это основа автоматизации — от простых утилит до сложных систем деплоя. Понимание того, как правильно запускать и контролировать внешние процессы, поможет вам создавать надёжные скрипты для обслуживания серверов и автоматизации рутинных задач.
Основы запуска внешних программ в shell
В shell-скриптах запуск внешних программ — это основной способ выполнения большинства задач. Когда вы пишете команду в скрипте, shell создаёт дочерний процесс, выполняет программу и возвращает её статус завершения.
#!/bin/bash
# Простой запуск команды
ls -la
# Запуск с сохранением вывода
output=$(ls -la)
echo "Результат: $output"
# Проверка успешности выполнения
if ls /nonexistent 2>/dev/null; then
echo "Каталог существует"
else
echo "Каталог не найден"
fi
Ключевые механизмы работы:
- Прямой запуск — команда выполняется синхронно, скрипт ждёт завершения
- Захват вывода — через подстановку команд $(command) или обратные кавычки
- Перенаправление потоков — stdout, stderr, stdin
- Код возврата — переменная $? содержит статус последней команды
Способы запуска команд
Существует несколько методов выполнения внешних программ, каждый имеет свои особенности:
#!/bin/bash
# Метод 1: Прямой запуск
date
# Метод 2: Подстановка команд (современный синтаксис)
current_date=$(date)
echo "Текущая дата: $current_date"
# Метод 3: Обратные кавычки (устаревший, но всё ещё работает)
old_style=`date`
# Метод 4: Фоновый запуск
sleep 10 &
background_pid=$!
echo "Запущен процесс с PID: $background_pid"
# Метод 5: Через eval (осторожно с безопасностью!)
cmd="ls -la"
eval $cmd
Метод | Синтаксис | Плюсы | Минусы |
---|---|---|---|
Прямой запуск | command | Простота, читаемость | Нельзя захватить вывод |
$(command) | var=$(command) | Вложенность, современный синтаксис | Теряется код возврата |
Обратные кавычки | var=`command` | Совместимость со старыми системами | Устаревший, плохая вложенность |
Фоновый запуск | command & | Параллельное выполнение | Сложность контроля |
Управление выводом и ошибками
Правильная обработка stdout и stderr — критически важна для надёжных скриптов:
#!/bin/bash
# Перенаправление stdout
ls > output.txt
# Перенаправление stderr
ls /nonexistent 2> error.txt
# Объединение stdout и stderr
ls / /nonexistent > all_output.txt 2>&1
# Подавление вывода
ls /nonexistent 2>/dev/null
# Сохранение и stdout, и stderr отдельно
{
output=$(ls /home 2>&1)
exit_code=$?
} 2>/dev/null
# Функция для логирования
log_command() {
local cmd="$1"
echo "[$(date)] Выполняется: $cmd" >> /var/log/script.log
eval "$cmd" >> /var/log/script.log 2>&1
local status=$?
echo "[$(date)] Завершено со статусом: $status" >> /var/log/script.log
return $status
}
# Использование
log_command "systemctl status nginx"
Проверка успешности выполнения
Контроль за выполнением команд — основа надёжности скриптов:
#!/bin/bash
# Включение строгого режима
set -euo pipefail # завершать при ошибке, неопределённых переменных, ошибках в pipeline
# Простая проверка
if ping -c 1 google.com >/dev/null 2>&1; then
echo "Интернет доступен"
else
echo "Нет соединения с интернетом"
exit 1
fi
# Проверка с детальным анализом
check_service() {
local service=$1
if systemctl is-active --quiet "$service"; then
echo "✓ $service запущен"
return 0
else
echo "✗ $service не запущен"
return 1
fi
}
# Функция с retry-логикой
retry_command() {
local cmd="$1"
local max_attempts=3
local delay=2
for ((i=1; i<=max_attempts; i++)); do
echo "Попытка $i из $max_attempts"
if eval "$cmd"; then
echo "Команда выполнена успешно"
return 0
else
echo "Ошибка выполнения команды"
if [[ $i -lt $max_attempts ]]; then
echo "Ожидание $delay секунд..."
sleep $delay
fi
fi
done
echo "Команда не выполнена после $max_attempts попыток"
return 1
}
# Использование
retry_command "wget -O /tmp/test.html https://example.com"
Работа с параллельными процессами
Для высокопроизводительных скриптов важно уметь запускать команды параллельно:
#!/bin/bash
# Простой параллельный запуск
backup_database() {
echo "Бэкап базы данных..."
sleep 5
echo "Бэкап БД завершён"
}
backup_files() {
echo "Бэкап файлов..."
sleep 3
echo "Бэкап файлов завершён"
}
# Запуск в фоне
backup_database &
db_pid=$!
backup_files &
files_pid=$!
# Ожидание завершения всех процессов
wait $db_pid
wait $files_pid
echo "Все бэкапы завершены"
# Использование GNU parallel (если установлен)
check_servers() {
local servers=("web1.example.com" "web2.example.com" "web3.example.com")
# Последовательно
for server in "${servers[@]}"; do
ping -c 1 "$server" >/dev/null && echo "$server: OK" || echo "$server: FAIL"
done
# Параллельно с parallel
printf '%s\n' "${servers[@]}" | parallel -j 10 'ping -c 1 {} >/dev/null && echo "{}: OK" || echo "{}: FAIL"'
}
# Пул воркеров
worker_pool() {
local max_jobs=5
local job_count=0
for task in task1 task2 task3 task4 task5 task6 task7 task8; do
while [[ $job_count -ge $max_jobs ]]; do
wait -n # ждём завершения любого фонового процесса
((job_count--))
done
(
echo "Обработка $task"
sleep $((RANDOM % 5 + 1))
echo "$task завершён"
) &
((job_count++))
done
wait # ждём завершения всех задач
}
Полезные утилиты и команды
Набор инструментов, которые часто используются в администрировании:
#!/bin/bash
# Мониторинг системы
system_check() {
echo "=== Системная информация ==="
uptime
df -h
free -h
echo -e "\n=== Топ процессов ==="
ps aux --sort=-%cpu | head -10
echo -e "\n=== Сетевые соединения ==="
ss -tuln
echo -e "\n=== Дисковые операции ==="
iostat -x 1 1
}
# Управление сервисами
service_manager() {
local services=("nginx" "mysql" "redis" "php7.4-fpm")
for service in "${services[@]}"; do
if systemctl is-enabled --quiet "$service"; then
status=$(systemctl is-active "$service")
echo "$service: $status"
if [[ $status != "active" ]]; then
echo "Перезапуск $service..."
systemctl restart "$service"
fi
fi
done
}
# Очистка логов
cleanup_logs() {
local log_dirs=("/var/log/nginx" "/var/log/mysql" "/var/log/php")
for log_dir in "${log_dirs[@]}"; do
if [[ -d $log_dir ]]; then
echo "Очистка логов в $log_dir"
find "$log_dir" -name "*.log" -type f -mtime +30 -delete
find "$log_dir" -name "*.gz" -type f -mtime +90 -delete
fi
done
}
# Проверка безопасности
security_check() {
echo "=== Проверка безопасности ==="
# Проверка неудачных попыток входа
echo "Последние неудачные попытки входа:"
grep "Failed password" /var/log/auth.log | tail -5
# Проверка открытых портов
echo -e "\nОткрытые порты:"
nmap -sT -O localhost
# Проверка обновлений
echo -e "\nДоступные обновления:"
apt list --upgradable 2>/dev/null || yum check-update
}
Интеграция с внешними API
Современные скрипты часто взаимодействуют с веб-сервисами:
#!/bin/bash
# Функция для HTTP-запросов
http_request() {
local method=$1
local url=$2
local data=$3
local headers=$4
curl -s -X "$method" \
-H "Content-Type: application/json" \
-H "$headers" \
-d "$data" \
"$url"
}
# Отправка уведомлений в Slack
notify_slack() {
local message=$1
local webhook_url="https://hooks.slack.com/services/YOUR/WEBHOOK/URL"
local payload=$(cat </dev/null | \
openssl x509 -noout -enddate | cut -d= -f2)
expiry_timestamp=$(date -d "$expiry" +%s)
current_timestamp=$(date +%s)
days_left=$(( (expiry_timestamp - current_timestamp) / 86400 ))
if [[ $days_left -lt 30 ]]; then
notify_slack "⚠️ SSL-сертификат для $domain истекает через $days_left дней!"
fi
done
}
# Мониторинг веб-сайтов
monitor_websites() {
local sites=("https://example.com" "https://api.example.com/health")
for site in "${sites[@]}"; do
response_time=$(curl -o /dev/null -s -w "%{time_total}" "$site")
http_code=$(curl -o /dev/null -s -w "%{http_code}" "$site")
if [[ $http_code -ne 200 ]]; then
notify_slack "🔴 Сайт $site недоступен (HTTP $http_code)"
elif (( $(echo "$response_time > 5.0" | bc -l) )); then
notify_slack "🟡 Сайт $site медленно отвечает (${response_time}s)"
fi
done
}
Обработка ошибок и отладка
Профессиональные скрипты должны корректно обрабатывать ошибки:
#!/bin/bash
# Настройка отладки
set -euo pipefail
# set -x # раскомментировать для отладки
# Глобальные переменные
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
LOG_FILE="/var/log/$(basename "$0" .sh).log"
PID_FILE="/var/run/$(basename "$0" .sh).pid"
# Функция логирования
log() {
local level=$1
shift
local message="$*"
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
echo "[$timestamp] [$level] $message" | tee -a "$LOG_FILE"
}
# Функция очистки при завершении
cleanup() {
log "INFO" "Завершение скрипта"
[[ -f $PID_FILE ]] && rm -f "$PID_FILE"
# Завершение фоновых процессов
jobs -p | xargs -r kill 2>/dev/null || true
}
# Обработчик сигналов
trap cleanup EXIT INT TERM
# Проверка блокировки
check_lock() {
if [[ -f $PID_FILE ]]; then
local old_pid=$(cat "$PID_FILE")
if kill -0 "$old_pid" 2>/dev/null; then
log "ERROR" "Скрипт уже запущен с PID $old_pid"
exit 1
else
log "WARN" "Найден устаревший PID-файл, удаляю"
rm -f "$PID_FILE"
fi
fi
echo $$ > "$PID_FILE"
}
# Безопасное выполнение команд
safe_exec() {
local cmd="$1"
local description="$2"
log "INFO" "Выполняется: $description"
log "DEBUG" "Команда: $cmd"
if eval "$cmd"; then
log "INFO" "Успешно: $description"
return 0
else
local exit_code=$?
log "ERROR" "Ошибка при выполнении: $description (код: $exit_code)"
return $exit_code
fi
}
# Проверка зависимостей
check_dependencies() {
local deps=("curl" "jq" "systemctl" "nginx")
local missing=()
for dep in "${deps[@]}"; do
if ! command -v "$dep" >/dev/null 2>&1; then
missing+=("$dep")
fi
done
if [[ ${#missing[@]} -gt 0 ]]; then
log "ERROR" "Не найдены зависимости: ${missing[*]}"
exit 1
fi
}
# Основная функция
main() {
log "INFO" "Запуск скрипта"
check_lock
check_dependencies
# Ваш код здесь
safe_exec "nginx -t" "Проверка конфигурации nginx"
safe_exec "systemctl reload nginx" "Перезагрузка nginx"
log "INFO" "Скрипт завершён успешно"
}
# Запуск только если скрипт выполняется напрямую
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
main "$@"
fi
Практические примеры для серверного администрирования
Вот несколько готовых скриптов для повседневных задач:
#!/bin/bash
# Автоматическое резервное копирование
backup_script() {
local backup_dir="/backup/$(date +%Y%m%d)"
local mysql_user="backup_user"
local mysql_pass="backup_password"
mkdir -p "$backup_dir"
# Бэкап базы данных
mysqldump -u "$mysql_user" -p"$mysql_pass" --all-databases \
| gzip > "$backup_dir/mysql_backup.sql.gz"
# Бэкап файлов сайта
tar -czf "$backup_dir/website_backup.tar.gz" -C /var/www/html .
# Очистка старых бэкапов (старше 7 дней)
find /backup -type d -mtime +7 -exec rm -rf {} \; 2>/dev/null || true
# Отправка на удалённый сервер
rsync -av "$backup_dir/" backup@remote.server.com:/backups/
}
# Мониторинг дискового пространства
disk_monitor() {
local threshold=80
df -h | awk 'NR>1 {print $5 " " $1 " " $6}' | while read line; do
usage=$(echo "$line" | awk '{print $1}' | sed 's/%//')
partition=$(echo "$line" | awk '{print $2}')
mountpoint=$(echo "$line" | awk '{print $3}')
if [[ $usage -gt $threshold ]]; then
message="⚠️ Диск заполнен на ${usage}%: $partition ($mountpoint)"
echo "$message"
# Отправка уведомления
notify_slack "$message"
fi
done
}
# Обновление системы
system_update() {
log "INFO" "Начинаю обновление системы"
# Проверка доступности репозиториев
if ! apt-get update; then
log "ERROR" "Не удалось обновить список пакетов"
return 1
fi
# Получение списка обновлений
local updates=$(apt list --upgradable 2>/dev/null | wc -l)
if [[ $updates -gt 1 ]]; then
log "INFO" "Доступно обновлений: $((updates-1))"
# Обновление пакетов
DEBIAN_FRONTEND=noninteractive apt-get -y upgrade
# Проверка необходимости перезагрузки
if [[ -f /var/run/reboot-required ]]; then
log "WARN" "Требуется перезагрузка системы"
notify_slack "🔄 Сервер требует перезагрузки после обновления"
fi
else
log "INFO" "Система актуальна"
fi
}
# Очистка временных файлов
cleanup_temp() {
local temp_dirs=("/tmp" "/var/tmp" "/var/cache/apt/archives")
for dir in "${temp_dirs[@]}"; do
if [[ -d $dir ]]; then
log "INFO" "Очистка $dir"
# Удаление файлов старше 3 дней
find "$dir" -type f -mtime +3 -delete 2>/dev/null || true
# Удаление пустых директорий
find "$dir" -type d -empty -delete 2>/dev/null || true
fi
done
# Очистка логов systemd
journalctl --vacuum-time=30d
# Очистка кэша пакетов
apt-get autoclean
apt-get autoremove -y
}
Интеграция с системами мониторинга
Современные инструменты мониторинга требуют интеграции на уровне скриптов:
#!/bin/bash
# Отправка метрик в Prometheus
send_metrics() {
local metric_name=$1
local metric_value=$2
local labels=$3
# Используем node_exporter textfile collector
local metrics_file="/var/lib/node_exporter/textfile_collector/custom_metrics.prom"
echo "# HELP $metric_name Custom metric from script" > "$metrics_file"
echo "# TYPE $metric_name gauge" >> "$metrics_file"
echo "${metric_name}{${labels}} $metric_value" >> "$metrics_file"
}
# Интеграция с Grafana через API
grafana_annotation() {
local title=$1
local text=$2
local grafana_url="http://grafana.example.com:3000"
local api_key="your_api_key_here"
local payload=$(cat < 80" | bc -l) )); then
grafana_annotation "High CPU Usage" "CPU usage is ${cpu_usage}%"
fi
if (( $(echo "$memory_usage > 85" | bc -l) )); then
grafana_annotation "High Memory Usage" "Memory usage is ${memory_usage}%"
fi
}
Безопасность и лучшие практики
При работе с внешними командами критически важно соблюдать принципы безопасности:
- Валидация входных данных — всегда проверяйте пользовательский ввод
- Избегайте eval — используйте только для доверенных данных
- Используйте полные пути — избегайте PATH injection
- Установка строгих прав — скрипты должны иметь минимальные права
- Логирование действий — все операции должны быть задокументированы
#!/bin/bash
# Безопасная обработка пользовательского ввода
validate_input() {
local input=$1
local pattern=$2
if [[ ! $input =~ $pattern ]]; then
log "ERROR" "Недопустимый ввод: $input"
return 1
fi
}
# Безопасный запуск команд с пользовательскими данными
safe_user_command() {
local user_input=$1
# Валидация — только буквы, цифры, точки и дефисы
if ! validate_input "$user_input" '^[a-zA-Z0-9.-]+$'; then
return 1
fi
# Использование -- для разделения опций и аргументов
/usr/bin/systemctl status -- "$user_input"
}
# Установка безопасных переменных окружения
secure_environment() {
# Очистка PATH
export PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
# Установка безопасного umask
umask 077
# Очистка опасных переменных
unset IFS
export LC_ALL=C
}
Заключение
Умение правильно запускать внешние программы в shell-скриптах — это основа эффективного администрирования серверов. Освоив эти техники, вы сможете создавать надёжные системы автоматизации, которые будут работать стабильно и безопасно.
Основные принципы, которые стоит помнить:
- Всегда проверяйте коды возврата команд
- Используйте строгий режим (set -euo pipefail) для отлова ошибок
- Логируйте все важные операции
- Валидируйте входные данные
- Предусматривайте обработку исключительных ситуаций
Для серьёзных проектов рекомендую арендовать VPS или выделенный сервер для тестирования и развёртывания ваших скриптов. Это позволит вам экспериментировать с различными техниками в безопасной среде.
Дополнительные ресурсы для изучения:
- Официальная документация Bash
- ShellCheck — инструмент для анализа shell-скриптов
- Pure Bash Bible — коллекция bash-решений
В этой статье собрана информация и материалы из различных интернет-источников. Мы признаем и ценим работу всех оригинальных авторов, издателей и веб-сайтов. Несмотря на то, что были приложены все усилия для надлежащего указания исходного материала, любая непреднамеренная оплошность или упущение не являются нарушением авторских прав. Все упомянутые товарные знаки, логотипы и изображения являются собственностью соответствующих владельцев. Если вы считаете, что какой-либо контент, использованный в этой статье, нарушает ваши авторские права, немедленно свяжитесь с нами для рассмотрения и принятия оперативных мер.
Данная статья предназначена исключительно для ознакомительных и образовательных целей и не ущемляет права правообладателей. Если какой-либо материал, защищенный авторским правом, был использован без должного упоминания или с нарушением законов об авторском праве, это непреднамеренно, и мы исправим это незамедлительно после уведомления. Обратите внимание, что переиздание, распространение или воспроизведение части или всего содержимого в любой форме запрещено без письменного разрешения автора и владельца веб-сайта. Для получения разрешений или дополнительных запросов, пожалуйста, свяжитесь с нами.