TGViewer
Channel Public Channel
BashTex | Linux

BashTex | Linux

@bash_tex

Авторский канал для тех, кто хочет глубже погрузиться в мир Linux.

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

Реклама: @dad_admin
Subscribers
2.51K
Photos
78
Videos
13
Links
497

Showing posts older than #435 · Back to latest

Older Posts 20 shown
Post #434 1.26K
Распределенный журнал событий через SSH fan-out

Иногда нужно собрать события с группы серверов и посмотреть их как единый хронологический поток: кто что делал, когда, и на каком хосте. Можно сделать это чисто на bash + SSH.

Для этого:

По SSH запускаем команду на каждом сервере (journalctl, tail, свой лог).
Добавляем timestamp + hostname.
Объединяем и сортируем и получаем общий timeline.

▪️ Fan-out по серверам


HOSTS=(srv1 srv2 srv3)

for h in "${HOSTS[@]}"; do
ssh "$h" "journalctl -n 50 --no-pager" | \
awk -v host="$h" '{print strftime("%s"), host, $0}' &
done

wait


▪️ Централизованное объединение и сортировка


{
for h in "${HOSTS[@]}"; do
ssh "$h" "journalctl -n 200 --no-pager" \
| awk -v host="$h" '{print strftime("%s"), host, $0}'
done
} | sort -n > timeline.log


Теперь timeline.log выглядит так:


1712409123 srv1 sshd[1023]: Accepted password for admin
1712409124 srv3 systemd[1]: Started backup.
1712409125 srv2 docker[532]: Container restarted.


BashTex 📱 #bash
  • 👍 7
Post #433 1.18K
Использование socat как универсального канала между скриптами

socat позволяет связать любой источник данных с любым приемником. Bash-скрипты с ним превращаются в гибкие системы обмена сообщениями.

▪️ Bash через UNIX-сокет. Мини-IPC без tmp-файлов и named pipes:

Сервер:


socat UNIX-LISTEN:/tmp/app.sock,fork SYSTEM:'bash handler.sh'


Клиент:


echo "PING" | socat - UNIX-CONNECT:/tmp/app.sock


Отлично подходит для локальных API между скриптами.

2️⃣ Передача данных между серверами (TCP)

Сервер:


socat TCP-LISTEN:9000,fork FILE:/var/log/app.log


Клиент:


socat - TCP:server:9000


Можно в реальном времени стримить логи или метрики.

3️⃣ Замена сложных пайпов. Напрямую соединяем процессы, даже если они не умеют работать с stdin/stdout:


socat EXEC:"cmd1" EXEC:"cmd2"


Аналог cmd1 | cmd2, но без ограничений пайпа (например, поддержка bidirectional).

4️⃣ Двунаправленный канал в скрипте. Например, общение с интерактивным сервисом:


socat - TCP:localhost:8080 | while read l; do
echo ">> $l"
done


Можно строить собственные протоколы поверх TCP.

5️⃣ Быстрый импровизированный RPC

Сервер:


socat TCP-LISTEN:7000,reuseaddr,fork SYSTEM:'bash rpc-handler.sh'


Клиент:


echo '{"cmd":"status"}' | socat - TCP:host:7000


BashTex 📱 #bash #utils
  • 🔥 5
  • 👍 2
Post #432 1.14K
Сложные пайпы в bash

Иногда нужно одновременно: подавать данные в программу, анализировать ее вывод, логировать поток, фильтровать часть данных и все это без временных файлов. Такое легко делается через tee + process substitution.

▪️ Базовый шаблон


cat input.log \
| cmd \
| tee >(grep "ERROR" > errors.log) \
| awk '{print $1, $3}'


cmd принимает stdin от cat;
tee дублирует поток: в файл-поток >(grep ...) и дальше в awk;
grep работает параллельно, не мешая основному пайпу;
и при этом нет temp-файлов, все идёт по fd

▪️ Пример 1: анализ, логирование и фильтрация сразу. Извлекаем ошибки в отдельный файл, а общий вывод обрабатываем:


