Перехват stdout/stderr отдельных функций и подпрограмм
Обычно мы перенаправляем вывод глобально:
▪️ Пример 1. Логирование только stdout функции
stdout (info, debug) уйдет в stdout.log;
stderr (error) появится в терминале.
▪️ Пример 2. Перехват stderr подпрограммы
Ошибки от ls /root попадут в stderr.log, а «done» останется в терминале.
▪️ Пример 3. Отдельный канал для «отладки» внутри функции. Иногда хочется иметь третий тип вывода, помимо stdout/stderr.
Так можно вести невидимый debug-лог параллельно с обычной работой.
▪️ Пример 4. Подмена stdout на время вызова
Прием позволяет временно заменить stdout и потом вернуть его обратно.
BashTex📱 #bash #utils
Обычно мы перенаправляем вывод глобально:
myfunc >out.log 2>err.log. Но что, если нужно внутри скрипта гибко ловить stdout/stderr отдельных функций и даже подпрограмм, не ломая общий вывод? Тут могут помочь динамические файловые дескрипторы через exec {fd}>.
logfile="stdout.log"
myfunc() {
echo "info: started"
echo "debug: internal"
echo "error: fail" >&2
}
exec {fd}> "$logfile" # создаем FD
myfunc 1>&$fd # stdout -> в файл, stderr остаётся на экран
exec {fd}>&- # закрываем FD
stdout (info, debug) уйдет в stdout.log;
stderr (error) появится в терминале.
errlog="stderr.log"
exec {fd}> "$errlog"
{ ls /root; echo "done"; } 2>&$fd
exec {fd}>&-
Ошибки от ls /root попадут в stderr.log, а «done» останется в терминале.
exec {dbg}> debug.log # отдельный канал
mycalc() {
echo "42" # обычный результат
echo "step1 ok" >&$dbg
echo "step2 ok" >&$dbg
}
result=$(mycalc)
echo "result = $result"
exec {dbg}>&-
Так можно вести невидимый debug-лог параллельно с обычной работой.
mycmd() { echo "normal out"; }
{
exec {fd}> redirected.log
mycmd >&$fd
exec {fd}>&-
}
echo "done"
Прием позволяет временно заменить stdout и потом вернуть его обратно.
BashTex
Please open Telegram to view this post
VIEW IN TELEGRAM
👍9
Быстрый поиск и исправление команд из истории
Многие знают, что в bash есть история команд (history, !, !!), но мало кто активно пользуется экспресс-заменой.
▪️ Замена в последней команде: ^old^new
Если вы ввели команду с ошибкой, не нужно её перепечатывать целиком. Просто используйте конструкцию:
Пример:
Bash сам возьмет последнюю команду из истории и заменит в ней sl на ls.
▪️ Более гибкая работа с историей: !
Пример:
▪️ Ctrl+R для интерактивного поиска
нажмите Ctrl+R и начните печатать часть команды - bash покажет совпадение из истории.
Нажимайте Ctrl+R ещё раз, чтобы найти следующее.
BashTex📱 #bash
Многие знают, что в bash есть история команд (history, !, !!), но мало кто активно пользуется экспресс-заменой.
Если вы ввели команду с ошибкой, не нужно её перепечатывать целиком. Просто используйте конструкцию:
^ошибка^правильноПример:
sl -l #неверная команда
bash: sl: command not found
^sl^ls # исправление
ls -l
Bash сам возьмет последнюю команду из истории и заменит в ней sl на ls.
!! - повторяет последнюю команду!ls - повторяет последнюю команду, начинавшуюся с ls!$ - последний аргумент предыдущей команды!* - все аргументы предыдущей командыПример:
tar -xf archive.tar.gz /tmp
cd !$
cd /tmp
нажмите Ctrl+R и начните печатать часть команды - bash покажет совпадение из истории.
Нажимайте Ctrl+R ещё раз, чтобы найти следующее.
BashTex
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥8🫡1
Скрипт-сортер медиа по датам
Хаос в папке с фотографиями и видео? Все вперемешку: отпуск 2018-го рядом с мемами 2024-го. Можно навести порядок одним скриптом.
Идея скрипта в том, чтобы он автоматически раскладывал файлы по структуре:
Основание - дата создания файла (или модификации, если EXIF не нужен).
🛠 Скрипт
▪️ Запуск
🌟 Дополнительно
Если нужен EXIF (реальная дата съёмки фото, а не модификации файла) → используем exiftool:
⚠️ Для видео часто EXIF нет, но можно использовать ffprobe (часть ffmpeg) для даты.
Такой скрипт удобно запускать перед бэкапом в облако - сразу порядок в медиатеке.
BashTex📱 #bash
Хаос в папке с фотографиями и видео? Все вперемешку: отпуск 2018-го рядом с мемами 2024-го. Можно навести порядок одним скриптом.
Идея скрипта в том, чтобы он автоматически раскладывал файлы по структуре:
sorted/
├── 2018/
│ ├── 01/
│ └── 07/
├── 2020/
│ ├── 12/
└── 2024/
├── 02/
└── 05/
Основание - дата создания файла (или модификации, если EXIF не нужен).
#!/bin/bash
SRC_DIR=${1:-"./media"} # откуда берем файлы
DST_DIR=${2:-"./sorted"} # куда складываем
mkdir -p "$DST_DIR"
find "$SRC_DIR" -type f \( -iname "*.jpg" -o -iname "*.jpeg" -o -iname "*.png" -o -iname "*.mp4" -o -iname "*.mov" \) | while read -r file; do
# получаем дату модификации файла (год и месяц)
year=$(date -r "$file" +%Y)
month=$(date -r "$file" +%m)
# создаем директорию и переносим
target="$DST_DIR/$year/$month"
mkdir -p "$target"
echo "→ $file → $target/"
mv "$file" "$target/"
done
chmod +x media_sorter.sh
./media_sorter.sh ./Camera ./SortedMedia
Если нужен EXIF (реальная дата съёмки фото, а не модификации файла) → используем exiftool:
exiftool '-FileName<CreateDate' -d "%Y/%m/%Y-%m-%d_%H-%M-%S%%-c.%%e" ./media
Такой скрипт удобно запускать перед бэкапом в облако - сразу порядок в медиатеке.
BashTex
Please open Telegram to view this post
VIEW IN TELEGRAM
👍7
Множественные команды одной строкой
Одна из недооцененных фич bash - фигурные скобки {}. Она позволяет за одну строку сгенерировать множество вариантов команды.
▪️ Пример с файлами
Создаст сразу три файла:
▪️ Полезные варианты
1️⃣ Диапазоны чисел
Создаст папки:
2️⃣ Диапазоны букв
3️⃣ Комбинации (картезианское произведение)
4️⃣ Множественные команды
Скопирует оба файла за раз.
▪️ Фишки для автоматизации
1️⃣ Генерация тестовых данных:
сразу 100 файлов
2️⃣ Быстрое клонирование директорий:
создаст копию src-backup без лишнего ввода.
3️⃣ Комбинации шаблонов:
сразу раскладывание отчётов по месяцам и годам.
BashTex📱 #bash #utils
Одна из недооцененных фич bash - фигурные скобки {}. Она позволяет за одну строку сгенерировать множество вариантов команды.
touch file-{1,2,3}.md
Создаст сразу три файла:
file-1.md
file-2.md
file-3.md
mkdir backup-{2022..2025}
Создаст папки:
backup-2022 backup-2023 backup-2024 backup-2025
touch part-{a..d}.txt
part-a.txt part-b.txt part-c.txt part-d.txt
echo {dev,staging,prod}-{us,eu,asia}
dev-us staging-us prod-us dev-eu staging-eu prod-eu dev-asia staging-asia prod-asia
cp config.{yml,json} /etc/myapp/
Скопирует оба файла за раз.
touch user-{001..100}.log
сразу 100 файлов
cp -r src{,-backup}
создаст копию src-backup без лишнего ввода.
mv report_{2022..2024}_{01..12}.csv /data/reports/
сразу раскладывание отчётов по месяцам и годам.
BashTex
Please open Telegram to view this post
VIEW IN TELEGRAM
👍14
Букмарк-система для директорий
Если вы часто прыгаете между одними и теми же каталогами, держать их пути в голове (или копировать через cd /long/very/deep/path/...) - мучение. Решение: написать мини-систему закладок для директорий.
🛠 Скрипт j (добавьте это в свой ~/.bashrc или ~/.zshrc):
▪️ Использование
Добавить закладку для текущей директории:
сохраняет myproj:
Перейти в закладку:
мгновенный cd в
Удалить закладку:
Посмотреть все закладки:
BashTex📱 #bash
Если вы часто прыгаете между одними и теми же каталогами, держать их пути в голове (или копировать через cd /long/very/deep/path/...) - мучение. Решение: написать мини-систему закладок для директорий.
# Файл для хранения закладок
J_BOOKMARKS=~/.j_bookmarks
# Функция j
j() {
# без аргументов - показать список закладок
if [[ $# -eq 0 ]]; then
cat "$J_BOOKMARKS" 2>/dev/null || echo "Закладок пока нет."
return
fi
case "$1" in
+) # добавить закладку для текущей директории
echo "$2:$(pwd)" >> "$J_BOOKMARKS"
echo "Добавлено: $2 → $(pwd)"
;;
-) # удалить закладку
grep -v "^$2:" "$J_BOOKMARKS" > "$J_BOOKMARKS.tmp" && mv "$J_BOOKMARKS.tmp" "$J_BOOKMARKS"
echo "Удалено: $2"
;;
*) # перейти по метке
local target
target=$(grep "^$1:" "$J_BOOKMARKS" | cut -d: -f2-)
if [[ -n "$target" ]]; then
cd "$target" || echo "Ошибка: нет доступа к $target"
else
echo "Нет закладки: $1"
fi
;;
esac
}
Добавить закладку для текущей директории:
j + myproj
сохраняет myproj:
/home/user/projects/myprojПерейти в закладку:
j myproj
мгновенный cd в
/home/user/projects/myprojУдалить закладку:
j - myproj
Посмотреть все закладки:
j
BashTex
Please open Telegram to view this post
VIEW IN TELEGRAM
👍10
Bash-лайфхак: комментирование команд прямо в истории
Когда вы часто экспериментируете в терминале, история превращается в хаос - одинаковые команды, тестовые прогоны, случайные опечатки… И вот через месяц вы смотрите на history | grep rsync и думаете: "А что это я тут хотел сделать?" или какая из этих команд рабочая..
Но тут есть один простой трюк - добавляете комментарий прямо к команде, и он сохранится в истории.
🌟 Как это работает?
В bash можно писать так:
В истории будет сохранена вся строка. И когда через Ctrl+R или history | grep backup вы ищете - строка находится по тексту комментария тоже!
▪️ Примеры
▪️ Полезные фишки
Можно делать свои теги:
А потом искать:
BashTex📱 #bash
Когда вы часто экспериментируете в терминале, история превращается в хаос - одинаковые команды, тестовые прогоны, случайные опечатки… И вот через месяц вы смотрите на history | grep rsync и думаете: "А что это я тут хотел сделать?" или какая из этих команд рабочая..
Но тут есть один простой трюк - добавляете комментарий прямо к команде, и он сохранится в истории.
В bash можно писать так:
rsync -av --delete /data/ /backup/ # бэкап данных на внешний диск
В истории будет сохранена вся строка. И когда через Ctrl+R или history | grep backup вы ищете - строка находится по тексту комментария тоже!
ssh user@host # тест сервера
docker exec -it web bash # залезть внутрь контейнера nginx
find /var/log -type f -size +100M # поиск больших логов
Можно делать свои теги:
apt update && apt upgrade # [system-maintenance]
А потом искать:
history | grep "\[system-maintenance\]"
BashTex
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥11
Архивация только новых или измененных файлов
Как архивировать только измененные файлы без ведения отдельного списка и без сложных скриптов. Решение на самом деле простое - связка find + tar + gzip.
▪️ Проблема
Обычный
Хотелось бы архивировать только то, что изменилось за последний день/час.
▪️ Решение: find + tar
▪️ Вариант с часами (например, за 2 часа)
▪️ Инкрементальные архивы (только изменения с момента последнего бэкапа)
BashTex📱 #bash #utils
Как архивировать только измененные файлы без ведения отдельного списка и без сложных скриптов. Решение на самом деле простое - связка find + tar + gzip.
Обычный
tar -czf backup.tar.gz /data каждый раз сжимает все файлы → при больших каталогах это очень долго.Хотелось бы архивировать только то, что изменилось за последний день/час.
find /data -type f -mtime -1 -print0 | tar --null -czf backup-$(date +%F).tar.gz --files-from=-
-mtime -1 - ищет файлы, измененные за последние 24 часа-print0 + --null - защита от пробелов в именах--files-from=- - tar берет список файлов прямо из stdin
find /data -type f -mmin -120 -print0 | tar --null -czf backup-$(date +%F_%H%M).tar.gz --files-from=-
touch /tmp/last-backup
find /data -type f -newer /tmp/last-backup -print0 | \
tar --null -czf backup-$(date +%F).tar.gz --files-from=-
touch /tmp/last-backup
BashTex
Please open Telegram to view this post
VIEW IN TELEGRAM
👍5
Расширенные шаблоны
Есть фишки, про которые многие говорят: «я только недавно узнал, что так можно!» Одна из таких - расширенные шаблоны.
▪️ Включаем расширенные шаблоны
Теперь доступны конструкции:
▪️ Полезные примеры
1️⃣ Исключить расширение
Пробегаем по всем файлам, кроме .bak.
2️⃣ Сгруппировать несколько расширений
Ловим только картинки, не заморачиваясь с длинными условиями.
3️⃣ Лестница через case
Можно строить почти регулярочные проверки, но быстрее и нагляднее.
4️⃣ Исключение нескольких типов
Берём всё, кроме .log и .tmp.
5️⃣ Валидация формата прямо в if
Короткая проверка без grep и regex.
BashTex📱 #bash #utils
Есть фишки, про которые многие говорят: «я только недавно узнал, что так можно!» Одна из таких - расширенные шаблоны.
shopt -s extglob
Теперь доступны конструкции:
?(pattern) - 0 или 1 совпадение
*(pattern) - 0 или больше
+(pattern) - 1 или больше
@(pattern) - ровно одно из
!(pattern) - всё, кроме
for f in !(*.bak); do
echo "Обрабатываю $f"
done
Пробегаем по всем файлам, кроме .bak.
for img in *.@(jpg|png|gif); do
echo "Найдено изображение: $img"
done
Ловим только картинки, не заморачиваясь с длинными условиями.
case $var in
+([0-9])) echo "Это число" ;;
?(http)://*) echo "Это URL" ;;
*.@(sh|bash)) echo "Это Bash-скрипт" ;;
*) echo "Что-то другое" ;;
esac
Можно строить почти регулярочные проверки, но быстрее и нагляднее.
for f in !(*.log|*.tmp); do
echo "Чистый файл: $f"
done
Берём всё, кроме .log и .tmp.
if [[ $user == +([a-zA-Z0-9._-]) ]]; then
echo "Имя пользователя валидно"
else
echo "Некорректное имя"
fi
Короткая проверка без grep и regex.
BashTex
Please open Telegram to view this post
VIEW IN TELEGRAM
👍11
Bash как оркестратор
Многие используют
▪️ Пример 1. Bash + docker-compose
Скрипт запускает сначала базу, ждет, проверяет ее готовность и только потом запускает приложение. Такой контроль docker-compose сам по себе не умеет.
▪️ Пример 2. Bash + Kubernetes
Тут добавлена проверка статуса роллаута и откат, если приложение не поднялось.
▪️ Пример 3. Управление конфигами и миграциями
Bash может быть связующим звеном:
сначала подгружаем env-настройки,
потом запускаем миграции БД,
и только если они успешны - применяем манифесты.
BashTex📱 #bash
Многие используют
docker-compose или kubectl apply напрямую, но часто в реальной инфраструктуре требуются условные проверки, ветвления, откаты, повторные попытки. И тут bash оказывается неожиданно полезным инструментом.
#!/usr/bin/env bash
set -euo pipefail
echo "[*] Deploying database..."
docker-compose -f compose.db.yml up -d
sleep 10
echo "[*] Checking DB health..."
docker-compose exec db pg_isready -U postgres || {
echo "DB is not ready, aborting!"
exit 1
}
echo "[*] Deploying app..."
docker-compose -f compose.app.yml up -d
Скрипт запускает сначала базу, ждет, проверяет ее готовность и только потом запускает приложение. Такой контроль docker-compose сам по себе не умеет.
#!/usr/bin/env bash
set -euo pipefail
NAMESPACE="staging"
echo "[*] Applying secrets..."
kubectl apply -f secrets.yml -n $NAMESPACE
echo "[*] Deploying app..."
kubectl apply -f app.yml -n $NAMESPACE
echo "[*] Waiting for rollout..."
kubectl rollout status deployment/myapp -n $NAMESPACE --timeout=60s || {
echo "Rollout failed, rolling back..."
kubectl rollout undo deployment/myapp -n $NAMESPACE
}
Тут добавлена проверка статуса роллаута и откат, если приложение не поднялось.
Bash может быть связующим звеном:
сначала подгружаем env-настройки,
потом запускаем миграции БД,
и только если они успешны - применяем манифесты.
source .env
echo "[*] Running migrations..."
docker-compose run --rm app ./manage.py migrate || exit 1
echo "[*] Applying manifests..."
kubectl apply -f k8s/ -n $NAMESPACE
BashTex
Please open Telegram to view this post
VIEW IN TELEGRAM
👍6
Сравнение конфигов на разных серверах через md5sum
▪️ Сравнение директорий по контрольным суммам
Затем можно скопировать файлы на локальный хост и сравнить:
Отличия будут видны построчно: какие файлы отличаются и какие отсутствуют.
▪️ Более гибкий вариант
Хэшируем сразу несколько директорий и добавляем префикс хоста:
Теперь легко фильтровать:
Если файл совпадает на всех хостах - будет одинаковая хэш-сумма.
Если разный - сразу видно у какого сервера отличается.
BashTex📱 #bash #utils
# На сервере A:
find /etc -type f -print0 \
| sort -z \
| xargs -0 md5sum > /tmp/etc-hash.txt
# На сервере B:
find /etc -type f -print0 \
| sort -z \
| xargs -0 md5sum > /tmp/etc-hash.txt
Затем можно скопировать файлы на локальный хост и сравнить:
diff -u serverA/etc-hash.txt serverB/etc-hash.txt
Отличия будут видны построчно: какие файлы отличаются и какие отсутствуют.
Хэшируем сразу несколько директорий и добавляем префикс хоста:
for h in server1 server2; do
ssh $h 'find /etc -type f -print0 \
| sort -z \
| xargs -0 md5sum' \
| sed "s|^|$h |"
done > all-hashes.txt
Теперь легко фильтровать:
awk '{print $2,$1}' all-hashes.txt | sort | uniq -c
Если файл совпадает на всех хостах - будет одинаковая хэш-сумма.
Если разный - сразу видно у какого сервера отличается.
BashTex
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥9
Копирование между хостами без SCP
Сегодня поделюсь с вами альтернативным способом копирования между хостами, когда SCP недоступен или файлы лежат в приватной сети.
▪️ Отправка каталога по SSH
На источнике:
▪️ Копирование через буфер (без SCP и SSH)
На источнике:
Копируем вывод в буфер и затем на приемнике:
Вставляем скопированное содержимое и файл восстановится
Работает даже там, где SCP недоступен.
▪️ Обмен папками между хостами без сети
Через tmux buffer или screen можно передавать данные вообще без файлов и SSH, просто через терминал. Пример в tmux:
На сервере А:
На сервере Б:
🌟 Для больших архивов можно добавить pv и тогда будет видно прогресс:
BashTex📱 #bash #utils
Сегодня поделюсь с вами альтернативным способом копирования между хостами, когда SCP недоступен или файлы лежат в приватной сети.
На источнике:
tar cz dir_to_copy \
| base64 \
| ssh user@remote "base64 -d | tar xz"
dir_to_copy упаковывается в tar.gz, кодируется base64, передается по SSH и на удаленном хосте раскодируется и распаковывается.На источнике:
tar cz myfile.txt | base64
Копируем вывод в буфер и затем на приемнике:
base64 -d | tar xz
Вставляем скопированное содержимое и файл восстановится
Работает даже там, где SCP недоступен.
Через tmux buffer или screen можно передавать данные вообще без файлов и SSH, просто через терминал. Пример в tmux:
На сервере А:
tar cz project | base64 | tmux load-buffer -
На сервере Б:
tmux save-buffer - | base64 -d | tar xz
tar cz bigdir | pv | base64 | ssh host "base64 -d | tar xz"
BashTex
Please open Telegram to view this post
VIEW IN TELEGRAM
👍11🗿1
PROMPT_COMMAND
Недавно на форуме arch linux увидел такую штуку:
при каждом входе в новую директорию автоматически вызывается ls -a.
А все дело в переменной окружения PROMPT_COMMAND - bash выполняет ее перед выводом нового приглашения. Если разобраться, можно превратить её в инструмент автоматизации.
▪️ Базовые сценарии
📍 Автолистинг при смене директории
ls срабатывает только если реально поменяли директорию.
📍 Показ текущей git-ветки
приглашение (PS1) динамически обновляется: добавляет (ветка) только в репозитории.
📍 Отображение количества фоновых задач
рядом с промптом видим, сколько задач выполняется в фоне.
▪️ Продвинутые хаки
📍 Время последней команды
каждый раз в начале промпта вставляется текущее время.
📍 Звуковой сигнал при ошибке
если последняя команда завершилась ошибкой (exit code != 0), терминал пищит.
📍 История + логирование всех команд
история сохраняется сразу (не только при выходе из shell), что полезно при одновременных сессиях.
▪️ Комбо вариант
- при смене директории автоматом делается ls
- в промпте видно количество фоновых задач
- в git-репозитории показывается текущая ветка
BashTex📱 #bash #utils
Недавно на форуме arch linux увидел такую штуку:
export PROMPT_COMMAND='[[ $curdir != $PWD ]] && ls -a; curdir=$PWD'
при каждом входе в новую директорию автоматически вызывается ls -a.
А все дело в переменной окружения PROMPT_COMMAND - bash выполняет ее перед выводом нового приглашения. Если разобраться, можно превратить её в инструмент автоматизации.
PROMPT_COMMAND='[[ $lastdir != $PWD ]] && ls; lastdir=$PWD'
ls срабатывает только если реально поменяли директорию.
PROMPT_COMMAND='branch=$(git rev-parse --abbrev-ref HEAD 2>/dev/null); PS1="[\u@\h \W${branch:+ ($branch)}]\\$ "'
приглашение (PS1) динамически обновляется: добавляет (ветка) только в репозитории.
PROMPT_COMMAND='jobs_count=$(jobs -p | wc -l); PS1="[jobs:$jobs_count] \u@\h:\w\$ "'
рядом с промптом видим, сколько задач выполняется в фоне.
PROMPT_COMMAND='PS1="[\t] \u@\h:\w\$ "'
каждый раз в начале промпта вставляется текущее время.
PROMPT_COMMAND='[ $? -ne 0 ] && echo -ne "\a"'
если последняя команда завершилась ошибкой (exit code != 0), терминал пищит.
PROMPT_COMMAND='history -a; history -c; history -r'
история сохраняется сразу (не только при выходе из shell), что полезно при одновременных сессиях.
PROMPT_COMMAND='
[[ $lastdir != $PWD ]] && ls; lastdir=$PWD;
branch=$(git rev-parse --abbrev-ref HEAD 2>/dev/null);
jobs_count=$(jobs -p | wc -l);
PS1="[jobs:$jobs_count ${branch:+git:$branch}] \u@\h:\w\$ "
'
- при смене директории автоматом делается ls
- в промпте видно количество фоновых задач
- в git-репозитории показывается текущая ветка
BashTex
Please open Telegram to view this post
VIEW IN TELEGRAM
👍10
Cтатистика по сетевым соединениям
1️⃣ Быстрая сводка: сколько вообще соединений. Используем ss - он современнее netstat и есть почти везде.
Пример вывода:
2️⃣ Топ IP по количеству соединений (TCP). Самый распространенный прием - взять список активных соединений и посчитать, какие удаленные IP встречаются чаще всего.
ss -tn state established - TCP, только установленные соединения;
$5 в выводе ss - удалённый адрес (remote:port); sed убирает порт;
uniq -c | sort -nr - считаем и сортируем по убыванию.
Пример вывода:
3️⃣ Топ удаленных портов (куда подключаются клиенты). Иногда важно понять, на какие порты чаще всего идут входящие соединения:
Здесь мы берем локальный адрес $4 и вычленяем порт.
Пример вывода:
4️⃣ Показать top IP + процесс (PID). Хочется не только IP, но и процесс, который держит соединения:
ss -tnp выводит PID/program в конце строки, мы парсим это и группируем.
Пример вывода:
Интерпретация: 203.0.113.42 имеет 50 соединений, принадлежащих процессу с PID 2345.
5️⃣ Однострочник: показать какие процессы создают больше всего соединений. Если нужно быстро понять, какие программы держат трафик:
Результат - счетчик + PID + имя процесса.
6️⃣ Оценка трафика (байты) - через iptables счётчики. ss не всегда дает удобную сумму байт на IP. Для оценки трафика по IP удобно использовать iptables счетчики (если вы можете изменять правила). Создадим правило для счёта байт/пакетов входящего/исходящего трафика:
Вывод покажет количество пакетов/байтов для каждого правила - можно парсить и сохранять в CSV.
7️⃣ Несколько полезных примеров про запас
Топ слушающих сокетов (портов на сервере):
Топ активных UDP-адресов:
Полный список соединений с PID и программой:
BashTex📱 #bash
ss -s
Пример вывода:
Total: 1024 (kernel 2048)
TCP: 512 (estab 200, closed 10, orphaned 0, synrecv 0, timewait 55/0), ports 0
UDP: 256
RAW: 0
ss -tn state established '( dport != :22 )' \
| awk '{print $5}' \
| sed 's/:[0-9]*$//' \
| sort \
| uniq -c \
| sort -nr \
| head -n 20
ss -tn state established - TCP, только установленные соединения;
$5 в выводе ss - удалённый адрес (remote:port); sed убирает порт;
uniq -c | sort -nr - считаем и сортируем по убыванию.
Пример вывода:
120 203.0.113.42
87 198.51.100.10
45 10.0.0.5
...
ss -tn state established \
| awk '{print $4}' \
| sed 's/.*://; s/::ffff://g' \
| sort \
| uniq -c \
| sort -nr \
| head -n 20
Здесь мы берем локальный адрес $4 и вычленяем порт.
Пример вывода:
340 443
200 80
50 22
...
ss -tnp | awk '/ESTAB/ { split($6, a, ":"); ip=a[1]; match($0, /pid=[0-9]+,/, m); pid=(m[0]=="")? "?" : substr(m[0],5,length(m[0])-5); print ip, pid }' \
| sort | uniq -c | sort -nr | head -n 30
ss -tnp выводит PID/program в конце строки, мы парсим это и группируем.
Пример вывода:
50 203.0.113.42 2345
30 198.51.100.10 5012
...
Интерпретация: 203.0.113.42 имеет 50 соединений, принадлежащих процессу с PID 2345.
ss -tnp | awk '/ESTAB/ { if (match($0, /pid=([0-9]+),/ , m)) { pid=m[1]; sub(/^.*pid=[0-9]+, /,""); proc=$0; split(proc,p," "); print pid, p[1] } }' \
| sort | uniq -c | sort -nr | head -n 20
Результат - счетчик + PID + имя процесса.
# создать chain для мониторинга (однократно)
sudo iptables -N MONITOR 2>/dev/null || true
sudo iptables -I INPUT -j MONITOR
sudo iptables -I OUTPUT -j MONITOR
# добавить правило для конкретного IP (пример)
sudo iptables -I MONITOR -s 203.0.113.42 -j ACCEPT
# посмотреть счётчики
sudo iptables -L MONITOR -vn --line-numbers
Вывод покажет количество пакетов/байтов для каждого правила - можно парсить и сохранять в CSV.
Топ слушающих сокетов (портов на сервере):
ss -lnpt | awk '{print $4}' | sed 's/.*://; /^[[:space:]]*$/d' | sort | uniq -c | sort -nr
Топ активных UDP-адресов:
ss -un | awk '{print $5}' | sed 's/:[0-9]*$//' | sort | uniq -c | sort -nr | head
Полный список соединений с PID и программой:
ss -tnp | sed 's/ */ /g' | awk '{print $1, $4, $5, $6}'
BashTex
Please open Telegram to view this post
VIEW IN TELEGRAM
👍7🔥2
Как выполнить команду без следов
Многие знают, что bash сохраняет введенные команды в ~/.bash_history. Но иногда, когда требуется ввести пароль в команде или использовать одноразовый токен, хочется, чтобы этого не было в истории.
📌 Решение: начни команду с пробела. Если в .bashrc (или глобально в /etc/bash.bashrc) включена опция:
то любая команда, набранная с пробелом в начале, не попадет в историю.
▪️ Пример:
▪️ Полезные комбинации с HISTCONTROL
ignoredups - не сохранять дубликаты подряд.
ignorespace - не сохранять команды, начинающиеся с пробела.
Можно управлять этим через
BashTex📱 #bash
Многие знают, что bash сохраняет введенные команды в ~/.bash_history. Но иногда, когда требуется ввести пароль в команде или использовать одноразовый токен, хочется, чтобы этого не было в истории.
HISTCONTROL=ignoreboth
то любая команда, набранная с пробелом в начале, не попадет в историю.
# обычная команда - сохраняется в истории
echo "hello"
# команда с пробелом - не будет в history
echo "secret run"
ignoredups - не сохранять дубликаты подряд.
ignorespace - не сохранять команды, начинающиеся с пробела.
Можно управлять этим через
.bashrc:
export HISTCONTROL=ignoreboth
export HISTSIZE=5000
export HISTFILESIZE=10000
BashTex
Please open Telegram to view this post
VIEW IN TELEGRAM
👍6🔥3
Компактный контроль фоновых процессов
При работе с фоновыми процессами в скриптах часто возникает два вопроса:
1. Как реагировать на завершение первого из них, не дожидаясь всех?
2. Как корректно снять всю пачку, если что-то пошло не так?
В Bash на этот случай имеется удобный механизм:
▪️ Проблема: несколько фоновых задач. Допустим, мы запускаем несколько параллельных процессов:
Обычно wait ждет завершения всех процессов. Но что если нужно отреагировать на первый фейл или отменить остальных после успешной задачи?
Решение: wait -n. Команда
▪️ Kill group: гасим всю пачку. У kill есть прием: если передать отрицательный PID, сигнал уйдет всей группе процессов.
Это удобно, если внутри группы запускаются дочерние процессы, которые обычный
▪️ Комбо. Представим сценарий: несколько загрузчиков качают один и тот же файл из разных зеркал. Как только первый скачал успешно - убиваем остальных.
BashTex📱 #bash #utils
При работе с фоновыми процессами в скриптах часто возникает два вопроса:
1. Как реагировать на завершение первого из них, не дожидаясь всех?
2. Как корректно снять всю пачку, если что-то пошло не так?
В Bash на этот случай имеется удобный механизм:
wait -n и управление группами процессов.
#!/bin/bash
long_task_1 &
long_task_2 &
long_task_3 &
Обычно wait ждет завершения всех процессов. Но что если нужно отреагировать на первый фейл или отменить остальных после успешной задачи?
Решение: wait -n. Команда
wait -n ждет завершения любого из фоновых процессов и возвращает его код выхода. Как пример: запускаем 3 задачи, как только одна успешно завершилась - гасим остальные:
#!/bin/bash
cmd1 & p1=$!
cmd2 & p2=$!
cmd3 & p3=$!
while true; do
if wait -n; then
echo "Одна из задач завершилась успешно, убиваем остальные..."
kill $p1 $p2 $p3 2>/dev/null
break
fi
done
# запустить группу
( cmd1 & cmd2 & cmd3 & wait ) &
# сохранить PID группы
PGID=$!
# ... позже убить всех одним движением:
kill -- -$PGID
Это удобно, если внутри группы запускаются дочерние процессы, которые обычный
kill $PID не заденет.
#!/bin/bash
(
curl -s -O http://mirror1/file.iso &
curl -s -O http://mirror2/file.iso &
curl -s -O http://mirror3/file.iso &
wait -n # ждем первый завершившийся процесс
kill -- -$$ # убиваем всю группу (все ещё живые curl)
) &
wait
echo "Файл получен!"
BashTex
Please open Telegram to view this post
VIEW IN TELEGRAM
👍6
Мониторинг логов с реакцией
Если вы не хотите заморачиваться с созданием триггера, а нужно просто отловить ошибку один раз и забыть про нее, то есть вариант сделать это только с помощью bash.
1️⃣ Реакция на ошибку в логах
Каждая новая строка с error вызывает уведомление на рабочем столе.
2️⃣ Отправка алертов на почту
По событию будет отправлено письмо на почту.
3️⃣ Подсчет в реальном времени
Видно, какие IP чаще всего обращаются к серверу.
BashTex📱 #bash
Если вы не хотите заморачиваться с созданием триггера, а нужно просто отловить ошибку один раз и забыть про нее, то есть вариант сделать это только с помощью bash.
tail -f /var/log/syslog | grep --line-buffered "error" | while read line; do
notify-send "Ошибка в логах!" "$line"
done
Каждая новая строка с error вызывает уведомление на рабочем столе.
tail -f /var/log/nginx/error.log | grep --line-buffered "timeout" | while read line; do
echo "$line" | mail -s "Alert Alert! Help!!!" admin@bashtex.com
done
По событию будет отправлено письмо на почту.
tail -f /var/log/nginx/access.log | awk '{print $1}' | sort | uniq -c
Видно, какие IP чаще всего обращаются к серверу.
BashTex
Please open Telegram to view this post
VIEW IN TELEGRAM
👍6
Автообновление python-зависимостей в виртуальных окружениях
В проектах на python у меня неоднократно возникала ситуация: виртуальное окружение развернуто давно, а пакеты внутри уже устарели.
Чтобы не держать руками список и не забывать про обновления, можно автоматизировать этот процесс.
1️⃣ Получение списка пакетов. В активированном окружении
2️⃣ Скрипт автообновления. Скрипт ищет все устаревшие пакеты и обновляет их до последней версии:
3️⃣ Массовое обновление для проектов. Если проектов несколько, можно пройтись по каталогам и обновить все:
4️⃣ Фиксируем версии. Перед обновлением полезно сохранить текущие версии, чтобы в случае проблем легко откатиться:
BashTex📱 #bash #utils
В проектах на python у меня неоднократно возникала ситуация: виртуальное окружение развернуто давно, а пакеты внутри уже устарели.
Чтобы не держать руками список и не забывать про обновления, можно автоматизировать этот процесс.
pip list --outdated --format=freeze вернет пакеты, которые можно обновить:
source venv/bin/activate
pip list --outdated --format=freeze
#!/bin/bash
set -euo pipefail
VENV_PATH="./venv"
source "$VENV_PATH/bin/activate"
echo "Проверяем устаревшие пакеты..."
outdated=$(pip list --outdated --format=freeze | cut -d= -f1)
if [[ -z "$outdated" ]]; then
echo "Все пакеты актуальны"
exit 0
fi
echo "Обновляем пакеты:"
for pkg in $outdated; do
echo " - $pkg"
pip install -U "$pkg"
done
#!/bin/bash
for dir in ~/projects/*; do
if [[ -d "$dir/venv" ]]; then
echo "=== $dir ==="
source "$dir/venv/bin/activate"
pip list --outdated --format=freeze | cut -d= -f1 | xargs -n1 pip install -U
deactivate
fi
done
pip freeze > requirements-$(date +%F).txt
BashTex
Please open Telegram to view this post
VIEW IN TELEGRAM
👍7
Создание резервных копий БД с шифрованием
Правильный подход к шифрованию БД, чтобы не оставлять персональные данные открытыми это: создать дамп → зашифровать его GPG → сохранить/отправить.
▪️ Подготовка GPG. На сервере, который будет создавать бэкапы, импортируйте публичный ключ:
Проверьте наличие ключа:
Использовать будем шифрование public key → private key, так что дешифровать можно только на безопасном ПК/сервере.
🛠 Скрипт универсального бэкапа PostgreSQL/MySQL с шифрованием GPG
▪️ Запуск
PostgreSQL:
MySQL/MariaDB:
▪️ Как восстановить?
PostgreSQL:
MySQL:
BashTex📱 #bash #utils
Правильный подход к шифрованию БД, чтобы не оставлять персональные данные открытыми это: создать дамп → зашифровать его GPG → сохранить/отправить.
gpg --import backup_public.key
Проверьте наличие ключа:
gpg --list-keys
Использовать будем шифрование public key → private key, так что дешифровать можно только на безопасном ПК/сервере.
#!/usr/bin/env bash
set -euo pipefail
# === Настройки ===
BACKUP_DIR="/var/backups/db"
RETENTION_DAYS=7
GPG_RECIPIENT="backup@bashtex.com" # email из публичного ключа
TS=$(date +"%F_%H-%M-%S")
DB_TYPE="$1" # postgres | mysql
DB_NAME="$2" # имя базы
DB_USER="$3" # пользователь
mkdir -p "$BACKUP_DIR"
DUMP_FILE="$BACKUP_DIR/${DB_TYPE}_${DB_NAME}_${TS}.sql"
ENC_FILE="$DUMP_FILE.gpg"
LOG_FILE="$BACKUP_DIR/backup.log"
# === Логгер ===
log() {
echo "[$(date +%F_%T)] $*" | tee -a "$LOG_FILE"
}
log "=== Старт резервного копирования $DB_TYPE:$DB_NAME ==="
# === Дамп базы ===
case "$DB_TYPE" in
postgres)
log "Выполняю pg_dump…"
pg_dump -U "$DB_USER" "$DB_NAME" > "$DUMP_FILE"
;;
mysql)
log "Выполняю mysqldump…"
mysqldump -u "$DB_USER" "$DB_NAME" > "$DUMP_FILE"
;;
*)
log "Неизвестный тип БД: $DB_TYPE"
exit 1
;;
esac
log "Дамп создан: $DUMP_FILE"
# === Шифрование GPG ===
log "Шифрую дамп для получателя: $GPG_RECIPIENT"
gpg --yes --encrypt --recipient "$GPG_RECIPIENT" "$DUMP_FILE"
log "Шифрованный файл: $ENC_FILE"
# === Удаление оригинального дампа ===
rm -f "$DUMP_FILE"
log "Оригинальный файл удалён"
# === Очистка старых бэкапов ===
log "Удаление файлов старше $RETENTION_DAYS дней"
find "$BACKUP_DIR" -type f -name "*.gpg" -mtime "+$RETENTION_DAYS" -delete
log "Готово"
Скрипт:
делает бэкап PostgreSQL или MySQL;
шифрует его GPG-ключом;
сохраняет с датой;
удаляет старые копии.
логирует все происходящее
PostgreSQL:
./backup.sh postgres mydb dbuser
MySQL/MariaDB:
./backup.sh mysql mydb root
gpg -d backup_postgres_mydb_2025-11-01_03-00-00.sql.gpg > restore.sql
PostgreSQL:
psql -U postgres mydb < restore.sql
MySQL:
mysql -u root mydb < restore.sql
BashTex
Please open Telegram to view this post
VIEW IN TELEGRAM
👍7
Использование printf -v для динамического формирования переменных
Бывает, что нужно создавать или заполнять переменные с динамическими именами: item_1, item_2, user_alice и т.д. Вместо eval попробуем использовать встроенный printf -v, который позволяет записать форматированный строковый результат прямо в переменную по имени.
▪️ Базовый пример: простая запись
Здесь мы записали строку в переменную user_alice без eval.
▪️ Нумерация с форматированием (zero-padding)
▪️ Создаем набор конфигов из CSV (пример)
▪️ Инспекция созданных переменных
(indirect expansion) позволяет получить значение переменной по имени.
▪️ Комбинация с declare -n (nameref)
declare -n дает ссылку на переменную, это удобнее, чем постоянно подставлять ${!name}.
▪️ Как избежать проблем с именами переменных. Переменная должна соответствовать правилам имени в bash. Можно валидировать:
Если допустить в имени пробелы или символы ; - printf -v либо даст ошибку, либо поведет себя непредсказуемо.
BashTex📱 #bash #utils
Бывает, что нужно создавать или заполнять переменные с динамическими именами: item_1, item_2, user_alice и т.д. Вместо eval попробуем использовать встроенный printf -v, который позволяет записать форматированный строковый результат прямо в переменную по имени.
Почему printf -v лучше, чем eval?
безопаснее (без спаивания и выполнения произвольного кода),
дает форматирование (числа, нули, float),
удобно в функциях (можно создавать глобальные переменные через declare -g).
name="user_alice"
printf -v "$name" "%s" "id=42"
echo "$user_alice"
id=42
Здесь мы записали строку в переменную user_alice без eval.
for i in {1..5}; do
printf -v "item_%03d" "$i" "val_$i"
done
# показать созданные переменные
for i in {1..5}; do
var="item_$(printf '%03d' "$i")"
echo "$var = ${!var}"
done
item_001 = val_1
item_002 = val_2
printf -v "item_%03d" "$i" "val" - формат имени и значение удобно комбинировать.
while IFS=, read -r key value; do
# нормализовать имя (латиница, цифры, _)
safe_key=$(echo "$key" | tr -c '[:alnum:]_' '_')
printf -v "cfg_%s" "$safe_key" "%s" "$value"
done <<'CSV'
db.host,localhost
db.port,5432
feature.enable,true
CSV
echo "Host: $cfg_db_host"
echo "Port: $cfg_db_port"
echo "Feature: $cfg_feature_enable"
# показать все переменные с префиксом cfg_
for v in ${!cfg_*}; do
echo "$v = ${!v}"
done
(indirect expansion) позволяет получить значение переменной по имени.
printf -v "user_alice" "%s" "alice@bashtex.com"
ref_var="user_alice"
declare -n userref="$ref_var"
echo "Email via nameref: $userref"
declare -n дает ссылку на переменную, это удобнее, чем постоянно подставлять ${!name}.
sanitize() {
printf '%s' "$1" | sed 's/[^a-zA-Z0-9_]/_/g'
}
raw="user-name@host"
safe=$(sanitize "$raw")
printf -v "$safe" "%s" "value"
Если допустить в имени пробелы или символы ; - printf -v либо даст ошибку, либо поведет себя непредсказуемо.
BashTex
Please open Telegram to view this post
VIEW IN TELEGRAM
👍4
Контроль размера логов и архивация быстро растущих файлов
Неловко будет если кто-то включил дебаг, забыл про него и оставил так, даже на выходные. Покажу скрипт который будет: мониторить размер файлов в директории логов, сравнивать с предыдущим значением, при подозрительном росте - архивировать лог и обнулять его, сохраняя копию.
🛠 Скрипт
▪️ Пояснение:
хранит состояние предыдущих размеров в log_sizes.db,
если файл вырос больше порога, делает копию с timestamp, архивирует и очищает,
работает для всех *.log в выбранной директории.
▪️ Применение. Добавь в cron для регулярной проверки:
BashTex📱 #bash
Неловко будет если кто-то включил дебаг, забыл про него и оставил так, даже на выходные. Покажу скрипт который будет: мониторить размер файлов в директории логов, сравнивать с предыдущим значением, при подозрительном росте - архивировать лог и обнулять его, сохраняя копию.
#!/usr/bin/env bash
set -euo pipefail
LOG_DIR="/var/log/myapp"
STATE_FILE="/tmp/log_sizes.db"
THRESHOLD_MB=50 # если лог вырос более чем на 50 MB - архивируем
ARCHIVE_DIR="/var/log/archive"
mkdir -p "$ARCHIVE_DIR"
touch "$STATE_FILE"
# читаем прошлые размеры
declare -A old_sizes
while IFS=":" read -r file size; do
old_sizes["$file"]="$size"
done < "$STATE_FILE"
> "$STATE_FILE" # перезапишем свежим состоянием
for log in "$LOG_DIR"/*.log; do
[[ -f "$log" ]] || continue
size=$(stat -c %s "$log")
echo "$log:$size" >> "$STATE_FILE"
old_size=${old_sizes["$log"]:-0}
diff=$(( (size - old_size) / 1024 / 1024 ))
if (( diff > THRESHOLD_MB )); then
ts=$(date +%F_%H-%M-%S)
archive="$ARCHIVE_DIR/$(basename "$log").$ts.gz"
echo "$log вырос на ${diff}MB - архивируем в $archive"
cp "$log" "$archive"
gzip "$archive"
: > "$log" # обнуляем лог
fi
done
хранит состояние предыдущих размеров в log_sizes.db,
если файл вырос больше порога, делает копию с timestamp, архивирует и очищает,
работает для всех *.log в выбранной директории.
*/10 * * * * /usr/local/bin/log_watch.sh
BashTex
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2