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
👍11
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
👍8🔥2
Please open Telegram to view this post
VIEW IN TELEGRAM
😁8🔥3
Как выполнить команду без следов
Многие знают, что 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
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
😁14🔥3🫡3