BashTex | Linux
2.5K subscribers
44 photos
8 videos
275 links
Авторский канал для тех, кто хочет глубже погрузиться в мир Linux.

Подойдет для разработчиков, системных администраторов и DevOps

Реклама: @dad_admin
Download Telegram
Нужна оптимизация

BashTex 📱 #юмор
Please open Telegram to view this post
VIEW IN TELEGRAM
😁8👨‍💻1
Компактный контроль фоновых процессов

При работе с фоновыми процессами в скриптах часто возникает два вопроса:

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


▪️ Kill group: гасим всю пачку. У kill есть прием: если передать отрицательный PID, сигнал уйдет всей группе процессов.


# запустить группу
( 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 📱 #bash #utils
Please open Telegram to view this post
VIEW IN TELEGRAM
👍6
Мониторинг логов с реакцией

Если вы не хотите заморачиваться с созданием триггера, а нужно просто отловить ошибку один раз и забыть про нее, то есть вариант сделать это только с помощью bash.

1️⃣ Реакция на ошибку в логах


tail -f /var/log/syslog | grep --line-buffered "error" | while read line; do
notify-send "Ошибка в логах!" "$line"
done


Каждая новая строка с error вызывает уведомление на рабочем столе.

2️⃣ Отправка алертов на почту


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


По событию будет отправлено письмо на почту.

3️⃣ Подсчет в реальном времени


tail -f /var/log/nginx/access.log | awk '{print $1}' | sort | uniq -c


Видно, какие IP чаще всего обращаются к серверу.

BashTex 📱 #bash
Please open Telegram to view this post
VIEW IN TELEGRAM
👍7
Автообновление python-зависимостей в виртуальных окружениях

В проектах на python у меня неоднократно возникала ситуация: виртуальное окружение развернуто давно, а пакеты внутри уже устарели.
Чтобы не держать руками список и не забывать про обновления, можно автоматизировать этот процесс.

1️⃣ Получение списка пакетов. В активированном окружении pip list --outdated --format=freeze вернет пакеты, которые можно обновить:


source venv/bin/activate
pip list --outdated --format=freeze


2️⃣ Скрипт автообновления. Скрипт ищет все устаревшие пакеты и обновляет их до последней версии:


#!/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


3️⃣ Массовое обновление для проектов. Если проектов несколько, можно пройтись по каталогам и обновить все:


#!/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


4️⃣ Фиксируем версии. Перед обновлением полезно сохранить текущие версии, чтобы в случае проблем легко откатиться:


pip freeze > requirements-$(date +%F).txt


BashTex 📱 #bash #utils
Please open Telegram to view this post
VIEW IN TELEGRAM
👍7
Создание резервных копий БД с шифрованием

Правильный подход к шифрованию БД, чтобы не оставлять персональные данные открытыми это: создать дамп → зашифровать его GPG → сохранить/отправить.

▪️ Подготовка GPG. На сервере, который будет создавать бэкапы, импортируйте публичный ключ:


gpg --import backup_public.key


Проверьте наличие ключа:


gpg --list-keys


Использовать будем шифрование public key → private key, так что дешифровать можно только на безопасном ПК/сервере.

🛠 Скрипт универсального бэкапа PostgreSQL/MySQL с шифрованием GPG


#!/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 📱 #bash #utils
Please open Telegram to view this post
VIEW IN TELEGRAM
👍9
Использование printf -v для динамического формирования переменных

Бывает, что нужно создавать или заполнять переменные с динамическими именами: 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.

▪️ Нумерация с форматированием (zero-padding)


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" - формат имени и значение удобно комбинировать.

▪️ Создаем набор конфигов из CSV (пример)


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) позволяет получить значение переменной по имени.

▪️ Комбинация с declare -n (nameref)


printf -v "user_alice" "%s" "[email protected]"
ref_var="user_alice"
declare -n userref="$ref_var"
echo "Email via nameref: $userref"


declare -n дает ссылку на переменную, это удобнее, чем постоянно подставлять ${!name}.

▪️ Как избежать проблем с именами переменных. Переменная должна соответствовать правилам имени в bash. Можно валидировать:


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 📱 #bash #utils
Please open Telegram to view this post
VIEW IN TELEGRAM
👍6
Контроль размера логов и архивация быстро растущих файлов

Неловко будет если кто-то включил дебаг, забыл про него и оставил так, даже на выходные. Покажу скрипт который будет: мониторить размер файлов в директории логов, сравнивать с предыдущим значением, при подозрительном росте - архивировать лог и обнулять его, сохраняя копию.