journalctl -u nginx \
| tee >(grep 500 >500.log) \
| awk '/request/ {print $NF}'


▪️ Пример 2: split-поток для алертов и статистики


tail -F app.log \
| tee >(grep CRITICAL | notify-send "ALERT!") \
| awk '{count[$5]++} END {for (i in count) print i, count[i]}'


▪️ Пример 3: читаем и пишем в интерактивный процесс. Двунаправленная работа с программой:


coproc APP { cmd --interactive; }

echo "status" >&"${APP[1]}"

cat <&"${APP[0]}" \
| tee >(grep ALERT >alerts.log) \
| awk '{print "OUT:", $0}'


Работает как мини-оркестратор для интерактивного ввода/вывода.

BashTex 📱 #bash #utils
  • 👍 10
Post #430 1.25K
Сравнение authorized_keys между серверами

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

▪️ Базовый прием: сравнение двух серверов


diff <(ssh h1 "sort ~/.ssh/authorized_keys") \
<(ssh h2 "sort ~/.ssh/authorized_keys")


Сразу видно какие ключи есть только на h1 и какие есть только на h2. Строки отличаются полностью, поэтому легко понять лишние.

▪️ Сравнение директорий для всех пользователей


compare_keys() {
user="$1"
diff <(ssh "$2" "sort /home/$user/.ssh/authorized_keys 2>/dev/null") \
<(ssh "$3" "sort /home/$user/.ssh/authorized_keys 2>/dev/null")
}


Использование:


compare_keys deploy h1 h2
compare_keys admin h1 h2


▪️ Мультисерверная проверка списка хостов


hosts=(h1 h2 h3)
base="h1"

for h in "${hosts[@]:1}"; do
echo "=== $base vs $h ==="
diff <(ssh "$base" "sort ~/.ssh/authorized_keys") \
<(ssh "$h" "sort ~/.ssh/authorized_keys")
done


Покажет отличия между эталоном и всеми остальными.

▪️ Поиск лишних ключей


ssh h1 "sort ~/.ssh/authorized_keys" \
| grep -vFf <(ssh h2 "sort ~/.ssh/authorized_keys")


Покажет ключи, присутствующие только на h1.

BashTex 📱 #bash #utils
  • 👍 6
Post #429 1.3K
Сбор данных с нескольких серверов


<(ssh host1 cmd) vs <(ssh host2 cmd)


Конструкция позволяет сравнивать состояние двух (или нескольких) машин без временных файлов, параллельно и максимально быстро.

Process substitution создает виртуальные файлы, в которые пишется вывод команд. SSH-запросы выполняются параллельно, а diff думает, что сравнивает два обычных файла:


diff <(ssh h1 cmd) <(ssh h2 cmd)


▪️ Лучшие практические примеры

▪️ Сравнение пакетов


diff <(ssh h1 "dpkg -l | sort") \
<(ssh h2 "dpkg -l | sort")


▪️ Конфиги (sshd_config)


diff -u <(ssh h1 "grep -v '^#' /etc/ssh/sshd_config") \
<(ssh h2 "grep -v '^#' /etc/ssh/sshd_config")


▪️ Сетевые соединения


diff <(ssh h1 "ss -tuna | sort") \
<(ssh h2 "ss -tuna | sort")


▪️ Docker-контейнеры


diff <(ssh h1 "docker ps --format '{{.Names}} {{.Image}}' | sort") \
<(ssh h2 "docker ps --format '{{.Names}} {{.Image}}' | sort")


▪️ Мини-функция


compare_hosts() {
diff <(ssh "$1" "$3") <(ssh "$2" "$3")
}


Использование:


compare_hosts node1 node2 "df -h"


BashTex 📱 #bash #utils
  • 👍 12
Post #428 1.26K
Сны при температуре 40

BashTex 📱 #юмор
  • 😁 15
Post #427 1.48K
Резервное копирование Docker volumes без остановки контейнеров

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

1️⃣ Проблема горячих бэкапов

