Оптимизация вложенных циклов и массивов
Вложенные циклы в bash - частая причина медленных скриптов при работе с большими массивами и файлами. Особенно если ты обрабатываешь 10000+ элементов и каждый проход делает grep, awk, cut, cat, sed...
▪️ Антипаттерн
➖ Это O(N²). Если массивы по 10к строк - будет 100 млн сравнений.
▪️ Оптимизация через associative array (Bash 4+)
➕ Это уже O(N). И в 1000 раз быстрее.
▪️ Ускорение чтения данных. Избавляемся от cat в цикле:
➖ Плохо:
➕ Лучше:
▪️ Убираем лишние циклы. Когда можно - переноси логику внутрь awk, grep, join, sort -m и т.д.
Пример: пересечение двух файлов без bash-циклов:
BashTex📱 #bash #utils
Вложенные циклы в bash - частая причина медленных скриптов при работе с большими массивами и файлами. Особенно если ты обрабатываешь 10000+ элементов и каждый проход делает grep, awk, cut, cat, sed...
for i in "${list1[@]}"; do
for j in "${list2[@]}"; do
if [[ "$i" == "$j" ]]; then
echo "Match: $i"
fi
done
done
declare -A lookup
# Заполняем хеш
for item in "${list2[@]}"; do
lookup["$item"]=1
done
# Ищем быстро
for i in "${list1[@]}"; do
if [[ ${lookup["$i"]+found} ]]; then
echo "Match: $i"
fi
done
while read line; do
cat "$line"
done < files.txt
mapfile -t files < files.txt
for f in "${files[@]}"; do
cat "$f"
done
Пример: пересечение двух файлов без bash-циклов:
sort file1.txt file2.txt | uniq -d
BashTex
Please open Telegram to view this post
VIEW IN TELEGRAM
👍9
Использование readarray и mapfile для групповой обработки строк
Когда обрабатываем многострочный вывод - часто используем while read, но есть более быстрый способ:
❓ Что делает mapfile? Читает строки из stdin или команды сразу в массив, построчно:
Аналог:
Флаг -t удаляет \n на конце строк.
▪️ Обработка вывода команд:
▪️ Фильтрация и трансформация. Комбинируем с grep, awk, sort, uniq:
▪️ Срезы и подмассивы. Работа с частями:
▪️ Использование с read-only входом:
BashTex📱 #bash #utils
Когда обрабатываем многострочный вывод - часто используем while read, но есть более быстрый способ:
mapfile (синоним - readarray).
mapfile -t lines < input.txt
# теперь ${lines[0]}, ${lines[1]} и т.д. - отдельные строки
Аналог:
readarray -t lines < input.txt
Флаг -t удаляет \n на конце строк.
mapfile -t users < <(cut -d: -f1 /etc/passwd)
for user in "${users[@]}"; do
echo "Пользователь: $user"
done
mapfile -t services < <(systemctl list-units --type=service | awk '/running/ {print $1}')
for svc in "${services[@]}"; do
echo "Активный сервис: $svc"
done
mapfile -t logs < <(tail -n 100 /var/log/syslog)
# последние 5 строк
printf '%s\n' "${logs[@]: -5}"
some_func() {
local lines=()
mapfile -t lines
echo "Прочитано строк: ${#lines[@]}"
}
cat /etc/passwd | some_func
BashTex
Please open Telegram to view this post
VIEW IN TELEGRAM
👍8
Упаковка структуры каталогов с фильтрацией
Когда нужно архивировать не весь каталог, а только часть - с фильтрацией и даже переименованием путей внутри архива - на помощь приходят
▪️ Базовый пример: фильтруем по маске и архивируем
Тут мы архивируем только .conf-файлы.
▪️ Добавим переименование путей внутри архива. Архивируем все из src/, но хотим, чтобы внутри архива это лежало как app/.
▪️ Упаковка через find + --transform + относительные пути. Предположим, хотим архивировать только .sh и .py-файлы, заменив scripts/ на bin/ внутри архива:
▪️ Упрощенный однострочник:
▪️ Продвинутый пример: исключения + трансформация + директория
BashTex📱 #bash #utils
Когда нужно архивировать не весь каталог, а только часть - с фильтрацией и даже переименованием путей внутри архива - на помощь приходят
find, tar и его флаг --transform.
find project/ -type f -name "*.conf" > list.txt
tar -czf config_backup.tar.gz -T list.txt
Тут мы архивируем только .conf-файлы.
tar -czf app.tar.gz \
--transform='s|^src|app|' \
-C ./ src
--transform использует sed-подобные выражения. Тут s|^src|app| означает: заменить в начале пути src на app.
find scripts/ -type f \( -name "*.sh" -o -name "*.py" \) > filelist.txt
tar -czf bin_scripts.tar.gz \
--transform='s|^scripts|bin|' \
-T filelist.txt
find scripts/ -type f -name "*.sh" \
| tar -czf archive.tar.gz --transform='s|^scripts|bin|' -T -
find ./src -type f ! -name "*.tmp" \
| tar -czf src_clean.tar.gz \
--transform='s|^./src|clean_src|' \
-T -
BashTex
Please open Telegram to view this post
VIEW IN TELEGRAM
👍9
Планировщик задач с временными окнами: только ночью, только по будням, только в выходные
Иногда cron не подходит:
📍 нужно запустить задачу в определенное время суток,
📍 но не строго по расписанию, а если есть что обрабатывать,
📍 и не запускать ее днем, когда сервер под нагрузкой.
Тут пригодится встроенная проверка временных окон в скрипте, а cron пусть просто каждые 10 минут запускает "умный планировщик".
🛠 Пример: выполнение задачи только с 02:00 до 05:00 по будням
▪️ Расширение: исключение праздников (по файлу)
▪️ Альтернатива: запуск только по выходным с 3 до 6 утра
▪️ Запуск через cron
BashTex📱 #bash #utils
Иногда cron не подходит:
Тут пригодится встроенная проверка временных окон в скрипте, а cron пусть просто каждые 10 минут запускает "умный планировщик".
#!/bin/bash
#настройки временного окна
start_hour=2
end_hour=5
# день недели (1..5 = пн-пт)
dow=$(date +%u)
#часы сейчас
hour=$(date +%H)
#проверка: будний день и нужное время
if (( dow >= 1 && dow <= 5 )) && (( hour >= start_hour && hour < end_hour )); then
echo "[$(date)] Временное окно открыто. Выполняем задачу."
#здесь реальная задача:
/opt/backup/backup-db.sh
else
echo "[$(date)] Вне временного окна. Пропуск."
fi
holiday_file="/etc/holidays.txt"
today=$(date +%Y-%m-%d)
if grep -q "$today" "$holiday_file"; then
echo "Сегодня праздник. Задача не выполняется."
exit 0
fi
(( dow == 6 || dow == 7 )) && (( hour >= 3 && hour < 6 )) && run_task
*/10 * * * * /usr/local/bin/task_scheduler.sh >> /var/log/task.log 2>&1
BashTex
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥10
Бинарные флаги и побитовые операции
Bash - не только про строки. В нtм легко использовать побитовую арифметику (&, |, ^, ~, <<, >>) для управления флагами, что бывает полезно в админских скриптах.
▪️ Базовая логика. Каждый бит числа можно трактовать как флаг (0 - выключено, 1 - включено):
▪️ Установка флага (OR |)
▪️ Проверка флага (AND &)
▪️ Сброс флага (AND + NOT &~)
▪️ Инверсия (XOR ^)
▪️ Практические кейсы
1️⃣ Система разрешений в скриптах. Можно задавать доступ к операциям через биты вместо кучи if:
2️⃣ Упаковка нескольких состояний в одно число. Например, код статуса сервиса:
3️⃣ Быстрые флаги для CLI-опций
4️⃣ Флаги как компактная замена массивов. Вместо массива enabled_features=(...) можно хранить все в одной переменной. Это особенно ценно при передаче значений между процессами.
BashTex📱 #bash #utils
Bash - не только про строки. В нtм легко использовать побитовую арифметику (&, |, ^, ~, <<, >>) для управления флагами, что бывает полезно в админских скриптах.
FLAG_READ=1 # 0001
FLAG_WRITE=2 # 0010
FLAG_EXEC=4 # 0100
FLAG_DELETE=8 # 1000
rights=0
rights=$(( rights | FLAG_READ | FLAG_WRITE ))
echo $rights # 3 (0001 + 0010 = 0011)
if (( rights & FLAG_WRITE )); then
echo "Есть право записи"
fi
rights=$(( rights & ~FLAG_READ ))
echo $rights # теперь без read
rights=$(( rights ^ FLAG_EXEC )) # переключение: если был - уберется, если не был - включится
if (( user_flags & FLAG_DELETE )); then
rm "$file"
fi
STATUS_RUNNING=1
STATUS_RESTARTING=2
STATUS_FAILED=4
status=$(( STATUS_RUNNING | STATUS_RESTARTING ))
(( status & STATUS_FAILED )) && echo "Ошибка!"
OPT_VERBOSE=1
OPT_DRYRUN=2
OPT_FORCE=4
opts=0
[[ $1 == "-v" ]] && opts=$(( opts | OPT_VERBOSE ))
[[ $1 == "-f" ]] && opts=$(( opts | OPT_FORCE ))
(( opts & OPT_VERBOSE )) && echo "[VERBOSE MODE]"
BashTex
Please open Telegram to view this post
VIEW IN TELEGRAM
👍10
Создание оффлайн инсталлятора из .deb зависимостей
Иногда сервер или рабочая машина не имеет прямого доступа в интернет. Но поставить нужный софт все же нужно. Решение: собрать локальный оффлайн инсталлятор из уже установленных пакетов и их зависимостей.
▪️ Установка dpkg-repack. На машине с интернетом:
▪️ Репак пакета в .deb. Если пакет уже стоит в системе:
▪️ Репак с зависимостями. Чтобы вытащить пакет + его зависимости:
▪️ Итог - "папка-инсталлятор". В каталоге будут все нужные .deb:
▪️ Установка на оффлайн серваке. Переносим папку, потом:
Можно превратить папку в локальный APT-репозиторий:
и подключить его через sources.list.
BashTex📱 #bash #utils
Иногда сервер или рабочая машина не имеет прямого доступа в интернет. Но поставить нужный софт все же нужно. Решение: собрать локальный оффлайн инсталлятор из уже установленных пакетов и их зависимостей.
sudo apt install dpkg-repack
dpkg-repack htop
# создаст htop_3.0.5-1_amd64.deb
mkdir offline-installer && cd offline-installer
# пример для nginx
for pkg in $(apt-cache depends --recurse --no-recommends --no-suggests \
--no-conflicts --no-breaks --no-replaces --no-enhances \
nginx | grep "^\w"); do
dpkg-repack "$pkg"
done
ls *.deb
nginx_1.24.0-1_amd64.deb
libpcre3_2:8.39-13_amd64.deb
libssl1.1_1.1.1n-0+deb11u5_amd64.deb
...
sudo dpkg -i *.deb
sudo apt -f install # подтянет зависимости из локальных .deb
Можно превратить папку в локальный APT-репозиторий:
dpkg-scanpackages . /dev/null | gzip -9c > Packages.gz
и подключить его через sources.list.
BashTex
Please open Telegram to view this post
VIEW IN TELEGRAM
👍13🔥2
Использование coproc для работы с асинхронными потоками
Многие знают про & и wait, но есть куда более элегантный инструмент для асинхронной работы -
▪️ Простейший пример
Процесс bc живет, пока вы его не закроете. То есть можно посылать команды и читать ответы несколько раз.
▪️ Практические кейсы
📍 Асинхронные задачи с обратной связью. Можно запускать ping, tcpdump или tail -f в coproc, а потом считывать поток строк построчно:
📍 Фоновый обработчик. Например, пишем строки в процессинг-скрипт:
📍 Несколько процессов. Coproc можно называть:
BashTex📱 #bash #utils
Многие знают про & и wait, но есть куда более элегантный инструмент для асинхронной работы -
coproc. Это встроенная команда, которая запускает процесс в фоне и автоматически подключает к нему двусторонний канал (pipe).❓ Как это работает
coproc создает процесс, у которого:
stdin доступен через дескриптор ${COPROC[1]} (запись в процесс);
stdout доступен через ${COPROC[0]} (чтение из процесса);
имя coproc можно задавать, чтобы управлять несколькими одновременно.
#!/bin/bash
# Запускаем фоновый bc как "асинхронный калькулятор"
coproc CALC { bc -l; }
# Отправляем в stdin команды
echo "2+3" >&"${CALC[1]}"
echo "s(1)" >&"${CALC[1]}"
# Читаем ответы из stdout
read -u "${CALC[0]}" result1
read -u "${CALC[0]}" result2
echo "Результат 1: $result1"
echo "Результат 2: $result2"
Процесс bc живет, пока вы его не закроете. То есть можно посылать команды и читать ответы несколько раз.
coproc PINGER { ping -O 8.8.8.8; }
while read -ru "${PINGER[0]}" line; do
echo "PING: $line"
done
coproc HANDLER { while read line; do echo ">> $line"; done; }
echo "Hello" >&"${HANDLER[1]}"
echo "World" >&"${HANDLER[1]}"
coproc P1 { ping -c2 8.8.8.8; }
coproc P2 { ping -c2 1.1.1.1; }
▪️ Подводные камни
Каналы буферизуются, иногда нужно sleep или stdbuf -oL для построчной работы.
Закрывайте дескрипторы exec {fd}>&-, иначе процесс может висеть.
coproc работает только в bash ≥ 4.
BashTex
Please open Telegram to view this post
VIEW IN TELEGRAM
👍7
Встраивание мини-HTTP-сервера на netcat + bash (для локальных API)
Иногда хочется быстро поднять легковесный API без Nginx/Apache, чтобы протестировать интеграцию или выдавать системные данные в JSON. Для этого достаточно bash + netcat.
🛠 Минимальный пример
▪️ Запуск
Теперь можно открыть в браузере: https://localhost:8080
▪️ Возможности
📍 отдавать системные метрики:
📍 простой healthcheck для Docker:
📍 мини-API для локальных скриптов (например, статус бэкапа).
Такой подход позволяет собрать сверхлегкий REST-like API прямо из bash - без сторонних веб-фреймворков.
BashTex📱 #bash #utils
Иногда хочется быстро поднять легковесный API без Nginx/Apache, чтобы протестировать интеграцию или выдавать системные данные в JSON. Для этого достаточно bash + netcat.
Общая идея такова:📍 netcat слушает порт (например, 8080);📍 bash скрипт парсит запрос и отвечает заголовками + данными;📍 результат можно использовать для локальных API-запросов (например, мониторинг).
#!/bin/bash
PORT=8080
while true; do
# Принимаем одно соединение
{
# Читаем первую строку HTTP-запроса
read request
echo ">>> $request"
# Отправляем HTTP-ответ
echo -e "HTTP/1.1 200 OK\r"
echo -e "Content-Type: application/json\r"
echo -e "\r"
echo -e '{"status": "ok", "time": "'$(date +%T)'"}'
} | nc -l -p $PORT -q 1
done
chmod +x mini-http.sh
./mini-http.sh
Теперь можно открыть в браузере: https://localhost:8080
echo -e '{"load": "'$(uptime | awk "{print \$10}")'"}'
curl localhost:8080/healthТакой подход позволяет собрать сверхлегкий REST-like API прямо из bash - без сторонних веб-фреймворков.
BashTex
Please open Telegram to view this post
VIEW IN TELEGRAM
👍7🔥5
systemd timers: сценарии, которые cron не умеет
Часто cron хватает для простого раз в день, но когда расписание становится хитрым - systemd timers выигрывают. Покажу несколько кейсов, которые на cron делать больно, а здесь - просто.
1️⃣ Только в рабочие дни, с 9:00 до 18:00. Хотим, чтобы скрипт мониторинга нагрузки запускался каждые 15 минут в будни, но не ночью и не в выходные:
Mon..Fri - только будни;
09..18/15:00 - каждые 15 минут с 9:00 до 18:00.
Аналог на cron выглядел бы как несколько строк с костылями.
2️⃣ Persistent=true - выполнение пропущенных задач. Задача должна выполняться раз в день, даже если сервер был выключен ночью.
Если сервак в оффлайне, при старте systemd увидит пропущенное выполнение и запустит задачу.
У cron такого поведения нет - выключил машину, задача пропала.
3️⃣ Замороченные расписания - раз в час, но не в обед. Допустим, хотим запускать скрипт синхронизации каждые 60 минут, но исключить обеденный перерыв (с 12 до 13):
Здесь мы задали диапазоны часов с дыркой.
4️⃣ Несколько расписаний для одной задачи. Хочется иметь и ночной запуск (2:00), и дополнительный в пятницу вечером:
В одном таймере можно указать несколько OnCalendar.
5️⃣ Контроль за пропусками и сбоями. Если критичный скрипт не отработал, мы хотим это видеть. Добавим OnFailure:
Если бэкап упадет, то сразу вызовется alert.service (например, отправка сообщения в телегу).
BashTex📱 #bash #utils
Часто cron хватает для простого раз в день, но когда расписание становится хитрым - systemd timers выигрывают. Покажу несколько кейсов, которые на cron делать больно, а здесь - просто.
/etc/systemd/system/load-check.timer
[Timer]
OnCalendar=Mon..Fri *-*-* 09..18/15:00
Mon..Fri - только будни;
09..18/15:00 - каждые 15 минут с 9:00 до 18:00.
Аналог на cron выглядел бы как несколько строк с костылями.
[Timer]
OnCalendar=daily
Persistent=true
Если сервак в оффлайне, при старте systemd увидит пропущенное выполнение и запустит задачу.
У cron такого поведения нет - выключил машину, задача пропала.
[Timer]
OnCalendar=Mon..Fri *-*-* 09..11:00,13..18:00
Здесь мы задали диапазоны часов с дыркой.
[Timer]
OnCalendar=*-*-* 02:00:00
OnCalendar=Fri *-*-* 19:00:00
В одном таймере можно указать несколько OnCalendar.
/etc/systemd/system/backup.service
[Unit]
Description=Nightly backup job
OnFailure=alert.service
Если бэкап упадет, то сразу вызовется alert.service (например, отправка сообщения в телегу).
BashTex
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥13👍7
Перехват 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 - фигурные скобки {}. Она позволяет за одну строку сгенерировать множество вариантов команды.
▪️ Пример с файлами
Создаст сразу три файла:
▪️ Полезные варианты
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
Архивация только новых или измененных файлов
Как архивировать только измененные файлы без ведения отдельного списка и без сложных скриптов. Решение на самом деле простое - связка 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
Сравнение конфигов на разных серверах через 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
Компактный контроль фоновых процессов
При работе с фоновыми процессами в скриптах часто возникает два вопроса:
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
Автообновление 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