Управление выводом
В консоли все выглядит одинаково. Но для системы это два разных потока:
stdout - нормальный вывод (FD 1)
stderr - ошибки (FD 2)
И если их не различать - автоматизация начинает ломаться.
▪️ Что такое stdout и stderr. Пример:
Первое уйдет в stdout, второе в stderr.
Проверим:
Теперь:
out.log - нормальный результат
err.log - ошибки
⚠️ Почему это важно
1️⃣ CI/CD пайплайны. В системах вроде GitHub Actions или GitLab CI:
stdout - это лог выполнения
stderr - это ошибка шага
Если приложение пишет все в stdout, то и пайплайн может считать шаг успешным.
2️⃣ Парсинг вывода в скриптах. Типичная ошибка:
Если some_command пишет ошибку в stdout - переменная будет содержать текст ошибки.
Автоматизация съест ее как валидные данные.
3️⃣ Логирование в systemd. В systemd:
stdout - это StandardOutput
stderr - это StandardError
Если сервис пишет ошибки в stdout, мониторинг может их не выделить.
🤩 Типичный анти-паттерн
Если curl вернет HTTP 500, сообщение об ошибке может уйти в stdout.
🤩 Правильно:
▪️ Управление потоками.
Раздельная запись
Объединить
Игнорировать ошибки
Поменять местами
BashTex📱 #bash #utils
В консоли все выглядит одинаково. Но для системы это два разных потока:
stdout - нормальный вывод (FD 1)
stderr - ошибки (FD 2)
И если их не различать - автоматизация начинает ломаться.
echo "OK"
echo "ERROR" >&2
Первое уйдет в stdout, второе в stderr.
Проверим:
./script.sh >out.log 2>err.log
Теперь:
out.log - нормальный результат
err.log - ошибки
stdout - это лог выполнения
stderr - это ошибка шага
Если приложение пишет все в stdout, то и пайплайн может считать шаг успешным.
result=$(some_command)
Если some_command пишет ошибку в stdout - переменная будет содержать текст ошибки.
Автоматизация съест ее как валидные данные.
stdout - это StandardOutput
stderr - это StandardError
Если сервис пишет ошибки в stdout, мониторинг может их не выделить.
if curl bashtex.com; then
echo "OK"
fi
Если curl вернет HTTP 500, сообщение об ошибке может уйти в stdout.
if curl -fsS bashtex.com; then
echo "OK"
else
echo "FAIL" >&2
fi
Раздельная запись
command >out.log 2>err.log
Объединить
command >all.log 2>&1
Игнорировать ошибки
command 2>/dev/null
Поменять местами
command 3>&1 1>&2 2>&3
BashTex
Please open Telegram to view this post
VIEW IN TELEGRAM
👍8
Please open Telegram to view this post
VIEW IN TELEGRAM
😁13🔥1
Сравнение конфигураций сервиса между серверами
Классическая ситуация: на одном сервере все работает. На другом - нет. При этом версии одинаковые, ОС одинаковая, но потом спустя время выясняется: конфиг отличается на одну строку.
1️⃣ Самый простой способ - diff через SSH
Но это неудобно и мусорно.
Лучше сразу:
Чисто. Без временных файлов.
2️⃣ Игнорировать комментарии и пустые строки. Очень часто различия только в комментариях.
Теперь сравнивается только реальная логика.
3️⃣ Если это nginx - сравнивать весь effective config. У nginx часто include-файлы.
Правильнее:
Так мы сравниваем итоговую конфигурацию.
4️⃣ Проверка sshd. У ssh есть удобная команда:
Она выводит итоговую конфигурацию с учетом defaults. Сравниваем так же через process substitution.
5️⃣ Если серверов много. Массовая проверка:
Сортировка помогает убрать разницу порядка строк.
BashTex📱 #bash #utils
Классическая ситуация: на одном сервере все работает. На другом - нет. При этом версии одинаковые, ОС одинаковая, но потом спустя время выясняется: конфиг отличается на одну строку.
😁 Задача
Сравнить конфигурации одного сервиса между серверами:
nginx.conf
sshd_config
app.conf
Быстро. Чисто. Без копипасты глазами.
ssh server1 "cat /etc/nginx/nginx.conf" > s1.conf
ssh server2 "cat /etc/nginx/nginx.conf" > s2.conf
diff -u s1.conf s2.conf
Но это неудобно и мусорно.
Лучше сразу:
diff -u \
<(ssh server1 "cat /etc/nginx/nginx.conf") \
<(ssh server2 "cat /etc/nginx/nginx.conf")
Чисто. Без временных файлов.
diff -u \
<(ssh s1 "grep -vE '^\s*#|^\s*$' /etc/ssh/sshd_config") \
<(ssh s2 "grep -vE '^\s*#|^\s*$' /etc/ssh/sshd_config")
Теперь сравнивается только реальная логика.
Правильнее:
ssh s1 "nginx -T" > s1.nginx
ssh s2 "nginx -T" > s2.nginx
diff -u s1.nginx s2.nginx
Так мы сравниваем итоговую конфигурацию.
sshd -T
Она выводит итоговую конфигурацию с учетом defaults. Сравниваем так же через process substitution.
for host in s1 s2 s3; do
ssh "$host" "sshd -T" | sort > "$host.conf"
done
diff -u s1.conf s2.conf
Сортировка помогает убрать разницу порядка строк.
BashTex
Please open Telegram to view this post
VIEW IN TELEGRAM
👍4
Отладка bash-скриптов
В bash есть встроенная трассировка, и она может быть эффективнее любого printf-дебага.
1️⃣ Включаем трассировку
Теперь каждая команда будет выводиться перед выполнением:
Отключить:
Полезно включать только на проблемном участке.
2️⃣ Отладка всего скрипта. Можно запустить сразу так:
Или добавить в начало:
3️⃣ Сделать трассировку читаемой. По умолчанию вывод начинается с +. Это скучно. Можно добавить больше контекста:
Теперь вывод будет выглядеть так:
Здесь видно: время, файл и номер строки Для сложных скриптов - это крайне полезно.
4️⃣ Отдельный лог отладки. Чтобы не засорять stdout:
Теперь трассировка уйдет в
5️⃣ Отладка переменных. Полезные режимы:
Продвинутый вариант:
BashTex📱 #bash #utils
В bash есть встроенная трассировка, и она может быть эффективнее любого printf-дебага.
set -x
Теперь каждая команда будет выводиться перед выполнением:
+ var=10
+ echo 10
Отключить:
set +x
Полезно включать только на проблемном участке.
bash -x script.sh
Или добавить в начало:
#!/bin/bash
set -x
export PS4='+ $(date "+%H:%M:%S") ${BASH_SOURCE}:${LINENO}: '
Теперь вывод будет выглядеть так:
+ 14:32:10 script.sh:12: var=10
Здесь видно: время, файл и номер строки Для сложных скриптов - это крайне полезно.
exec 3>/tmp/debug.log
BASH_XTRACEFD=3
set -x
Теперь трассировка уйдет в
/tmp/debug.log, а основной вывод останется чистым. Это особенно важно для автоматизации и CI.
set -u # ошибка при использовании unset переменной
set -e # выход при ошибке
set -o pipefail # ловим ошибки в пайпах
Продвинутый вариант:
set -euxo pipefail
BashTex
Please open Telegram to view this post
VIEW IN TELEGRAM
👍6🔥3
Генерация временных файлов
Практически каждый, кому необходимы были временные файлы писал так:
И вроде все ок, но на деле это один из самых старых и самых недооцененных багов в bash.
▪️ Почему /tmp/file.$RANDOM - это плохая идея
1️⃣ Коллизии.
2️⃣ Race condition. Ты:
А злоумышленник заранее создал симлинк с таким именем и твой скрипт перезапишет чужой файл. Это классическая symlink attack.
3️⃣ Неправильные права. Файл создается с текущим umask. Это может быть не то, что ты ожидал.
▪️ Правильный способ - mktemp
Результат:
▪️ Создание временной директории
▪️ Правильная очистка. Всегда следует добавлять cleanup:
Теперь файл удалится даже при ошибке или Ctrl+C.
▪️ Когда нужно имя с префиксом
Или:
BashTex📱 #bash #utils
Практически каждый, кому необходимы были временные файлы писал так:
TMP="/tmp/script.$RANDOM"
И вроде все ок, но на деле это один из самых старых и самых недооцененных багов в bash.
$RANDOM - это число от 0 до 32767. При параллельном запуске скриптов совпадения реальны.
echo "data" > /tmp/file.$RANDOM
А злоумышленник заранее создал симлинк с таким именем и твой скрипт перезапишет чужой файл. Это классическая symlink attack.
TMP=$(mktemp)
Результат:
/tmp/tmp.ABCd93kL
Что делает mktemp:
генерирует криптографически безопасное имя
создаёт файл атомарно
выставляет права 600
исключает race condition
TMP_DIR=$(mktemp -d)
TMP=$(mktemp)
cleanup() {
rm -f "$TMP"
}
trap cleanup EXIT
Теперь файл удалится даже при ошибке или Ctrl+C.
mktemp /tmp/myscript.XXXXXX
Или:
mktemp -d /tmp/myscript.XXXXXX
XXXXXX обязательно - это шаблон.BashTex
Please open Telegram to view this post
VIEW IN TELEGRAM
👍8
Почему sleep - это костыль?
Очень часто используют такую конструкцию:
Или ещё хуже: в цикле с фиксированной паузой.
Это неуправляемый retry, который: создает лишнюю нагрузку, не учитывает тип ошибки, не масштабируется и может устроить небольшой DDoS при массовом падении сервиса.
Правильный подход - retry с exponential backoff.
▪️ Почему sleep 5 - это плохая стратегия.
Представим: API лежит 2 минуты. 100 серверов начинают делать: запрос -> sleep 5 -> запрос -> sleep 5 → …
Ты получаешь синхронную атаку на уже мертвый сервис.
Exponential backoff делает паузы все длиннее: 1s -> 2s -> 4s -> 8s -> 16s -> ...
Это: снижает нагрузку, дает сервису восстановиться, уменьшает каскадные падения
🛠 Базовая retry-обертка
▪️ Использование:
▪️ Что здесь происходит
выходной код управляет retry;
задержка удваивается;
максимум попыток ограничен.
▪️ Когда retry вреден
при логических ошибках (401, 403);
если операция не идемпотентна;
если сервис возвращает fatal error.
Retry должен применяться к временным ошибкам, а не ко всем подряд.
BashTex📱 #bash #utils
Очень часто используют такую конструкцию:
curl https://api.bashtex.com || sleep 5 && curl https://api.bashtex.com
Или ещё хуже: в цикле с фиксированной паузой.
Это неуправляемый retry, который: создает лишнюю нагрузку, не учитывает тип ошибки, не масштабируется и может устроить небольшой DDoS при массовом падении сервиса.
Правильный подход - retry с exponential backoff.
Представим: API лежит 2 минуты. 100 серверов начинают делать: запрос -> sleep 5 -> запрос -> sleep 5 → …
Ты получаешь синхронную атаку на уже мертвый сервис.
Exponential backoff делает паузы все длиннее: 1s -> 2s -> 4s -> 8s -> 16s -> ...
Это: снижает нагрузку, дает сервису восстановиться, уменьшает каскадные падения
retry() {
local max_attempts=$1
shift
local attempt=1
local delay=1
while true; do
"$@" && return 0
if (( attempt >= max_attempts )); then
echo "Команда не удалась после $attempt попыток"
return 1
fi
echo "Попытка $attempt неудачна. Повтор через $delay сек."
sleep "$delay"
attempt=$(( attempt + 1 ))
delay=$(( delay * 2 ))
done
}
retry 5 curl -fsS https://api.bashtex.com
"$@" - передаем любую команду;выходной код управляет retry;
задержка удваивается;
максимум попыток ограничен.
при логических ошибках (401, 403);
если операция не идемпотентна;
если сервис возвращает fatal error.
Retry должен применяться к временным ошибкам, а не ко всем подряд.
BashTex
Please open Telegram to view this post
VIEW IN TELEGRAM
👍4
Профилирование bash-скрипта
Большинство bash-скриптов медленные не потому что bash плохой, а потому что внутри: лишние grep | awk | sed, десятки subshell, сетевые вызовы без таймаутов и циклы по 100k строк
И пока не измерил - не оптимизируешь.
1️⃣ Базовый уровень - time. Самое простое:
Вывод:
Если real > user + sys - это значит, что скрипт ждет (сеть, диск, sleep).
Если user большой - это значит много вычислений.
2️⃣ Профилирование построчно через DEBUG trap. Настоящая магия начинается здесь.
Но это просто трассировка. Чтобы измерять время между строками:
Теперь перед каждой командой будет вывод:
И сразу видно: где реальная задержка, какой вызов тормозит и сколько стоит subshell
⚠️ Важно
На больших скриптах лог будет огромный.
▪️ Мини-профилировщик функции. Если нужно измерить только конкретный блок:
Использование:
BashTex📱 #bash #utils
Большинство bash-скриптов медленные не потому что bash плохой, а потому что внутри: лишние grep | awk | sed, десятки subshell, сетевые вызовы без таймаутов и циклы по 100k строк
И пока не измерил - не оптимизируешь.
time ./script.sh
Вывод:
real 0m3.421s
user 0m0.212s
sys 0m0.084s
Что важно:real- общее время (включая ожидание сети, IO)user- CPU в user spacesys- системные вызовы
Если real > user + sys - это значит, что скрипт ждет (сеть, диск, sleep).
Если user большой - это значит много вычислений.
#!/usr/bin/env bash
PS4='+ $(date "+%s.%N") '
exec 3>&2
BASH_XTRACEFD=3
set -x
Но это просто трассировка. Чтобы измерять время между строками:
#!/usr/bin/env bash
last_time=$(date +%s%N)
trap '
now=$(date +%s%N)
diff=$(( (now - last_time) / 1000000 ))
echo "${diff} ms → $BASH_COMMAND"
last_time=$now
' DEBUG
Теперь перед каждой командой будет вывод:
2 ms → var=$(cat file)
153 ms → curl https://api
1 ms → echo done
И сразу видно: где реальная задержка, какой вызов тормозит и сколько стоит subshell
trap DEBUG вызывается перед каждой командой, включая: внутренние, подстановки и циклы.На больших скриптах лог будет огромный.
measure() {
local start=$(date +%s%N)
"$@"
local end=$(date +%s%N)
echo "Функция заняла $(( (end - start)/1000000 )) ms"
}
Использование:
measure heavy_function
BashTex
Please open Telegram to view this post
VIEW IN TELEGRAM
👍8
Автоматический failover при недоступности mount-точки
Неприятная ситуация когда mount вроде есть, но по факту хранилище уже умерло. NFS отвалился. iSCSI завис. Ceph не отвечает. Сервис продолжает писать… в никуда.
Решение - автоматическая логика: Проверка -> попытка remount -> переключение на fallback.
▪️ Логика failover
Проверяем, что mount существует;
Проверяем доступность записи;
Если ошибка, то пробуем remount;
Если не помогло - переключаемся на fallback.
🛠 Пример скрипта
▪️ Что здесь важно
1.
2.
3.
▪️ Как запускать правильно
Лучше всего: systemd timer или healthcheck внутри сервиса или отдельный watchdog unit
⚠️ Нюансы
Если сервис пишет активно, то нужно ставить его на паузу перед failover;
fallback должен быть заранее подготовлен;
важно логировать переключения;
нужен механизм возврата обратно.
BashTex📱 #bash #utils
Неприятная ситуация когда mount вроде есть, но по факту хранилище уже умерло. NFS отвалился. iSCSI завис. Ceph не отвечает. Сервис продолжает писать… в никуда.
Решение - автоматическая логика: Проверка -> попытка remount -> переключение на fallback.
Проверяем, что mount существует;
Проверяем доступность записи;
Если ошибка, то пробуем remount;
Если не помогло - переключаемся на fallback.
#!/usr/bin/env bash
MOUNT_POINT="/data"
FALLBACK="/data_local"
TEST_FILE="$MOUNT_POINT/.healthcheck"
log() {
echo "$(date '+%F %T') | $1"
}
check_mount() {
mountpoint -q "$MOUNT_POINT" || return 1
timeout 3 touch "$TEST_FILE" 2>/dev/null || return 1
rm -f "$TEST_FILE"
}
remount() {
log "Попытка remount..."
mount -o remount "$MOUNT_POINT"
}
switch_to_fallback() {
log "Переключение на fallback $FALLBACK"
umount -l "$MOUNT_POINT"
mount --bind "$FALLBACK" "$MOUNT_POINT"
}
main() {
if check_mount; then
log "Mount работает нормально"
exit 0
fi
log "Mount недоступен"
remount && sleep 2
if check_mount; then
log "Remount помог"
exit 0
fi
switch_to_fallback
}
main
1.
timeout - если NFS завис, без timeout скрипт повиснет навсегда.2.
mountpoint -q - Проверяет именно mount, а не просто директорию.3.
umount -l - lazy umount - полезно, если есть залипшие процессы.Лучше всего: systemd timer или healthcheck внутри сервиса или отдельный watchdog unit
Если сервис пишет активно, то нужно ставить его на паузу перед failover;
fallback должен быть заранее подготовлен;
важно логировать переключения;
нужен механизм возврата обратно.
BashTex
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥8
Please open Telegram to view this post
VIEW IN TELEGRAM
😁12
Sync tricks: инкременты, hard links и бэкапы
Все знают про rsync, но не все используют его на 100%. Сегодня разберем три мощных приема: инкрементальные бэкапы, hard links для экономии места и атомарная схема снапшотов
1️⃣ Инкрементальный sync. База:
Что происходит:
Это зеркало. Но это не история. Каждый запуск перезаписывает прошлый бэкап.
2️⃣ Настоящие снапшоты через hard links. Допустим, структура такая:
Команда:
Что происходит:
изменённые файлы - копируются
неизмененные - создаются как hard link
места занимает почти как один бэкап
Что тут необычного?
Hard link - это не копия файла. Это второе имя для того же inode. Проверка:
Одинаковый inode - это один и тот же файл.
Удаляешь снапшот 29-го числа и файл не удалится, если он есть в 30-м.
Здесь же и начинается экономия места. Если данные почти не меняются: 30 снапшотов могут занимать +5–10% к объему, вместо ×30 бэкапов. Это почти как ZFS snapshots, но работает на обычном ext4.
3️⃣ Атомарная схема без битых бэкапов. Никогда не пиши прямо в конечную папку.
Правильно:
Теперь:
если rsync упал - снапшот не появится
всегда либо полный, либо отсутствует
4️⃣ Ротация снапшотов. Храним 14 дней:
(Осторожно с путями.)
🛠 Мини-скрипт
Первый запуск - обычная копия.
Все следующие - инкременты.
BashTex📱 #bash #backup
Все знают про rsync, но не все используют его на 100%. Сегодня разберем три мощных приема: инкрементальные бэкапы, hard links для экономии места и атомарная схема снапшотов
rsync -a --delete /data/ /backup/current/
Что происходит:
-a - сохраняет права, владельца, время--delete - удаляет лишнее в приемникеЭто зеркало. Но это не история. Каждый запуск перезаписывает прошлый бэкап.
/backup/
├── 2026-03-29/
├── 2026-03-30/
└── 2026-03-31/
Команда:
TODAY=$(date +%F)
YESTERDAY=$(date -d "yesterday" +%F)
rsync -a --delete \
--link-dest=/backup/$YESTERDAY \
/data/ /backup/$TODAY/
Что происходит:
изменённые файлы - копируются
неизмененные - создаются как hard link
места занимает почти как один бэкап
Что тут необычного?
Hard link - это не копия файла. Это второе имя для того же inode. Проверка:
ls -li file1 file2
Одинаковый inode - это один и тот же файл.
Удаляешь снапшот 29-го числа и файл не удалится, если он есть в 30-м.
Здесь же и начинается экономия места. Если данные почти не меняются: 30 снапшотов могут занимать +5–10% к объему, вместо ×30 бэкапов. Это почти как ZFS snapshots, но работает на обычном ext4.
Правильно:
rsync -a --delete \
--link-dest=/backup/$YESTERDAY \
/data/ /backup/.tmp-$TODAY/ &&
mv /backup/.tmp-$TODAY /backup/$TODAY
Теперь:
если rsync упал - снапшот не появится
всегда либо полный, либо отсутствует
find /backup -maxdepth 1 -type d -mtime +14 -exec rm -rf {} \;
(Осторожно с путями.)
#!/usr/bin/env bash
set -euo pipefail
SRC="/data"
DST="/backup"
TODAY=$(date +%F)
YESTERDAY=$(ls -1 $DST | sort | tail -n 1)
rsync -a --delete \
${YESTERDAY:+--link-dest=$DST/$YESTERDAY} \
"$SRC/" "$DST/.tmp-$TODAY/"
mv "$DST/.tmp-$TODAY" "$DST/$TODAY"
Первый запуск - обычная копия.
Все следующие - инкременты.
BashTex
Please open Telegram to view this post
VIEW IN TELEGRAM
👍10
Почему cron не сработал
Каждый админ хотя бы раз слышал: Cron не отработал.
Но, спойлер:в 90% случаев cron работает. Не работает: окружение, путь или логика скрипта.
1️⃣ Неправильный PATH. Cron запускается с минимальным окружением. То, что работает в shell:
В cron может не найти:
Проверка:
Решение:
Или использовать абсолютные пути:
2️⃣ Не тот пользователь. Есть:
И есть:
Это два разных crontab.
Проверка:
Иногда задача просто стоит не там.
3️⃣ Нет прав на файл. Скрипт есть. Но:
забыли. Или владелец не тот.
4️⃣ Неправильный shebang. Если в начале:
А на системе bash лежит в:
Cron молча свалится. Проверка:
Лучший вариант:
5️⃣ Нет логирования и кажется, не сработал Если вывод не перенаправлен, ты его не увидишь. Правильно:
Без этого ты гадаешь.
6️⃣ Разница окружения. Cron не знает:
твоих alias
твоих переменных
твоего virtualenv
твоего nvm
Если работает вручную, но не в cron - почти всегда проблема в ENV.
Debug-метод:
7️⃣ Скрипт падает внутри. Cron сработал. Но внутри:
где
Без:
ошибка могла остаться незамеченной.
8️⃣ Cron вообще не запущен. Редко, но бывает. Проверка:
BashTex📱 #cron
Каждый админ хотя бы раз слышал: Cron не отработал.
Но, спойлер:
myscript.sh
В cron может не найти:
command not foundПроверка:
echo $PATH
Решение:
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
Или использовать абсолютные пути:
/usr/bin/rsync
/usr/bin/docker
crontab -e
И есть:
sudo crontab -e
Это два разных crontab.
Проверка:
crontab -l
sudo crontab -l
Иногда задача просто стоит не там.
chmod +x script.sh
забыли. Или владелец не тот.
#!/bin/bash
А на системе bash лежит в:
/usr/bin/bash
Cron молча свалится. Проверка:
which bash
Лучший вариант:
#!/usr/bin/env bash
0 3 * * * /path/script.sh >> /var/log/script.log 2>&1
Без этого ты гадаешь.
твоих alias
твоих переменных
твоего virtualenv
твоего nvm
Если работает вручную, но не в cron - почти всегда проблема в ENV.
Debug-метод:
env > /tmp/cron_env.txt
rm -rf "$DIR"
где
$DIR пустой.Без:
set -euo pipefail
ошибка могла остаться незамеченной.
systemctl status cron
# или
systemctl status crond
BashTex
Please open Telegram to view this post
VIEW IN TELEGRAM
👍11
Please open Telegram to view this post
VIEW IN TELEGRAM
😁11🔥1
Работа с дескрипторами файлов
В linux все - это файл. И каждый процесс работает с файловыми дескрипторами:
0 - stdin
1 - stdout
2 - stderr
3+ - любые дополнительные
Понимание этого - ключ к нормальной автоматизации.
▪️ Базовые перенаправления
⚠️ Порядок важен:
▪️ Dup (дублирование дескрипторов). Можно создать свой поток:
Теперь 3 пишет в
Это удобно, если нужно:
отделить debug-лог от основного вывода
не засорять stdout
вести параллельный лог
▪️ Временное перенаправление
Или:
Теперь весь скрипт пишет в лог.
▪️ Закрытие дескриптора. Иногда нужно закрыть поток:
Это освобождает дескриптор.
Полезно при работе с сокетами, FIFO и временными логами.
▪️ Практический кейс. Раздельный лог и чистый stdout:
Трассировка уйдет в файл, а stdout останется чистым для пайплайна.
BashTex📱 #bash #utils
В linux все - это файл. И каждый процесс работает с файловыми дескрипторами:
0 - stdin
1 - stdout
2 - stderr
3+ - любые дополнительные
Понимание этого - ключ к нормальной автоматизации.
command >out.log # stdout
command 2>err.log # stderr
command >all.log 2>&1 # объединить
2>&1 значит: направь stderr туда же, куда сейчас смотрит stdout.command 2>&1 >file - НЕ то же самое.
exec 3>debug.log
echo "debug" >&3
Теперь 3 пишет в
debug.log.Это удобно, если нужно:
отделить debug-лог от основного вывода
не засорять stdout
вести параллельный лог
{
echo "только в файл"
} >file.txt
Или:
exec >all_output.log 2>&1
Теперь весь скрипт пишет в лог.
exec 3>&-
Это освобождает дескриптор.
Полезно при работе с сокетами, FIFO и временными логами.
exec 3>debug.log
BASH_XTRACEFD=3
set -x
Трассировка уйдет в файл, а stdout останется чистым для пайплайна.
BashTex
Please open Telegram to view this post
VIEW IN TELEGRAM
👍9
Контроль превышения лимитов ulimit у сервисов
Сервис падает без причины, но в логах странные ошибки:
Очень часто это не баг. Это лимиты ulimit.
▪️ Какие лимиты критичны
1️⃣ nofile - открытые файлы. Сколько файловых дескрипторов может открыть процесс.
Проверка:
Или для конкретного процесса:
Если видишь 1024, то для прод-сервиса это почти всегда мало.
2️⃣ nproc - количество процессов. Ограничение на число процессов пользователя.
Проверка:
Или:
Если приложение активно форкает воркеры - лимит может убить его.
▪️ Почему это сложно заметить
сервис стартует нормально
падает только под нагрузкой
в логах нет прямого указания на лимит
systemd может перезапускать бесконечно
▪️ Проверка лимитов у systemd-сервиса
Если пусто, то используются дефолтные значения системы.
▪️ Как исправить. Создать override:
Добавить:
Перезагрузить:
▪️ Быстрая диагностика падений. Если сервис падает под нагрузкой:
Посмотреть количество открытых файлов:
Проверить количество процессов пользователя:
BashTex📱 #bash #check
Сервис падает без причины, но в логах странные ошибки:
Too many open files
Resource temporarily unavailable
fork: retry
cannot allocate memory
Очень часто это не баг. Это лимиты ulimit.
Проверка:
ulimit -n
Или для конкретного процесса:
cat /proc/<PID>/limits | grep "Max open files"
Если видишь 1024, то для прод-сервиса это почти всегда мало.
Проверка:
ulimit -u
Или:
cat /proc/<PID>/limits | grep "Max processes"
Если приложение активно форкает воркеры - лимит может убить его.
сервис стартует нормально
падает только под нагрузкой
в логах нет прямого указания на лимит
systemd может перезапускать бесконечно
systemctl show myservice | grep -E 'LimitNOFILE|LimitNPROC'
Если пусто, то используются дефолтные значения системы.
systemctl edit myservice
Добавить:
[Service]
LimitNOFILE=65535
LimitNPROC=4096
Перезагрузить:
systemctl daemon-reload
systemctl restart myservice
/proc/<PID>/limits
Посмотреть количество открытых файлов:
ls /proc/<PID>/fd | wc -l
Проверить количество процессов пользователя:
ps -u username | wc -l
BashTex
Please open Telegram to view this post
VIEW IN TELEGRAM
👍8
Современный мониторинг ресурсов
Если стандартный top кажется неудобным, а htop - уже привычным, стоит попробовать btop. Это современный мониторинг ресурсов с удобным интерфейсом и большим количеством полезных функций.
▪️ Что показывает btop. В одном окне можно увидеть:
загрузку CPU по ядрам
использование RAM и swap
дисковую активность
сетевой трафик
список процессов с сортировкой
Интерфейс обновляется в реальном времени и выглядит значительно понятнее, чем классические инструменты.
▪️ Установка
▪️ Запуск:
🤩 Что делает его удобным
Интерактивный список процессов
Можно:
сортировать по CPU / RAM
убивать процессы
фильтровать список
▪️ Клавиши:
F9 - kill process
F6 - сортировка
F - поиск процесса
▪️ Мониторинг сети. btop показывает:
входящий / исходящий трафик
скорость сети
активность интерфейсов
Это удобно, когда нужно быстро понять куда уходит трафик.
▪️ Наглядная нагрузка CPU. В отличие от top, здесь видно:
загрузку каждого ядра
историю нагрузки
spikes CPU
Очень полезно при диагностике перегрузки сервера.
▪️ Полезные настройки
Открыть настройки:
Можно изменить:
тему интерфейса
частоту обновления
отображаемые графики
сетевые интерфейсы
Конфиг хранится в:
BashTex📱 #linux #monitoring
Если стандартный top кажется неудобным, а htop - уже привычным, стоит попробовать btop. Это современный мониторинг ресурсов с удобным интерфейсом и большим количеством полезных функций.
загрузку CPU по ядрам
использование RAM и swap
дисковую активность
сетевой трафик
список процессов с сортировкой
Интерфейс обновляется в реальном времени и выглядит значительно понятнее, чем классические инструменты.
sudo apt install btop # Ubuntu / Debian
sudo dnf install btop # RHEL / CentOS / Fedora
sudo pacman -S btop # Arch
btop
Интерактивный список процессов
Можно:
сортировать по CPU / RAM
убивать процессы
фильтровать список
F9 - kill process
F6 - сортировка
F - поиск процесса
входящий / исходящий трафик
скорость сети
активность интерфейсов
Это удобно, когда нужно быстро понять куда уходит трафик.
загрузку каждого ядра
историю нагрузки
spikes CPU
Очень полезно при диагностике перегрузки сервера.
Открыть настройки:
ESCМожно изменить:
тему интерфейса
частоту обновления
отображаемые графики
сетевые интерфейсы
Конфиг хранится в:
~/.config/btop/btop.conf
BashTex
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥6👍3
Проверка, какие сервисы стартуют дольше всего
Если сервер долго загружается, проблема часто не в системе в целом, а в одном-двух медленных сервисах. В systemd есть встроенный инструмент для такой диагностики -
▪️ Смотрим самые медленные сервисы. Самая полезная команда:
Пример вывода:
Список уже отсортирован по времени запуска.
Что это значит:
docker.service запускался 8.5 секунд
networkd-wait-online.service ждал сеть почти 5 секунд
Именно такие сервисы чаще всего замедляют загрузку.
▪️ Смотрим цепочку зависимостей. Иногда сервис запускается быстро, но ждет другой сервис. Проверить можно так:
Пример:
Это показывает кто кого блокирует при загрузке.
▪️ Общая статистика загрузки. Полезная команда:
Пример:
Здесь видно:
время загрузки ядра
время загрузки systemd сервисов
▪️ Частые причины медленной загрузки. Чаще всего тормозят:
networkd-wait-online.service
docker.service
snapd.service
базы данных
storage-сервисы
Иногда сервис ждет ресурс, который ему не нужен.
BashTex📱 #linux #utils
Если сервер долго загружается, проблема часто не в системе в целом, а в одном-двух медленных сервисах. В systemd есть встроенный инструмент для такой диагностики -
systemd-analyze.
systemd-analyze blame
Пример вывода:
8.532s docker.service
4.921s networkd-wait-online.service
3.102s postgresql.service
1.876s nginx.service
Список уже отсортирован по времени запуска.
Что это значит:
docker.service запускался 8.5 секунд
networkd-wait-online.service ждал сеть почти 5 секунд
Именно такие сервисы чаще всего замедляют загрузку.
systemd-analyze critical-chain
Пример:
graphical.target
└─docker.service
└─network-online.target
└─systemd-networkd-wait-online.service
Это показывает кто кого блокирует при загрузке.
systemd-analyze
Пример:
Startup finished in 3.112s (kernel)
+ 9.842s (userspace)
Здесь видно:
время загрузки ядра
время загрузки systemd сервисов
networkd-wait-online.service
docker.service
snapd.service
базы данных
storage-сервисы
Иногда сервис ждет ресурс, который ему не нужен.
BashTex
Please open Telegram to view this post
VIEW IN TELEGRAM
👍6
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥7🗿1
Использование /dev/null правильно
/dev/null - это специальное устройство в linux, которое принимает любые данные и просто их выбрасывает. Часто используется, когда нужно убрать лишний вывод команд.
▪️ Отключаем stdout. Если команда выводит много информации:
Теперь stdout (fd 1) будет игнорироваться.
▪️ Отключаем только ошибки. Если нужно скрыть stderr (fd 2):
Ошибки исчезнут, но нормальный вывод останется.
▪️ Полное подавление вывода. Иногда нужно убрать все:
Что происходит:
stdout в /dev/null и stderr туда же
▪️ Пример:
Команда выполняется тихо.
▪️ Проверка команды без мусора. Полезный паттерн:
Проверяем наличие команды:
🤩 Частая ошибка. Некоторые пишут:
Это не то же самое. Правильный порядок:
Потому что редиректы выполняются слева направо.
▪️ Практические кейсы. Проверка файла
Проверка сервиса
Проверка сети
BashTex📱 #bash #utils
/dev/null - это специальное устройство в linux, которое принимает любые данные и просто их выбрасывает. Часто используется, когда нужно убрать лишний вывод команд.
ls /tmp > /dev/null
Теперь stdout (fd 1) будет игнорироваться.
ls /no/such/file 2> /dev/null
Ошибки исчезнут, но нормальный вывод останется.
command > /dev/null 2>&1
Что происходит:
stdout в /dev/null и stderr туда же
curl -s https://bashtex.com > /dev/null 2>&1
Команда выполняется тихо.
command -v docker > /dev/null 2>&1
Проверяем наличие команды:
if command -v docker > /dev/null 2>&1; then
echo "Docker установлен"
fi
command 2>&1 > /dev/null
Это не то же самое. Правильный порядок:
command > /dev/null 2>&1
Потому что редиректы выполняются слева направо.
test -f file.txt > /dev/null 2>&1
Проверка сервиса
systemctl is-active nginx > /dev/null
Проверка сети
ping -c1 google.com > /dev/null 2>&1
BashTex
Please open Telegram to view this post
VIEW IN TELEGRAM
👍10
Как работать с большими файлами без загрузки в память
Частая ошибка: пытаться обработать большой файл, загружая его целиком в память.
Например:
Если файл весит несколько гигабайт - скрипт может съесть всю RAM или просто упасть.
Правильный подход - обрабатывать файл потоково.
▪️ Чтение файла построчно. Самый безопасный способ:
файл читается строка за строкой;
память почти не используется;
можно обрабатывать очень большие файлы.
▪️ Использование grep / awk. Unix-утилиты работают потоково, поэтому идеально подходят для больших файлов. Например:
или:
Они читают файл частями, не загружая его полностью.
▪️ Анализ логов. Быстрый пример анализа:
Это обработает гигабайтные логи за секунды.
▪️ tail для живых логов. Если файл постоянно растет:
Можно фильтровать сразу:
Это удобно для мониторинга сервисов.
▪️ Чтение файла частями. Иногда нужно обрабатывать батчами:
Файл разобьется на куски:
Теперь их можно обрабатывать параллельно.
BashTex📱 #bash #utils
Частая ошибка: пытаться обработать большой файл, загружая его целиком в память.
Например:
content=$(cat huge.log)
Если файл весит несколько гигабайт - скрипт может съесть всю RAM или просто упасть.
Правильный подход - обрабатывать файл потоково.
while IFS= read -r line; do
echo "$line"
done < huge.log
файл читается строка за строкой;
память почти не используется;
можно обрабатывать очень большие файлы.
grep ERROR huge.log
или:
awk '/ERROR/ {print $0}' huge.log
Они читают файл частями, не загружая его полностью.
grep ERROR huge.log | awk '{print $5}' | sort | uniq -c
Это обработает гигабайтные логи за секунды.
tail -F huge.log
Можно фильтровать сразу:
tail -F huge.log | grep ERROR
Это удобно для мониторинга сервисов.
split -l 100000 huge.log part_
Файл разобьется на куски:
part_aa
part_ab
part_ac
Теперь их можно обрабатывать параллельно.
BashTex
Please open Telegram to view this post
VIEW IN TELEGRAM
👍8🔥1
Автоматическая очистка старых systemd-журналов
systemd-journal удобен, но есть проблема - логи могут занять гигабайты диска, особенно на загруженных серверах.
▪️ Сколько сейчас занимают журналы
▪️ Очистка по размеру. Оставить, например, не больше 500 МБ:
Удалятся самые старые записи, пока объем не станет ~ 500 МБ.
▪️ Очистка по времени. Оставить только последние 7 дней:
▪️ Комбинированный подход. Часто используют оба ограничения:
Это дает контроль и по времени, и по размеру.
▪️ Добавление в cron
▪️ Постоянная настройка через journald. Чтобы не чистить вручную:
Пример:
После изменения:
BashTex📱 #bash
systemd-journal удобен, но есть проблема - логи могут занять гигабайты диска, особенно на загруженных серверах.
journalctl --disk-usage
Archived and active journals take up 2.3G on disk
journalctl --vacuum-size=500M
Удалятся самые старые записи, пока объем не станет ~ 500 МБ.
journalctl --vacuum-time=7d
Поддерживаются:
d - дни
h - часы
m - минуты
journalctl --vacuum-time=7d
journalctl --vacuum-size=1G
Это дает контроль и по времени, и по размеру.
0 3 * * * /usr/bin/journalctl --vacuum-time=7d --vacuum-size=1G
/etc/systemd/journald.conf
Пример:
SystemMaxUse=1G
SystemKeepFree=500M
MaxRetentionSec=7day
После изменения:
systemctl restart systemd-journald
BashTex
Please open Telegram to view this post
VIEW IN TELEGRAM
👍10
Brace expansion для массовых операций
Brace expansion - это встроенная фича, которая позволяет генерировать списки прямо в командной строке. Работает быстро, читаемо и без лишних for.
▪️ Диапазоны чисел
С шагом:
▪️ Диапазоны букв
▪️ Массовое создание файлов
Создаст:
▪️ Создание каталогов
Результат:
▪️ Комбинирование
▪️ Практика: быстрые шаблоны. Создать структуру окружений
Создать пачку логов
Удаление группы файлов
⚠️ Важный момент. Brace expansion происходит до выполнения команды. Это не цикл и не glob. Например:
сначала превращается в:
BashTex📱 #bash #utils
Brace expansion - это встроенная фича, которая позволяет генерировать списки прямо в командной строке. Работает быстро, читаемо и без лишних for.
echo {1..5}
1 2 3 4 5
С шагом:
echo {1..10..2}
1 3 5 7 9
echo {a..e}
a b c d e
touch file_{1..5}.txt
Создаст:
file_1.txt
file_2.txt
...
mkdir -p project/{src,bin,config,logs}
Результат:
project/src
project/bin
project/config
project/logs
echo {dev,prod}_{1..3}
dev_1 dev_2 dev_3 prod_1 prod_2 prod_3
mkdir -p env/{dev,stage,prod}/{logs,tmp,data}
Создать пачку логов
touch log_{2024..2026}_{01..12}.log
Удаление группы файлов
rm file_{1..10}.txt
echo file_{1..3}.txt
сначала превращается в:
echo file_1.txt file_2.txt file_3.txt
BashTex
Please open Telegram to view this post
VIEW IN TELEGRAM
👍9