Использование команды `sleep` в Bash-скриптах на Linux
Команда `sleep` в Linux приостанавливает выполнение скрипта на точно заданный промежуток времени — указанный в секундах, минутах, часах или днях — с использованием синтаксиса `sleep [NUMBER][SUFFIX]`. Это один из наиболее критически важных примитивов в Bash-скриптинге, обеспечивающий ограничение частоты запросов, логику повторных попыток, синхронизацию процессов и автоматизацию по расписанию без использования внешних планировщиков.
В отличие от cron или `at`, `sleep` работает полностью в контексте собственного процесса скрипта, что делает его правильным инструментом, когда задержка должна быть относительной к завершению предыдущей команды, а не привязанной к абсолютному времени.
Синтаксис и справочник единиц времени
“`bash
sleep NUMBER[SUFFIX]
“`
| Суффикс | Единица | Пример | Эквивалент в секундах |
|---|
| ——– | ——— | —————- | ———————– |
|---|
| `s` | Секунды | `sleep 30s` | 30 |
|---|
| `m` | Минуты | `sleep 5m` | 300 |
|---|
| `h` | Часы | `sleep 2h` | 7200 |
|---|
| `d` | Дни | `sleep 1d` | 86400 |
|---|
| (нет) | Секунды | `sleep 10` | 10 |
|---|
Суффикс является необязательным. Если он опущен, единицей по умолчанию являются секунды. В системах GNU/Linux (GNU coreutils) `sleep` также принимает значения с плавающей точкой и несколько аргументов — возможность, отсутствующая в реализациях BSD и macOS, если только GNU coreutils не установлен через Homebrew.
“`bash
GNU coreutils: both of these are valid
sleep 1.5
sleep 1m 30s # Equivalent to 90 seconds
“`
Важное замечание о переносимости: POSIX предписывает только целые секунды без суффикса. Если ваш скрипт должен работать на Alpine Linux (BusyBox), macOS или AIX, ограничьтесь `sleep INTEGER` и избегайте объединения нескольких аргументов.
Основные варианты использования в Bash-скриптах
1. Последовательная задержка между командами
Наиболее простое применение — вставка паузы между двумя операциями, когда вторая команда не должна начинаться до тех пор, пока реальное условие не успеет установиться:
“`bash
#!/bin/bash
echo "Restarting nginx…"
systemctl restart nginx
sleep 3
systemctl status nginx
“`
Пауза в 3 секунды здесь учитывает асинхронную последовательность запуска менеджера служб. Без неё `status` может сообщить устаревшее состояние, зафиксированное до полной инициализации процесса.
2. Цикл опроса с экспоненциальной выдержкой
Наивный цикл повторных попыток с фиксированным интервалом расходует ресурсы и может увеличить нагрузку на испытывающий трудности нижестоящий сервис. Правильным шаблоном является экспоненциальная выдержка с джиттером:
“`bash
#!/bin/bash
MAX_RETRIES=6
DELAY=2
for (( attempt=1; attempt<=MAX_RETRIES; attempt++ )); do
if curl -sf https://api.example.com/health > /dev/null; then
echo "Service healthy on attempt $attempt."
exit 0
fi
echo "Attempt $attempt failed. Retrying in ${DELAY}s…"
sleep "$DELAY"
DELAY=$(( DELAY * 2 ))
done
echo "Service unreachable after $MAX_RETRIES attempts." >&2
exit 1
“`
При каждом сбое время ожидания удваивается: 2с, 4с, 8с, 16с, 32с, 64с. Максимальное суммарное время ожидания перед отказом составляет 126 секунд. Этот шаблон является стандартным в скриптах производственного развёртывания, проверках работоспособности и CI/CD-конвейерах.
3. API-вызовы с ограничением частоты запросов
При взаимодействии с API, применяющими квоты запросов, `sleep` обеспечивает соблюдение необходимого интервала между запросами:
“`bash
#!/bin/bash
API_KEY="your_key_here"
ENDPOINTS=("users" "orders" "products" "inventory")
for endpoint in "${ENDPOINTS[@]}"; do
curl -s -H "Authorization: Bearer $API_KEY"
"https://api.example.com/v1/${endpoint}"
-o "${endpoint}.json"
echo "Fetched: $endpoint"
sleep 1 # Respect 1 req/sec rate limit
done
“`
4. Выполнение фоновой задачи по таймеру
Запуск отложенной команды без блокировки текущего сеанса оболочки требует сочетания `sleep` с фоновым выполнением в подоболочке:
“`bash
Trigger a cache flush 60 seconds after deployment completes
( sleep 60 && redis-cli FLUSHDB ) &
echo "Cache flush scheduled. PID: $!"
“`
Переменная `$!` захватывает PID фоновой подоболочки, который впоследствии можно использовать с `wait` или `kill`, если задачу необходимо отменить.
5. Цикл сторожевого таймера и мониторинга процессов
“`bash
#!/bin/bash
SERVICE="mysqld"
CHECK_INTERVAL=30
while true; do
if ! pgrep -x "$SERVICE" > /dev/null; then
echo "$(date '+%Y-%m-%d %H:%M:%S') $SERVICE not running. Restarting…"
>> /var/log/watchdog.log
systemctl start "$SERVICE"
fi
sleep "$CHECK_INTERVAL"
done
“`
Этот шаблон используется в облегчённом надзоре за процессами, когда полноценный демон-супервизор (systemd, supervisord, s6) недоступен или неуместен — что характерно для контейнеризированных сред или минимальных экземпляров VPS Хостинга.
6. Таймер обратного отсчёта с обратной связью для пользователя
Для интерактивных скриптов, в которых оператору необходима информация об оставшемся времени ожидания:
“`bash
#!/bin/bash
COUNTDOWN=10
echo "Starting in:"
for (( i=COUNTDOWN; i>=1; i– )); do
printf "r%2d seconds remaining…" "$i"
sleep 1
done
printf "rGo! n"
“`
`printf "r"` перезаписывает текущую строку вместо добавления новых строк, обеспечивая чистый терминальный обратный отсчёт.
`sleep` в сравнении с альтернативными механизмами синхронизации
| Механизм | Гранулярность | Блокирует оболочку | Абсолютное время | Вариант использования |
|---|
| —————– | —————– | ————– | ————— | ———————————————– |
|---|
| `sleep` | Субсекундная (GNU) | Да (если не `&`) | Нет | Относительные задержки внутри скриптов |
|---|
| `cron` | 1 минута | Нет | Да | Повторяющиеся запланированные задания |
|---|
| `at` | 1 минута | Нет | Да | Однократное выполнение в будущем |
|---|
| `systemd timer` | 1 секунда | Нет | Да | Постоянные, журналируемые задания с учётом зависимостей |
|---|
| `usleep` (C) | Микросекунда | Да | Нет | Точность на уровне ядра/C (не является нативным для Bash) |
|---|
| `read -t` | Субсекундная | Да | Нет | Тайм-аут с возможным вводом от пользователя |
|---|
Когда использовать `read -t` вместо `sleep`: Если скрипту необходимо приостановиться, но при этом позволить пользователю прервать выполнение или ответить во время ожидания, `read -t SECONDS` является правильным примитивом. Он возвращает код выхода 1 при тайм-ауте и 0, если пользователь нажимает Enter, предоставляя условную логику без отдельного процесса.
“`bash
echo "Press Enter to skip the 10-second wait, or wait for automatic continuation."
read -t 10 -r || true
echo "Continuing…"
“`
Точность, числа с плавающей точкой и поведение на разных платформах
GNU `sleep` принимает десятичные дроби, что важно в скриптах, управляющих анимацией, ограничивающих частоту просмотра журналов или имитирующих потоки данных в реальном времени:
“`bash
Tail a log file and print one line per 0.2 seconds (5 lines/sec)
while IFS= read -r line; do
echo "$line"
sleep 0.2
done < /var/log/app.log
“`
Фактическая продолжительность сна является минимальной, а не гарантированной. Планировщик ядра может разбудить процесс немного позже в зависимости от нагрузки на систему и разрешения таймера (`CONFIG_HZ`). На сильно загруженном Выделенном сервере, выполняющем десятки параллельных процессов, `sleep 0.1` может фактически приостановиться на 0,11–0,15 секунды. Для скриптов, в которых такое смещение недопустимо, используйте ссылку на монотонные часы:
“`bash
#!/bin/bash
INTERVAL=5
NEXT=$(date +%s%N) # Current time in nanoseconds
while true; do
NEXT=$(( NEXT + INTERVAL * 1000000000 ))
do_work
NOW=$(date +%s%N)
REMAINING=$(( (NEXT – NOW) / 1000000 )) # Convert to milliseconds
[ "$REMAINING" -gt 0 ] && sleep "$(echo "scale=3; $REMAINING/1000" | bc)"
done
“`
Этот цикл с компенсацией смещения поддерживает постоянный интервал независимо от того, сколько времени занимает `do_work`.
Обработка сигналов и прерывание `sleep`
Выполняющийся процесс `sleep` реагирует на сигналы. Отправка `SIGALRM` процессу sleep немедленно его пробуждает. На практике нажатие `Ctrl+C` отправляет `SIGINT` всей группе процессов, завершая как скрипт, так и любой активный `sleep`.
Чтобы скрипт корректно обрабатывал прерывание во время сна:
“`bash
#!/bin/bash
cleanup() {
echo "Interrupted. Cleaning up…"
exit 1
}
trap cleanup SIGINT SIGTERM
echo "Waiting 60 seconds…"
sleep 60 &
SLEEP_PID=$!
wait "$SLEEP_PID"
echo "Wait complete."
“`
Переводя `sleep` в фоновый режим и используя `wait`, `trap` срабатывает немедленно при получении `SIGINT`, а не откладывается до завершения сна. Это правильный шаблон для долго выполняющихся скриптов автоматизации на производственных серверах.
Практические подводные камни и граничные случаи
Подводный камень 1: Использование `sleep` в плотных циклах без условия завершения. Цикл `while true; do sleep 1; done` без пути выхода будет выполняться бесконечно, занимая слот процесса и накапливаясь в выводе `ps`. Всегда определяйте максимальное количество итераций или сторожевое условие.
Подводный камень 2: Предположение о синхронности `sleep` с подоболочками. При разветвлении подоболочки с помощью `&` родительский скрипт не ожидает завершения `sleep` подоболочки, если явно не вызван `wait`. Это приводит к состояниям гонки в скриптах параллельного развёртывания.
Подводный камень 3: Жёсткое кодирование задержек для готовности службы. Использование `sleep 5` после запуска службы ненадёжно. Служба может быть готова через 1 секунду или может занять 30 секунд под нагрузкой. Надёжной альтернативой является опрос готовности:
“`bash
#!/bin/bash
wait_for_port() {
local host="$1" port="$2" timeout="${3:-30}"
local elapsed=0
until nc -z "$host" "$port" 2>/dev/null; do
[ "$elapsed" -ge "$timeout" ] && return 1
sleep 1
(( elapsed++ ))
done
}
systemctl start postgresql
wait_for_port localhost 5432 30 && echo "PostgreSQL ready."
“`
Подводный камень 4: Сон с плавающей точкой в системах BusyBox. Контейнеры Alpine Linux используют `sleep` из BusyBox, который не поддерживает десятичные значения. Попытка выполнить `sleep 0.5` вызовет ошибку. Проверяйте своё окружение перед развёртыванием скриптов, использующих субсекундную точность.
Интеграция `sleep` в автоматизированные серверные рабочие процессы
На управляемом VPS с cPanel скрипты автоматизированного обслуживания часто сочетают `sleep` с cron для реализации субминутного планирования. Поскольку минимальное разрешение cron составляет одну минуту, можно добиться 15-секундных интервалов следующим образом:
“`bash
crontab entry — runs the script 4 times per minute
- * * * * /usr/local/bin/check_queue.sh
- * * * * sleep 15 && /usr/local/bin/check_queue.sh
- * * * * sleep 30 && /usr/local/bin/check_queue.sh
- * * * * sleep 45 && /usr/local/bin/check_queue.sh
“`
Этот метод широко используется для обработчиков очередей, проверок работоспособности и сборщиков метрик на общей инфраструктуре, где установка выделенного планировщика заданий не разрешена.
В скриптах обновления SSL-сертификатов `sleep` обеспечивает задержку между попытками, когда для распространения ACME-запроса требуется истечение DNS TTL до того, как CA сможет подтвердить право собственности. Если вы управляете сертификатами на собственной инфраструктуре, SSL-сертификаты с автоматизированными конвейерами обновления выигрывают от точно настроенных интервалов повторных попыток.
Аналогично, скрипты проверки распространения домена — полезные после обновления записей через Регистрацию доменов — используют циклы `sleep` для опроса DNS-резолверов с интервалами, соответствующими ожидаемым значениям TTL.
Матрица решений: выбор правильной стратегии задержки
| Сценарий | Рекомендуемый подход |
|---|
| ———————————————– | ————————————————— |
|---|
| Фиксированная пауза между двумя последовательными командами | `sleep N` |
|---|
| Повторные попытки до успеха, предотвращение эффекта «стада» | Экспоненциальная выдержка с `sleep` |
|---|
| Повторяющаяся задача каждые N минут | `cron` (не `sleep`) |
|---|
| Субминутная повторяющаяся задача | `cron` + трюк со смещением `sleep` |
|---|
| Задержка без блокировки терминала | `( sleep N && command ) &` |
|---|
| Пауза с возможностью прерывания пользователем | `sleep N &` + `wait $!` + `trap` |
|---|
| Проверка готовности службы | Цикл опроса порта/работоспособности с `sleep 1` на попытку |
|---|
| Высокоточный интервал (с компенсацией смещения) | Ссылка на монотонные часы с вычисленным `sleep` |
|---|
| Субсекундная задержка на Alpine/BusyBox | Избегайте; используйте целые секунды или смените базовый образ |
|---|
Ключевые технические выводы
- Всегда используйте `sleep "$VARIABLE"` с двойными кавычками, чтобы предотвратить ошибки разбиения слов, когда переменная содержит десятичное значение.
- Предпочитайте `sleep 1m 30s` вместо `sleep 90` для удобочитаемости в долго выполняющихся скриптах обслуживания.
- Переводите `sleep` в фоновый режим с помощью `wait` и `trap` всякий раз, когда скрипт должен реагировать на сигналы во время паузы.
- Никогда не используйте жёстко закодированный `sleep` как замену надлежащей проверки готовности — используйте цикл опроса с тайм-аутом.
- Проверяйте поведение `sleep` на целевой ОС перед развёртыванием скриптов, использующих числа с плавающей точкой или синтаксис с несколькими аргументами.
- На производственных серверах записывайте временну́ю метку до и после длительных вызовов `sleep` для обнаружения смещения планировщика при анализе инцидентов.
- При создании автоматизации на Панелях управления VPS убедитесь, что планировщик задач панели уже обеспечивает управление интервалами, прежде чем добавлять логику `sleep` вручную.
Часто задаваемые вопросы
Потребляет ли `sleep` CPU во время ожидания?
Нет. `sleep` вызывает `nanosleep()` (или эквивалент) на уровне ядра, переводя процесс в состояние прерываемого сна (`S` в выводе `ps`). Во время ожидания он не потребляет циклов CPU — только небольшой объём памяти для записи процесса в таблице процессов.
Каково максимальное значение, принимаемое `sleep`?
В GNU/Linux `sleep` принимает значения до пределов числа с плавающей точкой `double`, что практически не ограничено для реальных задач. `sleep 1d` (86400 секунд) является распространённым значением; `sleep 365d` допустимо. Практическим ограничением является время работы системы.
Почему `sleep 0.5` не работает в моём Docker-контейнере?
Alpine Linux использует BusyBox, реализация `sleep` которого принимает только целые секунды. Перейдите на базовый образ Debian или Ubuntu либо установите GNU coreutils (`apk add coreutils`), чтобы включить поддержку десятичных значений.
Можно ли отменить фоновый процесс `sleep`?
Да. Захватите его PID с помощью `SLEEP_PID=$!` сразу после перевода в фоновый режим, затем используйте `kill "$SLEEP_PID"` для его завершения. Если вы использовали `( sleep N && command ) &`, завершение PID подоболочки также предотвратит выполнение последующей команды.
Безопасно ли использовать `sleep` внутри скрипта `ExecStart` юнита `systemd`?
Да, но с оговорками. Если юнит службы имеет установленный `TimeoutStartSec`, длительный `sleep` во время запуска приведёт к тому, что systemd завершит службу как неудачный запуск. Для задержек после запуска используйте `ExecStartPost` с опросом готовности или настройте `Type=forking` с надлежащим управлением PID-файлом, а не полагайтесь на `sleep` для отсрочки инициализации.
