Условное выполнение: && 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🔥4🫡3
Создание песочницы для экспериментов
Иногда нужно проверить какую-то команду, поиграться с конфигами или собрать пакет - но без риска сломать систему. Для этого в linux можно сделать свою мини-песочницу, используя старое доброе
🛠 Скрипт создающий песочницу:
BashTex📱 #bash #utils
Иногда нужно проверить какую-то команду, поиграться с конфигами или собрать пакет - но без риска сломать систему. Для этого в linux можно сделать свою мини-песочницу, используя старое доброе
chroot и немного bash.chroot - это механизм, который меняет корень файловой системы для процесса. Все, что он видит, находится внутри изолированного каталога, и даже rm -rf / не затронет настоящую систему (если, конечно, настроено правильно🤓 ).
#!/usr/bin/env bash
SANDBOX="/opt/sandbox"
DEBIAN_MIRROR="https://deb.debian.org/debian"
ARCH=$(dpkg --print-architecture)
# Проверка зависимостей
for cmd in debootstrap chroot mount umount; do
command -v $cmd >/dev/null || { echo "$cmd not found"; exit 1; }
done
# Создание окружения, если нет
if [[ ! -d "$SANDBOX" ]]; then
echo "Создаю минимальную систему Debian..."
sudo debootstrap --arch="$ARCH" stable "$SANDBOX" "$DEBIAN_MIRROR"
fi
# Монтируем системные точки
sudo mount -t proc /proc "$SANDBOX/proc"
sudo mount --rbind /sys "$SANDBOX/sys"
sudo mount --rbind /dev "$SANDBOX/dev"
# Добавим базовые бинари для экспериментов
sudo cp /bin/bash "$SANDBOX/bin/"
sudo cp /usr/bin/ls "$SANDBOX/usr/bin/"
echo "Входим в песочницу!"
sudo chroot "$SANDBOX" /bin/bash
# После выхода - очистка
echo "Отмонтирую ресурсы..."
sudo umount -l "$SANDBOX/proc" "$SANDBOX/sys" "$SANDBOX/dev"
Здесь:debootstrapставит минимальный Debian прямо в каталог (/opt/sandbox)mountподключает системные псевдофайловые системы (/proc,/sys,/dev)chrootзапускает bash внутри нового корня
После выхода - все отмонтируется, и можно просто удалить/opt/sandbox
BashTex
Please open Telegram to view this post
VIEW IN TELEGRAM
👍11
Сравнение конфигураций между серверами
Иногда нужно понять, чем конфиги на dev и prod отличаются, но без полного копирования или внешних тулз.
🛠 Пример скрипта:
▪️ Как это работает
▪️ Расширение скрипта при необходимости:
1️⃣ Сравнение содержимого файлов:
2️⃣ Генерация markdown отчета:
3️⃣ Уведомление в телегу при изменениях:
BashTex📱 #bash
Иногда нужно понять, чем конфиги на dev и prod отличаются, но без полного копирования или внешних тулз.
Задача:
Сравнить/etc/nginx/и/etc/systemd/между двумя серверами, чтобы увидеть, какие файлы изменены, добавлены или удалены.
#!/usr/bin/env bash
REMOTE="[email protected]"
DIRS=("/etc/nginx" "/etc/systemd")
TMPDIR="/tmp/config-compare"
LOG="/tmp/config-diff.log"
mkdir -p "$TMPDIR"
> "$LOG"
for dir in "${DIRS[@]}"; do
echo "Checking $dir ..." | tee -a "$LOG"
rsync -avz --dry-run --delete "$REMOTE:$dir/" "$dir/" \
| grep -E '^deleting|^>f' \
| sed "s|^|$dir/ |" \
>> "$TMPDIR/rsync.diff"
done
if [[ -s "$TMPDIR/rsync.diff" ]]; then
echo -e "\nDifferences found:\n" | tee -a "$LOG"
cat "$TMPDIR/rsync.diff" | tee -a "$LOG"
else
echo "Configurations match across all directories." | tee -a "$LOG"
fi
rsync --dry-run --delete - имитирует синхронизацию, но ничего не меняет, выводит список различий между каталогами.grep -E '^>f|^deleting' - фильтрует только изменения (новые или удалённые файлы).
ssh "$REMOTE" "cat /etc/nginx/nginx.conf" > /tmp/remote.conf
diff -u /etc/nginx/nginx.conf /tmp/remote.conf || echo "nginx.conf differs!"
echo -e "## Config diff report\n\`\`\`\n$(cat $TMPDIR/rsync.diff)\n\`\`\`" > /tmp/diff_report.md
[[ -s $TMPDIR/rsync.diff ]] && curl -s -F "text=$(cat $TMPDIR/rsync.diff)" \
"https://api.telegram.org/bot$TOKEN/sendMessage?chat_id=$CHAT_ID"
BashTex
Please open Telegram to view this post
VIEW IN TELEGRAM
👍8🔥2
Системные уведомления через D-Bus и bash
Bash умеет не только писать логи в консоль - он может отправлять системные уведомления прямо в графическую среду, используя D-Bus. Это хороший способ сообщить пользователю о результатах скрипта, ошибках или завершении задач, не залезая в UI.
1️⃣ Простой способ - notify-send. Самая быстрая интеграция:
Работает через D-Bus (org.freedesktop.Notifications). Можно добавить приоритет, срок жизни, категории:
2️⃣ Напрямую через dbus-send. Если notify-send недоступен (например, в минимальной среде),
можно напрямую вызвать метод D-Bus:
Выглядит громоздко, но это чистый вызов через D-Bus, минуя внешние обертки. Можно внедрить в сценарии, где нужно абсолютное управление уведомлениями.
3️⃣ Уведомления в фоновом режиме. Если скрипт работает из cron или systemd-сервиса, нужно указать сеансовую шину D-Bus пользователя. Например, так:
Это позволяет отправлять уведомления от root в сессию конкретного пользователя.
4️⃣ Практический пример
Каждое выполнение скрипта будет сопровождаться визуальными уведомлениями в системе.
▪️ Динамические уведомления. Можно обновлять уведомление (пример для GNOME/KDE, не все среды поддерживают):
BashTex📱 #bash #utils
Bash умеет не только писать логи в консоль - он может отправлять системные уведомления прямо в графическую среду, используя D-Bus. Это хороший способ сообщить пользователю о результатах скрипта, ошибках или завершении задач, не залезая в UI.
notify-send "Бэкап завершен" "Все файлы успешно сохранены" --icon=dialog-information
Работает через D-Bus (org.freedesktop.Notifications). Можно добавить приоритет, срок жизни, категории:
notify-send \
--urgency=critical \
--expire-time=10000 \
--app-name="BackupScript" \
"Ошибка резервного копирования" \
"Недостаточно места на диске!"
можно напрямую вызвать метод D-Bus:
dbus-send --session --type=method_call \
--dest=org.freedesktop.Notifications \
/org/freedesktop/Notifications \
org.freedesktop.Notifications.Notify \
string:"BashScript" \
uint32:0 \
string:"dialog-warning" \
string:"Системное уведомление" \
string:"Задача завершена с ошибками" \
array:string:"ОК" \
dict:string:string: \
int32:-1
Выглядит громоздко, но это чистый вызов через D-Bus, минуя внешние обертки. Можно внедрить в сценарии, где нужно абсолютное управление уведомлениями.
export DBUS_SESSION_BUS_ADDRESS="unix:path=/run/user/$(id -u)/bus"
notify-send "Backup completed" "Проверено $(date)"
Это позволяет отправлять уведомления от root в сессию конкретного пользователя.
#!/usr/bin/env bash
export DBUS_SESSION_BUS_ADDRESS="unix:path=/run/user/$(id -u)/bus"
TASK="Резервное копирование /home"
notify-send "Начато" "$TASK..."
if tar czf /backup/home_$(date +%F).tar.gz /home 2>/dev/null; then
notify-send "Успешно" "$TASK завершено"
else
notify-send "Ошибка" "$TASK не выполнено"
fi
Каждое выполнение скрипта будет сопровождаться визуальными уведомлениями в системе.
ID=$(notify-send "Выполняется резервное копирование..." --print-id)
sleep 5
notify-send "Завершено" "Файлы успешно сохранены" --replace-id="$ID"
BashTex
Please open Telegram to view this post
VIEW IN TELEGRAM
👍7🔥2🤨1
Вставка последнего аргумента без копипасты
Сколько раз вы выполняли команду, вроде:
а потом через секунду нужно:
и снова набираете руками путь или выделяете путь?
Есть способ не повторять последнее слово - bash это помнит.
⠀
1️⃣ Alt + . - вставка последнего аргумента
В любой момент нажмите
превратится в
Нажимайте Alt + . несколько раз, чтобы пройтись по аргументам из истории (Bash перебирает их назад).
2️⃣ Альтернатива: !$ и !. Тоже самое, но в виде подстановки из истории:
или
3️⃣ Примеры
Быстрое удаление того, что только что создали:
Скопировали и сразу зашли:
Переместили файл и открыли его в редакторе:
Копируете в несколько мест подряд:
BashTex📱 #bash #utils
Сколько раз вы выполняли команду, вроде:
cp file.txt /tmp/somedir/
а потом через секунду нужно:
cd /tmp/somedir/
и снова набираете руками путь или выделяете путь?
Есть способ не повторять последнее слово - bash это помнит.
⠀
В любой момент нажмите
Alt + . - и bash подставит последний аргумент предыдущей команды.
cp file.txt /tmp/somedir/
cd <Alt+.>
превратится в
cd /tmp/somedir/
Нажимайте Alt + . несколько раз, чтобы пройтись по аргументам из истории (Bash перебирает их назад).
cd !$
или
cd !.
!$ - последний аргумент предыдущей команды.!. - то же самое, но безопаснее (некоторые шеллы по-разному интерпретируют $).Быстрое удаление того, что только что создали:
mkdir new_dir
rm -r !$
Скопировали и сразу зашли:
cp -r project /opt/
cd !$
Переместили файл и открыли его в редакторе:
mv data.log /var/log/archive/
nano !$
Копируете в несколько мест подряд:
cp backup.tar.gz /mnt/usb/
cp !$ /srv/backups/
BashTex
Please open Telegram to view this post
VIEW IN TELEGRAM
👍14🔥2😁1
Сохраняем и восстанавливаем окружение
Со временем окружение обрастает алиасами, функциями, переменными, путями - целой экосистемой, которую легко потерять при переключении между проектами. Что если сделать снимок текущего состояния shell и потом восстановить его одной командой?
1️⃣ Сохранение окружения. Bash позволяет получить всю информацию о текущем состоянии:
Можно собрать это в файл:
Теперь у вас дамп окружения. Содержит все, что вы настроили вручную или подгрузили через .bashrc, .bash_aliases, и т.д.
2️⃣ Восстановление окружения. Чтобы применить снимок в другой сессии:
Все ваши функции, алиасы и переменные возвращаются как были.
3️⃣ Пример автоматизации. Сделаем удобный скрипт:
Использование:
4️⃣ Сравнение. Финалочка, можно сравнить два снимка:
покажет, что отличается между dev и prod.
BashTex📱 #bash #utils
Со временем окружение обрастает алиасами, функциями, переменными, путями - целой экосистемой, которую легко потерять при переключении между проектами. Что если сделать снимок текущего состояния shell и потом восстановить его одной командой?
alias # все алиасы
declare -f # все функции
declare -p # все переменные (включая окружение)
Можно собрать это в файл:
{
echo "# Snapshot from $(date)"
echo "# Aliases"
alias
echo
echo "# Functions"
declare -f
echo
echo "# Variables"
declare -p
} > ~/.bash_snapshot
Теперь у вас дамп окружения. Содержит все, что вы настроили вручную или подгрузили через .bashrc, .bash_aliases, и т.д.
source ~/.bash_snapshot
Все ваши функции, алиасы и переменные возвращаются как были.
snapshot() {
local file="${1:-~/.bash_snapshot_$(date +%F_%H-%M-%S)}"
{
alias
declare -f
declare -p | grep -v '^declare -[a-z]* BASH'
} > "$file"
echo "Saved snapshot to $file"
}
restore() {
local file="${1:-~/.bash_snapshot_latest}"
[[ -f $file ]] && source "$file" && echo "Restored from $file"
}
Использование:
snapshot ~/envs/dev.env
restore ~/envs/dev.env
diff <(grep -v '^#' dev.env) <(grep -v '^#' prod.env)
покажет, что отличается между dev и prod.
BashTex
Please open Telegram to view this post
VIEW IN TELEGRAM
👍9
Захват переменных окружения из другой сессии
Иногда нужно подцепить окружение работающего процесса: например, чтобы повторить его контекст, восстановить переменные или понять, с какими параметрами запущен сервис.
Обычно это делают через export или env, но можно достать переменные прямо из
▪️ Чтение окружения процесса. Каждый процесс в Linux хранит свои переменные в:
Это бинарный файл, где переменные разделены нулевыми байтами (\0).
Выведет все окружение процесса:
▪️ Захват и использование в текущей сессии. Если нужно подгрузить это окружение в текущий bash:
Теперь ты в том же окружении, что и процесс $PID.
▪️ Извлечение конкретной переменной. Например, достанем PATH:
А если нужно универсальнее, то функция:
▪️ Захват окружения systemd-сервисов. Systemd не всегда передает окружение дальше, но его можно вытащить по PID активного процесса:
Пример: повторить сессию nginx worker’а
Теперь любая команда (например, curl, python, php) будет запускаться с теми же переменными, что и процесс веб-сервера.
🌟 Доступно только для процессов, владельцем которых ты являешься (или root).
BashTex📱 #bash #utils
Иногда нужно подцепить окружение работающего процесса: например, чтобы повторить его контекст, восстановить переменные или понять, с какими параметрами запущен сервис.
Обычно это делают через export или env, но можно достать переменные прямо из
/proc, без доступа к shell-сессии.
/proc/<PID>/environ
Это бинарный файл, где переменные разделены нулевыми байтами (\0).
PID=1234
tr '\0' '\n' < /proc/$PID/environ
Выведет все окружение процесса:
USER=www-data
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin
PWD=/var/www
HOME=/var/www
LANG=en_US.UTF-8
PID=1234
eval "$(tr '\0' '\n' < /proc/$PID/environ | sed 's/^/export /')"
Теперь ты в том же окружении, что и процесс $PID.
Будет полезно, если:
хочешь повторить окружение демона (nginx, systemd, custom app);
нужно пересоздать контекст для отладки;
запускаешь скрипт от того же пользователя, что и процесс.
grep -z '^PATH=' /proc/$PID/environ | tr -d '\0' | cut -d= -f2-
А если нужно универсальнее, то функция:
get_env_var() {
local pid=$1 var=$2
grep -z "^${var}=" /proc/$pid/environ | tr -d '\0' | cut -d= -f2-
}
get_env_var 1234 PATH
PID=$(systemctl show -p MainPID myservice | cut -d= -f2)
tr '\0' '\n' < /proc/$PID/environ
Пример: повторить сессию nginx worker’а
PID=$(pgrep -n nginx)
eval "$(tr '\0' '\n' < /proc/$PID/environ | sed 's/^/export /')"
env | grep -E 'PATH|LANG|PWD'
Теперь любая команда (например, curl, python, php) будет запускаться с теми же переменными, что и процесс веб-сервера.
BashTex
Please open Telegram to view this post
VIEW IN TELEGRAM
👍7
Please open Telegram to view this post
VIEW IN TELEGRAM
😁21👍3
Параллельная обработка файлов без GNU Parallel
▪️ Базовый пример: запуск в фоне
Все файлы сжимаются одновременно - но это нагружает CPU. Хотелось бы ограничить количество потоков, для этого добавим контроль.
▪️ Контроль количества одновременно работающих задач
Классика для bash 4+:
▪️ Ещё лаконичнее через xargs
🌟 Работает даже в старых системах (без wait -n).
▪️ Чуть сложнее: обработка с логированием и статусом
Вывод:
Все просто, читаемо и работает без внешних зависимостей.
BashTex📱 #bash #utils
for f in *.log; do
gzip "$f" &
done
wait
Все файлы сжимаются одновременно - но это нагружает CPU. Хотелось бы ограничить количество потоков, для этого добавим контроль.
Классика для bash 4+:
MAXJOBS=4
for f in *.log; do
gzip "$f" &
(( $(jobs -r | wc -l) >= MAXJOBS )) && wait -n
done
wait
jobs -r- считает активные фоновые процессыwait -n- дожидается завершения одного из них
В итоге максимум 4 задачи одновременно
ls *.log | xargs -n1 -P4 bash -c 'gzip "$0"'
-n1 - по одному аргументу на процесс
-P4 - максимум 4 параллельных процесса
bash -c 'gzip "$0"' - шаблон выполнения
process_file() {
local f="$1"
echo "[START] $f"
sleep $((RANDOM % 5)) # эмуляция нагрузки
echo "[DONE] $f"
}
export -f process_file
ls *.log | xargs -n1 -P3 bash -c 'process_file "$0"'
Вывод:
[START] file1.log
[START] file2.log
[START] file3.log
[DONE] file2.log
[DONE] file1.log
[START] file4.log
[DONE] file3.log
[DONE] file4.log
Все просто, читаемо и работает без внешних зависимостей.
BashTex
Please open Telegram to view this post
VIEW IN TELEGRAM
👍7
Изоляция временных директорий для скриптов
Каждый серьезный скрипт должен оставлять систему чистой после работы. Для этого нужно уметь создавать, использовать и безопасно очищать временные директории, особенно если в них хранятся промежуточные файлы, ключи или результаты сборки.
Многие пишут так:
Это рискованно, потому что:
- Возможна гонка при
- Если скрипт завершится с ошибкой -
- Несколько процессов перетрут файлы друг друга
▪️ Решение: mktemp + trap
▪️ Пример: временное рабочее окружение
При любом исходе (Ctrl+C, ошибка, SIGTERM) директория будет удалена автоматически.
▪️ Уровень выше: временные файлы внутри каталога
Все временные артефакты изолированы в одной папке. После завершения скрипта следов нет.
▪️ Безопасность и права
По умолчанию
Можно задать вручную:
Полезно, если скрипт работает под рутом и обрабатывает конфиденциальные данные.
BashTex📱 #bash #utils
Каждый серьезный скрипт должен оставлять систему чистой после работы. Для этого нужно уметь создавать, использовать и безопасно очищать временные директории, особенно если в них хранятся промежуточные файлы, ключи или результаты сборки.
Многие пишут так:
TMPDIR="/tmp/myscript"
mkdir -p "$TMPDIR"
# ...
rm -rf "$TMPDIR"
Это рискованно, потому что:
- Возможна гонка при
mkdir в /tmp- Если скрипт завершится с ошибкой -
rm -rf не выполнится- Несколько процессов перетрут файлы друг друга
TMPDIR=$(mktemp -d -t myscript.XXXXXX)
trap 'rm -rf "$TMPDIR"' EXIT
mktemp -d- создает уникальную директориюtrap ... EXIT- гарантирует удаление даже при ошибках
Безопасно, изолированно и без коллизий
#!/usr/bin/env bash
set -euo pipefail
TMPDIR=$(mktemp -d -t build.XXXXXX)
trap 'echo "Cleaning $TMPDIR"; rm -rf "$TMPDIR"' EXIT
echo "Workdir: $TMPDIR"
cp -r src/* "$TMPDIR/"
pushd "$TMPDIR" >/dev/null
make all
popd >/dev/null
При любом исходе (Ctrl+C, ошибка, SIGTERM) директория будет удалена автоматически.
TMPDIR=$(mktemp -d)
trap 'rm -rf "$TMPDIR"' EXIT
LOG="$TMPDIR/run.log"
OUT="$TMPDIR/result.txt"
echo "Starting..." > "$LOG"
echo "42" > "$OUT"
Все временные артефакты изолированы в одной папке. После завершения скрипта следов нет.
По умолчанию
mktemp создает директорию с правами 700.Можно задать вручную:
TMPDIR=$(mktemp -d -p /var/tmp myscript.XXXXXX)
chmod 700 "$TMPDIR"
Полезно, если скрипт работает под рутом и обрабатывает конфиденциальные данные.
BashTex
Please open Telegram to view this post
VIEW IN TELEGRAM
👍8
Динамическое создание и использование временных файлов
Иногда хочется или нужно передавать данные между процессами без создания реальных временных файлов: быстро, безопасно и с контролем потока. Вот тут в дело вступают именованные каналы (named pipes, FIFO).
▪️ Пример 1: Обработка данных на лету
Здесь mkfifo создает канал, а один процесс пишет в него, пока другой читает.
Результат - никаких temp-файлов, только живой поток.
▪️ Пример 2: Конвейер с фильтрацией и tee
Это хороший способ подменить лог-файл, не записывая на диск до тех пор, пока не нужно.
▪️ Пример 3: Генерация данных и параллельная обработка
Здесь один процесс производит список файлов, а другой параллельно обрабатывает их.
Нет конфликтов записи, нет tmp-файлов, нет гонок.
🌟 Подводные камни
FIFO блокируется, если один конец не открыт: пока нет читателя, писатель висит.
При множественных писателях стоит использовать lock-механизмы (flock или temp lock-файл).
Не забывай очищать rm "$fifo", иначе в
📌 Когда это реально нужно
При распараллеливании bash-пайплайнов без промежуточных файлов;
Для асинхронных логгеров: один пишет, другой агрегирует;
Для фоново работающих демонов, взаимодействующих через потоки.
BashTex📱 #bash #utils
Иногда хочется или нужно передавать данные между процессами без создания реальных временных файлов: быстро, безопасно и с контролем потока. Вот тут в дело вступают именованные каналы (named pipes, FIFO).
Что это такое
mkfifo создает специальный файл, через который можно организовать двусторонний обмен данными между процессами, без хранения на диске. Потоки читаются и пишутся вживую, а данные не буферизуются, пока другой процесс не откроет противоположную сторону.
#!/usr/bin/env bash
pipe=$(mktemp -u) # создаем уникальное имя
mkfifo "$pipe"
# Пишем данные в FIFO в фоне
{
for i in {1..5}; do
echo "[$(date +%T)] Обработка задачи #$i"
sleep 1
done > "$pipe"
} &
# Читаем и форматируем поток
while read -r line; do
echo ">>> $line"
done < "$pipe"
rm "$pipe"
Здесь mkfifo создает канал, а один процесс пишет в него, пока другой читает.
Результат - никаких temp-файлов, только живой поток.
#!/usr/bin/env bash
fifo=$(mktemp -u)
mkfifo "$fifo"
# Пишем лог в FIFO
{
dmesg | grep "error" > "$fifo"
} &
# Читаем и одновременно сохраняем
tee /tmp/errors.log < "$fifo" | awk '{print toupper($0)}'
rm "$fifo"
Это хороший способ подменить лог-файл, не записывая на диск до тех пор, пока не нужно.
#!/usr/bin/env bash
fifo=$(mktemp -u)
mkfifo "$fifo"
producer() {
for f in *.log; do
echo "$f"
done > "$fifo"
}
consumer() {
while read -r file; do
grep "ERROR" "$file" >> errors_all.txt
done < "$fifo"
}
producer & consumer
wait
rm "$fifo"
Здесь один процесс производит список файлов, а другой параллельно обрабатывает их.
Нет конфликтов записи, нет tmp-файлов, нет гонок.
FIFO блокируется, если один конец не открыт: пока нет читателя, писатель висит.
При множественных писателях стоит использовать lock-механизмы (flock или temp lock-файл).
Не забывай очищать rm "$fifo", иначе в
/tmp может накапливаться мусор.При распараллеливании bash-пайплайнов без промежуточных файлов;
Для асинхронных логгеров: один пишет, другой агрегирует;
Для фоново работающих демонов, взаимодействующих через потоки.
BashTex
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥8
Please open Telegram to view this post
VIEW IN TELEGRAM
😁11👨💻1
Собственная корзина
С помощью bash можно написать собственную корзину и при вводе
▪️ Реализация: базовый пример
Перезапусти терминал и теперь каждый
▪️ Пример использования
А теперь:
Никаких потерь. Можно легко вернуть:
▪️ Расширение: лог и автоочистка. Добавим журнал и автоматическую очистку старых файлов:
Теперь:
Все удаления пишутся в
Старые файлы очищаются автоматически
BashTex📱 #bash #utils
С помощью bash можно написать собственную корзину и при вводе
rm будет не удаление, а перемещение файла + все это будет с датой и логом. + Немного допилив можно сделать корзину в автоочисткой через 30 дней. Будет полезно тем, кто часто удаляет нужное.Идея
Перехватываем вызов rm через алиас или функцию и вместо удаления отправляем файлы в~/.quarantine/YYYY-MM-DD/, чтобы потом можно было восстановить.
# ~/.bashrc или отдельный файл ~/.bash_safe_rm.sh
SAFE_RM_DIR="$HOME/.quarantine"
safe_rm() {
local date_dir="$SAFE_RM_DIR/$(date +%F)"
mkdir -p "$date_dir"
for file in "$@"; do
if [[ -e "$file" ]]; then
local dest="$date_dir/$(basename "$file")_$(date +%H%M%S)"
mv "$file" "$dest"
echo "Moved '$file' -> '$dest'"
else
echo "File not found: $file"
fi
done
}
alias rm='safe_rm'
Перезапусти терминал и теперь каждый
rm file.txt будет просто перемещать файл.
$ echo "test" > /tmp/test.txt
$ rm /tmp/test.txt
Moved '/tmp/test.txt' -> '/home/user/.quarantine/2025-12-02/test.txt_102355'
А теперь:
$ ls ~/.quarantine/2025-12-02/
test.txt_102355
Никаких потерь. Можно легко вернуть:
mv ~/.quarantine/2025-12-02/test.txt_102355 ~/Documents/test.txt
safe_rm() {
local date_dir="$SAFE_RM_DIR/$(date +%F)"
mkdir -p "$date_dir"
local log_file="$SAFE_RM_DIR/deleted.log"
for file in "$@"; do
if [[ -e "$file" ]]; then
local dest="$date_dir/$(basename "$file")_$(date +%H%M%S)"
mv "$file" "$dest"
echo "$(date '+%F %T') | $PWD/$file -> $dest" >> "$log_file"
echo "$file quarantined"
else
echo "File not found: $file"
fi
done
# автоочистка старше 30 дней
find "$SAFE_RM_DIR" -type d -mtime +30 -exec rm -rf {} + 2>/dev/null
}
Теперь:
Все удаления пишутся в
~/.quarantine/deleted.logСтарые файлы очищаются автоматически
BashTex
Please open Telegram to view this post
VIEW IN TELEGRAM
👍7🔥2
Реализация кэша команд
Некоторые команды тратят секунды или даже минуты, например,
Будем сохранять:
результат выполнения команды
время последнего обновления
время жизни кэша (TTL)
И в следующий раз bash просто берет результат из файла, если он не устарел, при этом экономя CPU.
🛠 Пример реализации
▪️ Пример использования
При первом вызове:
А при повторном (в течение 60 сек):
▪️ Очистка старых кэшей
Можно добавить в cron:
Или в саму функцию:
BashTex📱 #bash #utils
Некоторые команды тратят секунды или даже минуты, например,
curl, find, du, git log. Если их результат не меняется часто, зачем выполнять их заново? Можно сделать кэш прямо в bash, без redis и внешних библиотекБудем сохранять:
результат выполнения команды
время последнего обновления
время жизни кэша (TTL)
И в следующий раз bash просто берет результат из файла, если он не устарел, при этом экономя CPU.
# ~/.bash_cache.sh
CACHE_DIR="$HOME/.bash_cache"
mkdir -p "$CACHE_DIR"
# $1 — время жизни (сек), $2 — команда
cache_run() {
local ttl="$1"
shift
local cmd="$*"
local key
key=$(echo "$cmd" | md5sum | awk '{print $1}')
local cache_file="$CACHE_DIR/$key.cache"
local ts_file="$CACHE_DIR/$key.ts"
# если кэш свежий — читаем
if [[ -f "$cache_file" && -f "$ts_file" ]]; then
local ts=$(<"$ts_file")
local now=$(date +%s)
if (( now - ts < ttl )); then
echo "[cache hit] $cmd"
cat "$cache_file"
return 0
fi
fi
# иначе выполняем и обновляем
echo "[cache miss] $cmd"
eval "$cmd" | tee "$cache_file"
date +%s > "$ts_file"
}
# Кэшируем команду на 60 секунд
cache_run 60 "curl -s https://api.github.com/repos/linux/kernel"
При первом вызове:
[cache miss] curl -s https://api.github.com/repos/linux/kernel
{ "id": 2325298, "name": "linux", ... }
А при повторном (в течение 60 сек):
[cache hit] curl -s https://api.github.com/repos/linux/kernel
{ "id": 2325298, "name": "linux", ... }
Можно добавить в cron:
find "$HOME/.bash_cache" -type f -mtime +1 -delete
Или в саму функцию:
(( RANDOM % 10 == 0 )) && find "$CACHE_DIR" -type f -mtime +1 -delete &
BashTex
Please open Telegram to view this post
VIEW IN TELEGRAM
👍9
Загрузка файлов по HTTP через /dev/tcp
Когда на сервере нет
Эта конструкция открывает сетевое соединение прямо из bash, без внешних утилит. Дальше можно писать и читать данные из этого дескриптора как из обычного файла.
▪️ Минимальный пример HTTP-запроса
скрипт откроет TCP-сокет на 80 порту,
отправит минимальный HTTP-запрос,
выведет сырые заголовки и контент ответа.
▪️ Скачивание файла с фильтрацией заголовков
Обычно хочется получить только тело, без HTTP-заголовков. Для этого можно пропустить пустую строку (\r) - границу заголовков:
▪️ HTTPS?
/dev/tcp умеет только чистый TCP, без TLS. Но можно обойтись через openssl s_client:
Да, это немного костыль, но работает даже на минимальных системах без wget/curl.
BashTex📱 #bash #utils
Когда на сервере нет
curl, wget и даже nc, а скачать файл все равно нужно, то тут на помощь приходит встроенный TCP-интерфейс: /dev/tcp/host/port. Bash поддерживает встроенные TCP/UDP-сокеты:exec {fd}>/dev/tcp/bashtex.com/80
Эта конструкция открывает сетевое соединение прямо из bash, без внешних утилит. Дальше можно писать и читать данные из этого дескриптора как из обычного файла.
#!/usr/bin/env bash
HOST="bashtex.com"
PATH="/index.html"
exec 3<>/dev/tcp/$HOST/80
# Отправляем HTTP-запрос
printf "GET $PATH HTTP/1.1\r\nHost: $HOST\r\nConnection: close\r\n\r\n" >&3
# Читаем ответ
while IFS= read -r line <&3; do
echo "$line"
done
exec 3>&-
скрипт откроет TCP-сокет на 80 порту,
отправит минимальный HTTP-запрос,
выведет сырые заголовки и контент ответа.
Обычно хочется получить только тело, без HTTP-заголовков. Для этого можно пропустить пустую строку (\r) - границу заголовков:
#!/usr/bin/env bash
HOST="bashtex.com"
FILE="/file.txt"
OUT="file.txt"
exec 3<>/dev/tcp/$HOST/80
printf "GET $FILE HTTP/1.1\r\nHost: $HOST\r\nConnection: close\r\n\r\n" >&3
# Пропускаем заголовки
while IFS= read -r line <&3; do
[[ $line == $'\r' ]] && break
done
# Сохраняем тело
cat <&3 > "$OUT"
exec 3>&-
echo "Файл сохранён: $OUT"
/dev/tcp умеет только чистый TCP, без TLS. Но можно обойтись через openssl s_client:
exec 3<> >(openssl s_client -connect bashtex.com:443 -quiet)
printf "GET / HTTP/1.1\r\nHost: bashtex.com\r\nConnection: close\r\n\r\n" >&3
cat <&3
Да, это немного костыль, но работает даже на минимальных системах без wget/curl.
BashTex
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥7👍3🗿1
Please open Telegram to view this post
VIEW IN TELEGRAM
😁15👍4