Если контейнер пишет в volume во время архивации:

файлы могут быть разорваны,
часть транзакций попадет в архив, а часть нет,
каталог может измениться, пока его читает tar.

Чтобы избежать этого, нужен мягкий снапшот: заморозить структуру, дождаться закрытия файлов и архивировать.

Мы это реализуем через:

проверку открытых дескрипторов lsof
копию структуры через freeze-режим
tar с фиксированным списком файлов

2️⃣ Определяем volume и контейнер


VOLUME="mydata"
BACKUP_DIR="/backups"
STAMP=$(date +%Y%m%d-%H%M)
TARGET="$BACKUP_DIR/$VOLUME-$STAMP.tar.gz"

MOUNTPOINT=$(docker volume inspect "$VOLUME" -f '{{.Mountpoint}}')


3️⃣ Ждем, пока контейнер перестанет писать. Контейнер может продолжать работать, но нам важно дождаться момента тишины.


wait_until_quiet() {
local path="$1"
local delay=${2:-1}

while lsof +D "$path" >/dev/null 2>&1; do
echo "[*] Volume busy, waiting..."
sleep "$delay"
done
}


Это не блокирует процесс, просто ждёт микропаузу между транзакциями.

4️⃣ Создаем снапшот список файлов

Важно: tar должен использовать фиксированный список, а не динамическое дерево.


SNAPLIST=$(mktemp)

find "$MOUNTPOINT" -type f -o -type d | sed "s#^$MOUNTPOINT/##" > "$SNAPLIST"


Так мы замораживаем текущее состояние структуры.

5️⃣ Архивация без остановки контейнера


echo "[*] Waiting for files to become quiet..."
wait_until_quiet "$MOUNTPOINT"

echo "[*] Creating hot-backup: $TARGET"
tar -czf "$TARGET" \
-C "$MOUNTPOINT" \
-T "$SNAPLIST"


Файлы читаются быстрее, чем контейнер успевает их поменять, а вероятность гонки почти нулевая, потому что мы:

дождались отсутствия открытых дескрипторов
зафиксировали список файлов заранее
используем tar с указанием точных путей

6️⃣ Опционально: перезапрос тишины перед чтением больших файлов

Для больших БД/логов:


pause_if_lock() {
local file="$1"
while lsof "$file" >/dev/null 2>&1; do
echo "[*] File still in use: $file"
sleep 0.5
done
}


И внутри цикла архивации:


while read -r f; do
pause_if_lock "$MOUNTPOINT/$f"
done < "$SNAPLIST"


Это максимально приближает бэкап к снапшот-принципам.

7️⃣ Очистка


rm -f "$SNAPLIST"
echo "[+] Backup completed: $TARGET"


8️⃣ Пример полностью готового скрипта. Минимальный файл backup_volume.sh:


#!/usr/bin/env bash
set -euo pipefail

VOLUME="$1"
BACKUP_DIR="/backups"
STAMP=$(date +%Y%m%d-%H%M)
TARGET="$BACKUP_DIR/$VOLUME-$STAMP.tar.gz"

MOUNTPOINT=$(docker volume inspect "$VOLUME" -f '{{.Mountpoint}}')

wait_until_quiet() {
while lsof +D "$1" >/dev/null 2>&1; do
sleep 1
done
}

echo "[*] Volume mountpoint: $MOUNTPOINT"

SNAPLIST=$(mktemp)
find "$MOUNTPOINT" -mindepth 1 -printf '%P\n' > "$SNAPLIST"

echo "[*] Waiting for quiet state..."
wait_until_quiet "$MOUNTPOINT"

echo "[*] Archiving..."
tar -czf "$TARGET" -C "$MOUNTPOINT" -T "$SNAPLIST"

rm -f "$SNAPLIST"
echo "[+] Done: $TARGET"


BashTex 📱 #bash
  • 👍 8
Post #426 1.1K
Генерация unit файлов из шаблонов

