Please open Telegram to view this post
VIEW IN TELEGRAM
😁8👨💻1
Компактный контроль фоновых процессов
При работе с фоновыми процессами в скриптах часто возникает два вопроса:
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 https://mirror1/file.iso &
curl -s -O https://mirror2/file.iso &
curl -s -O https://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!!!" [email protected]
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
👍7
Автообновление 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="[email protected]" # 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
👍9
Использование 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" "[email protected]"
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
👍6
Контроль размера логов и архивация быстро растущих файлов
Неловко будет если кто-то включил дебаг, забыл про него и оставил так, даже на выходные. Покажу скрипт который будет: мониторить размер файлов в директории логов, сравнивать с предыдущим значением, при подозрительном росте - архивировать лог и обнулять его, сохраняя копию.
🛠 Скрипт
▪️ Пояснение:
хранит состояние предыдущих размеров в 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
👍4
Please open Telegram to view this post
VIEW IN TELEGRAM
😁16👍1
Оптимизация скриптов: меньше внешних команд - выше скорость
Одна из самых недооцененных идей - это избегать лишних вызовов внешних утилит. Каждый grep, sed, awk, cut, wc - это новый процесс. Даже если он работает миллисекунды, то в большом цикле таких вызовов может быть сотни, и производительность падает кратно.
▪️ Примеры оптимизации
➖ Было:
➕ Стало:
или вообще без внешних утилит:
➖ Было:
➕ Стало:
расширение строк - это встроенная возможность bash, без cut
➖ Было:
➕ Стало:
не вызываем ls, здесь glob уже делает свою работу
▪️ Фичи встроенного bash
BashTex📱 #bash
Одна из самых недооцененных идей - это избегать лишних вызовов внешних утилит. Каждый grep, sed, awk, cut, wc - это новый процесс. Даже если он работает миллисекунды, то в большом цикле таких вызовов может быть сотни, и производительность падает кратно.
count=$(grep "error" logfile | wc -l)
count=$(awk '/error/{c++} END{print c}' logfile)
или вообще без внешних утилит:
count=0
while IFS= read -r line; do
[[ $line == *error* ]] && ((count++))
done < logfile
name=$(echo "$filename" | cut -d. -f1)
name=${filename%%.*}
расширение строк - это встроенная возможность bash, без cut
for f in $(ls *.log); do
grep "fatal" "$f" >/dev/null && echo "$f"
done
for f in *.log; do
grep -q "fatal" "$f" && echo "$f"
done
не вызываем ls, здесь glob уже делает свою работу
${var,,} и ${var^^}- lowercase / uppercase без tr${var//foo/bar}- замена без sed${#array[@]}- длина массива без wc[[ $var == pattern* ]]- шаблоны без grep((expr))- арифметика без expr или bcmapfile -t lines < file- чтение файлов без cat
BashTex
Please open Telegram to view this post
VIEW IN TELEGRAM
👍9
Кто тормозит твой скрипт
Когда скрипт вырастает, то понять, где он тратит время, становится сложно. Можно, конечно, запускать
А если внутри десятки функций, то какая из них проседает? Решается просто: встроенным профилированием на чистом bash.
▪️ Идея
Оборачиваем каждую функцию в таймер, считаем суммарное время вызовов и количество запусков.
В конце - печатаем красивые итоги.
🛠 Минимальный пример
▪️ Пример вывода:
▪️ Как это работает
Каждая реальная функция хранится как __name_impl.
Обернутый вариант считает время (date +%s.%N) до и после вызова.
Сохраняет статистику в ассоциативных массивах.
BashTex📱 #bash
Когда скрипт вырастает, то понять, где он тратит время, становится сложно. Можно, конечно, запускать
time ./script.sh, но это покажет лишь общую длительность.А если внутри десятки функций, то какая из них проседает? Решается просто: встроенным профилированием на чистом bash.
Оборачиваем каждую функцию в таймер, считаем суммарное время вызовов и количество запусков.
В конце - печатаем красивые итоги.
#!/usr/bin/env bash
declare -A FN_CALLS FN_TIME
# Обертка для функции
trace() {
local fn=$1
eval "
$fn() {
local start=\$(date +%s.%N)
__${fn}_impl \"\$@\"
local end=\$(date +%s.%N)
local delta=\$(awk -v s=\$start -v e=\$end 'BEGIN{print e-s}')
((FN_CALLS[$fn]++))
FN_TIME[$fn]=\$(awk -v a=\${FN_TIME[$fn]:-0} -v d=\$delta 'BEGIN{print a+d}')
}
"
}
# Пример функций
__work_impl() { sleep 0.2; }
__fast_impl() { echo ok >/dev/null; }
trace work
trace fast
# Нагрузочный сценарий
for i in {1..5}; do
work
fast
done
# Вывод статистики
echo -e "\nFunction Stats:"
for fn in "${!FN_CALLS[@]}"; do
avg=$(awk -v t=${FN_TIME[$fn]} -v c=${FN_CALLS[$fn]} 'BEGIN{printf "%.4f", t/c}')
printf "%-10s calls=%-3s total=%.4fs avg=%.4fs\n" "$fn" "${FN_CALLS[$fn]}" "${FN_TIME[$fn]}" "$avg"
done
Function Stats:
work calls=5 total=1.0012s avg=0.2002s
fast calls=5 total=0.0003s avg=0.0001s
Каждая реальная функция хранится как __name_impl.
Обернутый вариант считает время (date +%s.%N) до и после вызова.
Сохраняет статистику в ассоциативных массивах.
BashTex
Please open Telegram to view this post
VIEW IN TELEGRAM
👍9
Условное выполнение: && vs ; vs || vs !
У bash есть несколько операторов, которые управляют выполнением команд в зависимости от их кода возврата (0 - успех, ≠0 - ошибка). Разница кажется простой, но на практике часто приводит к тому, что люди не совсем понимают, что они используют.
▪️ ; - просто последовательность. Команды выполняются всегда, независимо от успеха/ошибки.
Используется, когда важно выполнить все подряд.
▪️ && - только при успехе. Выполнится вторая команда, если первая завершилась успешно (exit code 0).
Если mkdir упадет (например, папка уже есть), то echo не выполнится.
Полезно для цепочек успеха:
▪️ || - только при ошибке. Выполнится, если первая команда завершилась с ошибкой.
Удобно для fallback-сценариев.
▪️ Комбинация && … || … Классический паттерн if-else в одну строку:
Подводный камень: если echo "OK" упадет, то выполнится и "FAIL".
Безопасный вариант - скобки:
▪️ ! - инверсия результата. Меняет код возврата команды на противоположный.
Часто встречается в условиях:
BashTex📱 #bash #utils
У bash есть несколько операторов, которые управляют выполнением команд в зависимости от их кода возврата (0 - успех, ≠0 - ошибка). Разница кажется простой, но на практике часто приводит к тому, что люди не совсем понимают, что они используют.
echo "start"; false; echo "end"
# => выведет start, потом end, даже если false упала
Используется, когда важно выполнить все подряд.
mkdir data && echo "Папка создана"
Если mkdir упадет (например, папка уже есть), то echo не выполнится.
Полезно для цепочек успеха:
make build && make test && make deploy
ping -c1 host || echo "Хост недоступен"
Удобно для fallback-сценариев.
command && echo "OK" || echo "FAIL"
Подводный камень: если echo "OK" упадет, то выполнится и "FAIL".
Безопасный вариант - скобки:
command && { echo "OK"; } || { echo "FAIL"; }
! true # вернет 1
! false # вернет 0
Часто встречается в условиях:
if ! grep -q "needle" file.txt; then
echo "Нет совпадений"
fi
Результаты:
; → всегда выполнить
&& → выполнить при успехе
|| → выполнить при ошибке
! → инвертировать результат
&& … || … → if-else, но лучше со скобками для надежности
BashTex
Please open Telegram to view this post
VIEW IN TELEGRAM
👍16
Меньше if и больше логики в одну строку
Когда вы пишете:
В мире плачет один котенок…🥺 Ведь есть куда более лаконичная альтернатива:
Это логические цепочки (&& и ||), которые позволяют писать коротко и эффективно.
▪️ Принцип short-circuit логики
Bash, как и многие языки, останавливает выполнение цепочки, если уже ясно, что дальше не нужно:
cmd1 && cmd2 - cmd2 выполняется только если cmd1 завершилась успешно (exit 0).
cmd1 || cmd2 - cmd2 выполняется только если cmd1 завершилась с ошибкой (exit ≠ 0).
▪️ Примеры таких условий
📍 Минимизация if:
создаст и сразу перейдёт, только если каталог успешно создан.
📍 Команда с fallback:
Если cp не удался - сработает echo.
📍 Успех или откат:
Если echo "Успешно" завершится с ошибкой (что редко, но возможно), то сработает и ||.
Чтобы избежать этого, оберните в скобки:
📍 Цепочка нескольких условий:
Проверяем наличие каталога
Если есть - заходим и выводим содержимое
Если нет - сообщение
📍 Проверка переменной:
▪️ Более сложные варианты использования
📍 Комбинирование условий:
📍 Логическая цепочка до первой ошибки:
📍 Псевдо-транзакции:
Если что-то пошло не так - rollback.
BashTex📱 #bash #utils
Когда вы пишете:
if command; then
echo "OK"
else
echo "Fail"
fi
В мире плачет один котенок…
command && echo "OK" || echo "Fail"
Это логические цепочки (&& и ||), которые позволяют писать коротко и эффективно.
Bash, как и многие языки, останавливает выполнение цепочки, если уже ясно, что дальше не нужно:
cmd1 && cmd2 - cmd2 выполняется только если cmd1 завершилась успешно (exit 0).
cmd1 || cmd2 - cmd2 выполняется только если cmd1 завершилась с ошибкой (exit ≠ 0).
mkdir newdir && cd newdir
создаст и сразу перейдёт, только если каталог успешно создан.
cp file.txt backup/ || echo "Не удалось скопировать!"
Если cp не удался - сработает echo.
make build && echo "Успешно" || echo "Ошибка"
Если echo "Успешно" завершится с ошибкой (что редко, но возможно), то сработает и ||.
Чтобы избежать этого, оберните в скобки:
make build && { echo "Успешно"; } || { echo "Ошибка"; }
[ -d logs ] && cd logs && ls || echo "Нет каталога logs"
Проверяем наличие каталога
Если есть - заходим и выводим содержимое
Если нет - сообщение
[[ -n $API_KEY ]] && echo "Ключ найден" || echo "Нет ключа"
[[ -f $file && -s $file ]] && echo "Файл существует и не пуст"
do_step1 && do_step2 && do_step3 || echo "Ошибка на одном из этапов"
backup && update && restart || rollback
Если что-то пошло не так - rollback.
BashTex
Please open Telegram to view this post
VIEW IN TELEGRAM
👍14
Please open Telegram to view this post
VIEW IN TELEGRAM
😁22👨💻1
Автоматическое масштабирование ресурсов контейнеров
Казалось бы, нетривиальная задача - нужно, чтобы контейнер автоматически подстраивался под нагрузку: добавлял CPU/memory, когда процесс задыхается, и освобождал ресурсы при простое. Все это без kubernetes, без swarm, просто bash + docker API. Попробуем реализовать.
🛠 Пример скрипта (называться пусть будет
BashTex📱 #bash
Казалось бы, нетривиальная задача - нужно, чтобы контейнер автоматически подстраивался под нагрузку: добавлял CPU/memory, когда процесс задыхается, и освобождал ресурсы при простое. Все это без kubernetes, без swarm, просто bash + docker API. Попробуем реализовать.
▪️ План скрипта:
Скрипт опрашиваетdocker stats --no-stream
Сравнивает текущую загрузку CPU/памяти с порогами
При превышении - обновляет лимиты контейнера черезdocker update
При простое - возвращает лимиты назад
Все это циклически, с логированием и защитой от дребезга (частых переключений)
auto-scale.sh)
#!/usr/bin/env bash
CONTAINER="webapp"
CPU_MAX=200000 # 200% CPU
CPU_MIN=50000 # 50% CPU
MEM_MAX="1g"
MEM_MIN="256m"
LOGFILE="/var/log/docker_autoscale.log"
INTERVAL=30
STABILITY=3 # сколько циклов подряд должна держаться нагрузка
cpu_high=0
cpu_low=0
log() { echo "$(date '+%F %T') $*" >> "$LOGFILE"; }
while true; do
read cpu mem <<<$(docker stats --no-stream --format "{{.CPUPerc}} {{.MemUsage}}" "$CONTAINER" \
| awk -F'[ %/]' '{printf "%d %d", $1, $2}')
if (( cpu > 80 )); then
((cpu_high++))
cpu_low=0
elif (( cpu < 30 )); then
((cpu_low++))
cpu_high=0
fi
if (( cpu_high >= STABILITY )); then
log "High load detected ($cpu%). Increasing limits..."
docker update --cpus="2.0" --memory="$MEM_MAX" "$CONTAINER" >/dev/null
cpu_high=0
elif (( cpu_low >= STABILITY )); then
log "Low load detected ($cpu%). Decreasing limits..."
docker update --cpus="0.5" --memory="$MEM_MIN" "$CONTAINER" >/dev/null
cpu_low=0
fi
sleep "$INTERVAL"
done
BashTex
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥8
Генерация таблиц прогресса
Реализация живой таблицы прогресса выполнения сприпта, обновляемой прямо в терминале - без внешних тулз, только
🛠 Скрипт:
▪️ Пояснения:
BashTex📱 #bash #utils
Реализация живой таблицы прогресса выполнения сприпта, обновляемой прямо в терминале - без внешних тулз, только
tput, trap. Звучит заманчиво.
#!/usr/bin/env bash
tasks=("Backup" "Sync configs" "Rebuild cache" "Restart services" "Cleanup")
total=${#tasks[@]}
done=0
# Убираем курсор
tput civis
# При выходе вернуть курсор
trap 'tput cnorm; echo' EXIT
# Заголовок таблицы
printf "%-20s | %-10s | %-10s\n" "Task" "Status" "Progress"
printf -- "---------------------------------------------\n"
# Печатаем пустые строки под таблицу
for ((i=0; i<total; i++)); do
printf "%-20s | %-10s | %-10s\n" "${tasks[i]}" "Pending" "0%"
done
# Запоминаем позицию курсора для обновлений
start_row=$(tput lines)
start_row=$((start_row - total))
# Функция обновления строки
update_row() {
local idx=$1
local status=$2
local progress=$3
tput cup $((start_row + idx)) 0
printf "%-20s | %-10s | %-10s\n" "${tasks[idx]}" "$status" "$progress"
}
# Основной цикл выполнения
for i in "${!tasks[@]}"; do
for p in {10..100..10}; do
update_row "$i" "Running" "${p}%"
sleep 0.1
done
update_row "$i" "Done" "100%"
done
tput cnorm
echo -e "\nВсе задачи завершены!"
tput civis / cnorm - скрывает и возвращает курсор.tput cup row col - перемещает курсор для перерисовки конкретной строки.trap '...' EXIT - гарантирует возврат нормального состояния терминала даже при Ctrl+C.printf с фиксированной шириной (%-20s) создает выровненную таблицу.BashTex
Please open Telegram to view this post
VIEW IN TELEGRAM
👍4
Please open Telegram to view this post
VIEW IN TELEGRAM
😁13🔥3🫡3
Создание песочницы для экспериментов
Иногда нужно проверить какую-то команду, поиграться с конфигами или собрать пакет - но без риска сломать систему. Для этого в linux можно сделать свою мини-песочницу, используя старое доброе
🛠 Скрипт создающий песочницу:
BashTex📱 #bash #utils
Иногда нужно проверить какую-то команду, поиграться с конфигами или собрать пакет - но без риска сломать систему. Для этого в linux можно сделать свою мини-песочницу, используя старое доброе
chroot и немного bash.chroot - это механизм, который меняет корень файловой системы для процесса. Все, что он видит, находится внутри изолированного каталога, и даже rm -rf / не затронет настоящую систему (если, конечно, настроено правильно🤓 ).
#!/usr/bin/env bash
SANDBOX="/opt/sandbox"
DEBIAN_MIRROR="https://deb.debian.org/debian"
ARCH=$(dpkg --print-architecture)
# Проверка зависимостей
for cmd in debootstrap chroot mount umount; do
command -v $cmd >/dev/null || { echo "$cmd not found"; exit 1; }
done
# Создание окружения, если нет
if [[ ! -d "$SANDBOX" ]]; then
echo "Создаю минимальную систему Debian..."
sudo debootstrap --arch="$ARCH" stable "$SANDBOX" "$DEBIAN_MIRROR"
fi
# Монтируем системные точки
sudo mount -t proc /proc "$SANDBOX/proc"
sudo mount --rbind /sys "$SANDBOX/sys"
sudo mount --rbind /dev "$SANDBOX/dev"
# Добавим базовые бинари для экспериментов
sudo cp /bin/bash "$SANDBOX/bin/"
sudo cp /usr/bin/ls "$SANDBOX/usr/bin/"
echo "Входим в песочницу!"
sudo chroot "$SANDBOX" /bin/bash
# После выхода - очистка
echo "Отмонтирую ресурсы..."
sudo umount -l "$SANDBOX/proc" "$SANDBOX/sys" "$SANDBOX/dev"
Здесь:debootstrapставит минимальный Debian прямо в каталог (/opt/sandbox)mountподключает системные псевдофайловые системы (/proc,/sys,/dev)chrootзапускает bash внутри нового корня
После выхода - все отмонтируется, и можно просто удалить/opt/sandbox
BashTex
Please open Telegram to view this post
VIEW IN TELEGRAM
👍11
Сравнение конфигураций между серверами
Иногда нужно понять, чем конфиги на dev и prod отличаются, но без полного копирования или внешних тулз.
🛠 Пример скрипта:
▪️ Как это работает
▪️ Расширение скрипта при необходимости:
1️⃣ Сравнение содержимого файлов:
2️⃣ Генерация markdown отчета:
3️⃣ Уведомление в телегу при изменениях:
BashTex📱 #bash
Иногда нужно понять, чем конфиги на dev и prod отличаются, но без полного копирования или внешних тулз.
Задача:
Сравнить/etc/nginx/и/etc/systemd/между двумя серверами, чтобы увидеть, какие файлы изменены, добавлены или удалены.
#!/usr/bin/env bash
REMOTE="[email protected]"
DIRS=("/etc/nginx" "/etc/systemd")
TMPDIR="/tmp/config-compare"
LOG="/tmp/config-diff.log"
mkdir -p "$TMPDIR"
> "$LOG"
for dir in "${DIRS[@]}"; do
echo "Checking $dir ..." | tee -a "$LOG"
rsync -avz --dry-run --delete "$REMOTE:$dir/" "$dir/" \
| grep -E '^deleting|^>f' \
| sed "s|^|$dir/ |" \
>> "$TMPDIR/rsync.diff"
done
if [[ -s "$TMPDIR/rsync.diff" ]]; then
echo -e "\nDifferences found:\n" | tee -a "$LOG"
cat "$TMPDIR/rsync.diff" | tee -a "$LOG"
else
echo "Configurations match across all directories." | tee -a "$LOG"
fi
rsync --dry-run --delete - имитирует синхронизацию, но ничего не меняет, выводит список различий между каталогами.grep -E '^>f|^deleting' - фильтрует только изменения (новые или удалённые файлы).
ssh "$REMOTE" "cat /etc/nginx/nginx.conf" > /tmp/remote.conf
diff -u /etc/nginx/nginx.conf /tmp/remote.conf || echo "nginx.conf differs!"
echo -e "## Config diff report\n\`\`\`\n$(cat $TMPDIR/rsync.diff)\n\`\`\`" > /tmp/diff_report.md
[[ -s $TMPDIR/rsync.diff ]] && curl -s -F "text=$(cat $TMPDIR/rsync.diff)" \
"https://api.telegram.org/bot$TOKEN/sendMessage?chat_id=$CHAT_ID"
BashTex
Please open Telegram to view this post
VIEW IN TELEGRAM
👍7🔥2
Системные уведомления через D-Bus и bash
Bash умеет не только писать логи в консоль - он может отправлять системные уведомления прямо в графическую среду, используя D-Bus. Это хороший способ сообщить пользователю о результатах скрипта, ошибках или завершении задач, не залезая в UI.
1️⃣ Простой способ - notify-send. Самая быстрая интеграция:
Работает через D-Bus (org.freedesktop.Notifications). Можно добавить приоритет, срок жизни, категории:
2️⃣ Напрямую через dbus-send. Если notify-send недоступен (например, в минимальной среде),
можно напрямую вызвать метод D-Bus:
Выглядит громоздко, но это чистый вызов через D-Bus, минуя внешние обертки. Можно внедрить в сценарии, где нужно абсолютное управление уведомлениями.
3️⃣ Уведомления в фоновом режиме. Если скрипт работает из cron или systemd-сервиса, нужно указать сеансовую шину D-Bus пользователя. Например, так:
Это позволяет отправлять уведомления от root в сессию конкретного пользователя.
4️⃣ Практический пример
Каждое выполнение скрипта будет сопровождаться визуальными уведомлениями в системе.
▪️ Динамические уведомления. Можно обновлять уведомление (пример для GNOME/KDE, не все среды поддерживают):
BashTex📱 #bash #utils
Bash умеет не только писать логи в консоль - он может отправлять системные уведомления прямо в графическую среду, используя D-Bus. Это хороший способ сообщить пользователю о результатах скрипта, ошибках или завершении задач, не залезая в UI.
notify-send "Бэкап завершен" "Все файлы успешно сохранены" --icon=dialog-information
Работает через D-Bus (org.freedesktop.Notifications). Можно добавить приоритет, срок жизни, категории:
notify-send \
--urgency=critical \
--expire-time=10000 \
--app-name="BackupScript" \
"Ошибка резервного копирования" \
"Недостаточно места на диске!"
можно напрямую вызвать метод D-Bus:
dbus-send --session --type=method_call \
--dest=org.freedesktop.Notifications \
/org/freedesktop/Notifications \
org.freedesktop.Notifications.Notify \
string:"BashScript" \
uint32:0 \
string:"dialog-warning" \
string:"Системное уведомление" \
string:"Задача завершена с ошибками" \
array:string:"ОК" \
dict:string:string: \
int32:-1
Выглядит громоздко, но это чистый вызов через D-Bus, минуя внешние обертки. Можно внедрить в сценарии, где нужно абсолютное управление уведомлениями.
export DBUS_SESSION_BUS_ADDRESS="unix:path=/run/user/$(id -u)/bus"
notify-send "Backup completed" "Проверено $(date)"
Это позволяет отправлять уведомления от root в сессию конкретного пользователя.
#!/usr/bin/env bash
export DBUS_SESSION_BUS_ADDRESS="unix:path=/run/user/$(id -u)/bus"
TASK="Резервное копирование /home"
notify-send "Начато" "$TASK..."
if tar czf /backup/home_$(date +%F).tar.gz /home 2>/dev/null; then
notify-send "Успешно" "$TASK завершено"
else
notify-send "Ошибка" "$TASK не выполнено"
fi
Каждое выполнение скрипта будет сопровождаться визуальными уведомлениями в системе.
ID=$(notify-send "Выполняется резервное копирование..." --print-id)
sleep 5
notify-send "Завершено" "Файлы успешно сохранены" --replace-id="$ID"
BashTex
Please open Telegram to view this post
VIEW IN TELEGRAM
👍4🔥2🤨1
Вставка последнего аргумента без копипасты
Сколько раз вы выполняли команду, вроде:
а потом через секунду нужно:
и снова набираете руками путь или выделяете путь?
Есть способ не повторять последнее слово - bash это помнит.
⠀
1️⃣ Alt + . - вставка последнего аргумента
В любой момент нажмите
превратится в
Нажимайте Alt + . несколько раз, чтобы пройтись по аргументам из истории (Bash перебирает их назад).
2️⃣ Альтернатива: !$ и !. Тоже самое, но в виде подстановки из истории:
или
3️⃣ Примеры
Быстрое удаление того, что только что создали:
Скопировали и сразу зашли:
Переместили файл и открыли его в редакторе:
Копируете в несколько мест подряд:
BashTex📱 #bash #utils
Сколько раз вы выполняли команду, вроде:
cp file.txt /tmp/somedir/
а потом через секунду нужно:
cd /tmp/somedir/
и снова набираете руками путь или выделяете путь?
Есть способ не повторять последнее слово - bash это помнит.
⠀
В любой момент нажмите
Alt + . - и bash подставит последний аргумент предыдущей команды.
cp file.txt /tmp/somedir/
cd <Alt+.>
превратится в
cd /tmp/somedir/
Нажимайте Alt + . несколько раз, чтобы пройтись по аргументам из истории (Bash перебирает их назад).
cd !$
или
cd !.
!$ - последний аргумент предыдущей команды.!. - то же самое, но безопаснее (некоторые шеллы по-разному интерпретируют $).Быстрое удаление того, что только что создали:
mkdir new_dir
rm -r !$
Скопировали и сразу зашли:
cp -r project /opt/
cd !$
Переместили файл и открыли его в редакторе:
mv data.log /var/log/archive/
nano !$
Копируете в несколько мест подряд:
cp backup.tar.gz /mnt/usb/
cp !$ /srv/backups/
BashTex
Please open Telegram to view this post
VIEW IN TELEGRAM
👍11🔥1😁1