🛠 Скрипт


#!/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 в выбранной директории.

▪️ Применение. Добавь в cron для регулярной проверки:


*/10 * * * * /usr/local/bin/log_watch.sh


BashTex 📱 #bash
Please open Telegram to view this post
VIEW IN TELEGRAM
👍4
Следующая станция Ubuntu 25.10

BashTex 📱 #юмор
Please open Telegram to view this post
VIEW IN TELEGRAM
😁16👍1
Оптимизация скриптов: меньше внешних команд - выше скорость

Одна из самых недооцененных идей - это избегать лишних вызовов внешних утилит. Каждый 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 уже делает свою работу

▪️ Фичи встроенного bash

${var,,} и ${var^^} - lowercase / uppercase без tr
${var//foo/bar} - замена без sed
${#array[@]} - длина массива без wc
[[ $var == pattern* ]] - шаблоны без grep
((expr)) - арифметика без expr или bc
mapfile -t lines < file - чтение файлов без cat


BashTex 📱 #bash
Please open Telegram to view this post
VIEW IN TELEGRAM
👍9
Кто тормозит твой скрипт

Когда скрипт вырастает, то понять, где он тратит время, становится сложно. Можно, конечно, запускать 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 📱 #bash
Please open Telegram to view this post
VIEW IN TELEGRAM
👍9
Условное выполнение: && vs ; vs || vs !

У bash есть несколько операторов, которые управляют выполнением команд в зависимости от их кода возврата (0 - успех, ≠0 - ошибка). Разница кажется простой, но на практике часто приводит к тому, что люди не совсем понимают, что они используют.

▪️ ; - просто последовательность. Команды выполняются всегда, независимо от успеха/ошибки.


echo "start"; false; echo "end"
# => выведет start, потом end, даже если false упала


Используется, когда важно выполнить все подряд.

▪️ && - только при успехе. Выполнится вторая команда, если первая завершилась успешно (exit code 0).


mkdir data && echo "Папка создана"


Если mkdir упадет (например, папка уже есть), то echo не выполнится.

Полезно для цепочек успеха:


make build && make test && make deploy


▪️ || - только при ошибке. Выполнится, если первая команда завершилась с ошибкой.


ping -c1 host || echo "Хост недоступен"


Удобно для fallback-сценариев.

▪️ Комбинация && … || … Классический паттерн if-else в одну строку:


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 📱 #bash #utils
Please open Telegram to view this post
VIEW IN TELEGRAM
👍16
Меньше if и больше логики в одну строку

Когда вы пишете:


if command; then
echo "OK"
else
echo "Fail"
fi


В мире плачет один котенок…🥺 Ведь есть куда более лаконичная альтернатива:


command && echo "OK" || echo "Fail"


Это логические цепочки (&& и ||), которые позволяют писать коротко и эффективно.

▪️ Принцип short-circuit логики

Bash, как и многие языки, останавливает выполнение цепочки, если уже ясно, что дальше не нужно:

cmd1 && cmd2 - cmd2 выполняется только если cmd1 завершилась успешно (exit 0).
cmd1 || cmd2 - cmd2 выполняется только если cmd1 завершилась с ошибкой (exit ≠ 0).

▪️ Примеры таких условий

📍 Минимизация if:


mkdir newdir && cd newdir


создаст и сразу перейдёт, только если каталог успешно создан.

📍 Команда с fallback:


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 📱 #bash #utils
Please open Telegram to view this post
VIEW IN TELEGRAM
👍14
Продуманно

BashTex 📱 #юмор
Please open Telegram to view this post
VIEW IN TELEGRAM
😁22👨‍💻1
Автоматическое масштабирование ресурсов контейнеров

Казалось бы, нетривиальная задача - нужно, чтобы контейнер автоматически подстраивался под нагрузку: добавлял 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 📱 #bash
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥8
Генерация таблиц прогресса

Реализация живой таблицы прогресса выполнения сприпта, обновляемой прямо в терминале - без внешних тулз, только 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 📱 #bash #utils
Please open Telegram to view this post
VIEW IN TELEGRAM
👍4
This media is not supported in your browser
VIEW IN TELEGRAM
Мужчины мечтают только об одном…

BashTex 📱 #юмор
Please open Telegram to view this post
VIEW IN TELEGRAM
😁13🔥3🫡3
Создание песочницы для экспериментов

Иногда нужно проверить какую-то команду, поиграться с конфигами или собрать пакет - но без риска сломать систему. Для этого в 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 📱 #bash #utils
Please open Telegram to view this post
VIEW IN TELEGRAM
👍11
Сравнение конфигураций между серверами

Иногда нужно понять, чем конфиги на 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' - фильтрует только изменения (новые или удалённые файлы).

▪️ Расширение скрипта при необходимости:

1️⃣ Сравнение содержимого файлов:


ssh "$REMOTE" "cat /etc/nginx/nginx.conf" > /tmp/remote.conf
diff -u /etc/nginx/nginx.conf /tmp/remote.conf || echo "nginx.conf differs!"


2️⃣ Генерация markdown отчета:


echo -e "## Config diff report\n\`\`\`\n$(cat $TMPDIR/rsync.diff)\n\`\`\`" > /tmp/diff_report.md


3️⃣Уведомление в телегу при изменениях:


[[ -s $TMPDIR/rsync.diff ]] && curl -s -F "text=$(cat $TMPDIR/rsync.diff)" \
"https://api.telegram.org/bot$TOKEN/sendMessage?chat_id=$CHAT_ID"


BashTex 📱 #bash
Please open Telegram to view this post
VIEW IN TELEGRAM
👍7🔥2
Системные уведомления через D-Bus и bash

Bash умеет не только писать логи в консоль - он может отправлять системные уведомления прямо в графическую среду, используя D-Bus. Это хороший способ сообщить пользователю о результатах скрипта, ошибках или завершении задач, не залезая в UI.

1️⃣ Простой способ - notify-send. Самая быстрая интеграция:


notify-send "Бэкап завершен" "Все файлы успешно сохранены" --icon=dialog-information


Работает через D-Bus (org.freedesktop.Notifications). Можно добавить приоритет, срок жизни, категории:


notify-send \
--urgency=critical \
--expire-time=10000 \
--app-name="BackupScript" \
"Ошибка резервного копирования" \
"Недостаточно места на диске!"


2️⃣ Напрямую через dbus-send. Если notify-send недоступен (например, в минимальной среде),
можно напрямую вызвать метод 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, минуя внешние обертки. Можно внедрить в сценарии, где нужно абсолютное управление уведомлениями.

3️⃣ Уведомления в фоновом режиме. Если скрипт работает из cron или systemd-сервиса, нужно указать сеансовую шину D-Bus пользователя. Например, так:


export DBUS_SESSION_BUS_ADDRESS="unix:path=/run/user/$(id -u)/bus"
notify-send "Backup completed" "Проверено $(date)"


Это позволяет отправлять уведомления от root в сессию конкретного пользователя.

4️⃣ Практический пример


#!/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


Каждое выполнение скрипта будет сопровождаться визуальными уведомлениями в системе.

▪️ Динамические уведомления. Можно обновлять уведомление (пример для GNOME/KDE, не все среды поддерживают):


ID=$(notify-send "Выполняется резервное копирование..." --print-id)
sleep 5
notify-send "Завершено" "Файлы успешно сохранены" --replace-id="$ID"


BashTex 📱 #bash #utils
Please open Telegram to view this post
VIEW IN TELEGRAM
👍4🔥2🤨1
Вставка последнего аргумента без копипасты

Сколько раз вы выполняли команду, вроде:


cp file.txt /tmp/somedir/


а потом через секунду нужно:


cd /tmp/somedir/


и снова набираете руками путь или выделяете путь?
Есть способ не повторять последнее слово - bash это помнит.

1️⃣ Alt + . - вставка последнего аргумента

В любой момент нажмите Alt + . - и bash подставит последний аргумент предыдущей команды.


cp file.txt /tmp/somedir/
cd <Alt+.>


превратится в


cd /tmp/somedir/


Нажимайте Alt + . несколько раз, чтобы пройтись по аргументам из истории (Bash перебирает их назад).

2️⃣ Альтернатива: !$ и !. Тоже самое, но в виде подстановки из истории:


cd !$


или


cd !.


!$ - последний аргумент предыдущей команды.
!. - то же самое, но безопаснее (некоторые шеллы по-разному интерпретируют $).

3️⃣ Примеры

Быстрое удаление того, что только что создали:


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 📱 #bash #utils
Please open Telegram to view this post
VIEW IN TELEGRAM
👍11🔥1😁1