Если у вас десятки сервисов, работающих по схожей схеме (например, несколько python или node-приложений), руками плодить unit-файлы в /etc/systemd/system - сомнительное удовольствие. Правильнее и логичнее будет автоматизировать путем генерирации юнитов из шаблонов через bash.

▪️ Простейший шаблон. Создаем service.tpl:


[Unit]
Description={{NAME}} service
After=network.target

[Service]
Type=simple
User={{USER}}
WorkingDirectory={{WORKDIR}}
ExecStart={{EXEC}}
Restart=always

[Install]
WantedBy=multi-user.target


▪️ Генерация через bash


#!/bin/bash
set -euo pipefail

TEMPLATE="./service.tpl"
OUTPUT_DIR="/etc/systemd/system"

generate_unit() {
local name="$1" user="$2" workdir="$3" exec="$4"
local outfile="$OUTPUT_DIR/${name}.service"

sed \
-e "s|{{NAME}}|$name|g" \
-e "s|{{USER}}|$user|g" \
-e "s|{{WORKDIR}}|$workdir|g" \
-e "s|{{EXEC}}|$exec|g" \
"$TEMPLATE" > "$outfile"

echo "Сервис $name создан: $outfile"
}



# Пример использования
generate_unit "myapp" "deploy" "/opt/myapp" "/opt/myapp/venv/bin/python app.py"

systemctl daemon-reload
systemctl enable myapp --now


▪️ Массовая генерация из списка. Храним параметры сервисов в CSV:


name,user,workdir,exec
app1,deploy,/opt/app1,/opt/app1/start.sh
app2,deploy,/opt/app2,/usr/bin/python3 /opt/app2/run.py


А bash все развернет:


#!/bin/bash
while IFS=, read -r name user workdir exec; do
[[ "$name" == "name" ]] && continue # пропускаем заголовок
generate_unit "$name" "$user" "$workdir" "$exec"
done < services.csv


BashTex 📱 #bash #utils
  • 👍 6
  • 🤨 1
Post #424 1.21K
Локальные снапшоты каталога

Одна из интересных и полезных идей для реализации: сделать машину времени для каталога т.е. реализовать сценарий при котором возможно вернуться к состоянию вчера/час назад/неделю назад (без btrfs, zfs или lvm)

Решение: rsync + hardlinks

Это ближе всех к btrfs/ZFS-снапшотам в мире обычных файловых систем (ext4/xfs).

▪️ Структура:


/backups/
2025-12-05-12:00/
2025-12-05-13:00/
2025-12-05-14:00/


Каждый каталог - полноценная копия состояния.

🛠 Мини-скрипт


#!/usr/bin/env bash
set -e

SRC="/home/user/project"
DST="/backups"

TS=$(date +"%Y-%m-%d-%H:%M")
LAST="$DST/latest"
NEW="$DST/$TS"

mkdir -p "$DST"

if [[ -d "$LAST" ]]; then
echo "[i] Using hardlinks from: $LAST"
rsync -a --delete --link-dest="$LAST" "$SRC/" "$NEW/"
else
echo "[i] First snapshot — no link-dest"
rsync -a --delete "$SRC/" "$NEW/"
fi

# Update the "latest" symlink
rm -f "$LAST"
ln -s "$(basename "$NEW")" "$LAST"

echo "[i] Snapshot created: $NEW"



rsync -a —link-dest=... - команда говорит сравни каталог с предыдущим снапшотом и все неизмененные файлы сделай через hardlink. Это не копия, а именно вторая ссылка на тот же inode. Место: +0 байт.
--delete - удаленные файлы тоже исчезнут из новой точки — snapshot становится точным состоянием дерева в данный момент.
Symlink latest - позволяет быстро создавать следующую точку, не перебирая каталоги.

▪️ Восстановление состояния каталога. Чтобы вернуть старую версию:


rsync -a /backups/2025-12-05-12:00/ /home/user/project/


Либо наоборот: перейти в каталог снапшота и увидеть все как было.

▪️ Автоматизация. Например, снимок каждый час:


0 * * * * /usr/local/bin/snapshot.sh


▪️ Полезные дополнения

1️⃣ Автопургатор (ретеншн-политика)


find /backups -maxdepth 1 -type d -mtime +30 -exec rm -rf {} \;


Удалить снапшоты старше 30 дней.

2️⃣ Игнорирование временных файлов. Создай .rsync-filter:


- *.tmp
- .cache/


И вызывай rsync так:


rsync -a --filter='. .rsync-filter' ...


BashTex 📱 #bash #utils
  • 👍 10
Post #422 1.23K
Централизованный сбор логов со всех серверов по SSH

Когда в инфраструктуре 3 и более серверов, необходимость собирать логи в одно место становится актуальной. Не всем необходимо разворачивать полноценный ELK/Graylog. Может быть достаточно скрипта, который собирает логи по SSH и приводит их к единому формату.

Сегодня покажу, как сделать легкую систему централизованного логирования на rsync + journalctl, без агентов, демонов и тяжелых сервисов.

Допустим:

Каждый наш сервер умеет отдавать свои логи через:

journalctl --since --until - системные логи
/var/log/ - классические текстовые логи

Мы создаем на главном сервере каталог:


/var/log/central/
├── server1/
├── server2/
├── server3/
└── ...


И напрягаем bash забирать туда свежие логи по расписанию.

Что будем собирать:

1) systemd-журналы:


journalctl --since "1 hour ago" --output=short-iso


2) классические логи:


/var/log/*.log
/var/log/nginx/
/var/log/syslog*
/var/log/auth.log*


🛠 Скрипт централизованного сбора логов


#!/usr/bin/env bash
set -euo pipefail

# Список серверов
SERVERS=("srv1" "srv2" "srv3")
USER="logcollector"
DEST="/var/log/central"
SINCE="1 hour ago"

mkdir -p "$DEST"

for host in "${SERVERS[@]}"; do
echo "[+] Сбор логов с $host"

HOST_DIR="$DEST/$host"
mkdir -p "$HOST_DIR"

# 1) journalctl → локальный файл
ssh "$USER@$host" \
"journalctl --since \"$SINCE\" --output=short-iso" \
> "$HOST_DIR/journal.log"

# 2) rsync логов
rsync -az --delete \
"$USER@$host:/var/log/" \
"$HOST_DIR/textlogs/"
done


ssh "journalctl …" - Мы вызываем journalctl прямо на удаленной машине и как итог лог формируется быстро и передается по SSH. Формат short-iso нужен, чтобы было удобно анализировать.

rsync -az --delete

-a - сохраняет структуру, права
-z - сжатие
--delete - удаляет локальные файлы, которых уже нет на сервере (чтобы каталог не рос бесконечно)

Структура в итоге выглядит так:


/var/log/central/srv1/
├── journal.log
└── textlogs/
├── syslog
├── auth.log
├── nginx/access.log
└── ...


▪️ Версия с датой. Добавим архивирование и метку времени:


TS=$(date +%F_%H-%M)

ssh "$USER@$host" \
"journalctl --since \"$SINCE\" --output=short-iso" \
> "$HOST_DIR/journal_$TS.log"

rsync -az "$USER@$host:/var/log/" "$HOST_DIR/textlogs_$TS/"


Можно затем сделать cron-задачу:


0 * * * * /usr/local/bin/collect-logs.sh


BashTex 📱 #bash
  • 👍 9
Post #421 1.98K
Сынок, ПО должно ставиться бесплатно

BashTex 📱 #юмор
  • 😁 19
  • 👍 5
Post #419 1.22K
Загрузка файлов по HTTP через /dev/tcp

Когда на сервере нет curl, wget и даже nc, а скачать файл все равно нужно, то тут на помощь приходит встроенный TCP-интерфейс: /dev/tcp/host/port. Bash поддерживает встроенные TCP/UDP-сокеты:

exec {fd}>/dev/tcp/bashtex.com/80


Эта конструкция открывает сетевое соединение прямо из bash, без внешних утилит. Дальше можно писать и читать данные из этого дескриптора как из обычного файла.

▪️ Минимальный пример HTTP-запроса

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


▪️ HTTPS?

/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 📱 #bash #utils
  • 🔥 7
  • 👍 3
  • 🗿 1
Post #417 1.04K
Реализация кэша команд

Некоторые команды тратят секунды или даже минуты, например, 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 📱 #bash #utils
  • 👍 9
Post #416 1.17K
Собственная корзина

С помощью 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 📱 #bash #utils
  • 👍 8
  • 🔥 2
Post #414 1.14K
Пс, парень, давай поговорим о лучших дистрибутивах

BashTex 📱 #юмор
  • 😁 11
  • 👨‍💻 1
Post #413 1.15K
Динамическое создание и использование временных файлов

Иногда хочется или нужно передавать данные между процессами без создания реальных временных файлов: быстро, безопасно и с контролем потока. Вот тут в дело вступают именованные каналы (named pipes, FIFO).

Что это такое

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


▪️ Пример 1: Обработка данных на лету


#!/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-файлов, только живой поток.

▪️ Пример 2: Конвейер с фильтрацией и tee


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


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

▪️ Пример 3: Генерация данных и параллельная обработка


#!/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 📱 #bash #utils
  • 🔥 8
Post #412 1.06K
Изоляция временных директорий для скриптов

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

Многие пишут так:


TMPDIR="/tmp/myscript"
mkdir -p "$TMPDIR"
# ...
rm -rf "$TMPDIR"


Это рискованно, потому что:

- Возможна гонка при mkdir в /tmp
- Если скрипт завершится с ошибкой - rm -rf не выполнится
- Несколько процессов перетрут файлы друг друга

▪️ Решение: mktemp + trap


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 📱 #bash #utils
  • 👍 10
Post #411 922
Параллельная обработка файлов без GNU Parallel

▪️ Базовый пример: запуск в фоне


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 задачи одновременно


▪️ Ещё лаконичнее через xargs


ls *.log | xargs -n1 -P4 bash -c 'gzip "$0"'


-n1 - по одному аргументу на процесс
-P4 - максимум 4 параллельных процесса
bash -c 'gzip "$0"' - шаблон выполнения


🌟 Работает даже в старых системах (без wait -n).

▪️ Чуть сложнее: обработка с логированием и статусом


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 📱 #bash #utils
  • 👍 8
Post #410 1.23K
Еще и с виртуалки..

BashTex 📱 #юмор
  • 😁 21
  • 👍 3
Post #408 1.39K
Захват переменных окружения из другой сессии

Иногда нужно подцепить окружение работающего процесса: например, чтобы повторить его контекст, восстановить переменные или понять, с какими параметрами запущен сервис.

Обычно это делают через export или env, но можно достать переменные прямо из /proc, без доступа к shell-сессии.

▪️ Чтение окружения процесса. Каждый процесс в Linux хранит свои переменные в:


/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


▪️ Захват и использование в текущей сессии. Если нужно подгрузить это окружение в текущий bash:


PID=1234
eval "$(tr '\0' '\n' < /proc/$PID/environ | sed 's/^/export /')"


Теперь ты в том же окружении, что и процесс $PID.

Будет полезно, если:

хочешь повторить окружение демона (nginx, systemd, custom app);
нужно пересоздать контекст для отладки;
запускаешь скрипт от того же пользователя, что и процесс.


▪️ Извлечение конкретной переменной. Например, достанем PATH:


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


▪️ Захват окружения systemd-сервисов. Systemd не всегда передает окружение дальше, но его можно вытащить по PID активного процесса:


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

🌟 Доступно только для процессов, владельцем которых ты являешься (или root).

BashTex 📱 #bash #utils
  • 👍 7
Older posts →
Threads Profile ViewerView any public Threads profile without an account.Open ThreadLook →Writing with AI? Make it sound human.Metric37 rewrites AI drafts so they read naturally. Free AI detector, 1,500 words free.Try Metric37 →