Вставка последнего аргумента без копипасты
Сколько раз вы выполняли команду, вроде:
а потом через секунду нужно:
и снова набираете руками путь или выделяете путь?
Есть способ не повторять последнее слово - 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/mynoscript"
mkdir -p "$TMPDIR"
# ...
rm -rf "$TMPDIR"
Это рискованно, потому что:
- Возможна гонка при
mkdir в /tmp- Если скрипт завершится с ошибкой -
rm -rf не выполнится- Несколько процессов перетрут файлы друг друга
TMPDIR=$(mktemp -d -t mynoscript.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 mynoscript.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
👍8🔥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
😁17👍5
Централизованный сбор логов со всех серверов по SSH
Когда в инфраструктуре 3 и более серверов, необходимость собирать логи в одно место становится актуальной. Не всем необходимо разворачивать полноценный ELK/Graylog. Может быть достаточно скрипта, который собирает логи по SSH и приводит их к единому формату.
Сегодня покажу, как сделать легкую систему централизованного логирования на
Допустим:
Каждый наш сервер умеет отдавать свои логи через:
Мы создаем на главном сервере каталог:
И напрягаем bash забирать туда свежие логи по расписанию.
Что будем собирать:
1) systemd-журналы:
2) классические логи:
🛠 Скрипт централизованного сбора логов
-a - сохраняет структуру, права
-z - сжатие
--delete - удаляет локальные файлы, которых уже нет на сервере (чтобы каталог не рос бесконечно)
Структура в итоге выглядит так:
▪️ Версия с датой. Добавим архивирование и метку времени:
Можно затем сделать cron-задачу:
BashTex📱 #bash
Когда в инфраструктуре 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
Please open Telegram to view this post
VIEW IN TELEGRAM
👍7
Используй предстоящие новогодние выходные наилучшим образом! Изучай новые технологии или закрой пробелы в знаниях по своему стеку.
Стань экспертом в следующих направлениях:
• Системное администрирование
• Информационная безопасность
• Сетевое администрирование
• Этичный хакинг
Ссылка для своих: https://news.1rj.ru/str/admbooks
Please open Telegram to view this post
VIEW IN TELEGRAM
👍1
Локальные снапшоты каталога
Одна из интересных и полезных идей для реализации: сделать машину времени для каталога т.е. реализовать сценарий при котором возможно вернуться к состоянию вчера/час назад/неделю назад (без btrfs, zfs или lvm)
Решение: rsync + hardlinks
Это ближе всех к btrfs/ZFS-снапшотам в мире обычных файловых систем (ext4/xfs).
▪️ Структура:
Каждый каталог - полноценная копия состояния.
🛠 Мини-скрипт
▪️ Восстановление состояния каталога. Чтобы вернуть старую версию:
Либо наоборот: перейти в каталог снапшота и увидеть все как было.
▪️ Автоматизация. Например, снимок каждый час:
▪️ Полезные дополнения
1️⃣ Автопургатор (ретеншн-политика)
Удалить снапшоты старше 30 дней.
2️⃣ Игнорирование временных файлов. Создай
И вызывай rsync так:
BashTex📱 #bash #utils
Одна из интересных и полезных идей для реализации: сделать машину времени для каталога т.е. реализовать сценарий при котором возможно вернуться к состоянию вчера/час назад/неделю назад (без 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
find /backups -maxdepth 1 -type d -mtime +30 -exec rm -rf {} \;
Удалить снапшоты старше 30 дней.
.rsync-filter:
- *.tmp
- .cache/
И вызывай rsync так:
rsync -a --filter='. .rsync-filter' ...
BashTex
Please open Telegram to view this post
VIEW IN TELEGRAM
👍7
Привет. Вот тебе самые топовые каналы по IT!
⚙️ Free Znanija (IT) — Самая огромная коллекция платных курсов, которые можно скачать бесплатно;
👩💻 IT Books — Самая огромная библиотека книг;
💻 Hacking & InfoSec Base — Крутой блог белого хакера;
🛡 CyberGuard — Всё про ИБ;
🤔 ИБ Вакансии — Всё, чтобы найти работу в ИБ;
👩💻 linux administration — Всё про Линукс;
👩💻 Программистика — Python, python и ещё раз python;
👩💻 GameDev Base — Всё про GameDev;
😆 //code — Самые топовые мемы по IT:
Подпишись, чтобы не потерять!
Подпишись, чтобы не потерять!
Please open Telegram to view this post
VIEW IN TELEGRAM
Генерация unit файлов из шаблонов
Если у вас десятки сервисов, работающих по схожей схеме (например, несколько python или node-приложений), руками плодить unit-файлы в
▪️ Простейший шаблон. Создаем
▪️ Генерация через bash
▪️ Массовая генерация из списка. Храним параметры сервисов в CSV:
А bash все развернет:
BashTex📱 #bash #utils
Если у вас десятки сервисов, работающих по схожей схеме (например, несколько python или node-приложений), руками плодить unit-файлы в
/etc/systemd/system - сомнительное удовольствие. Правильнее и логичнее будет автоматизировать путем генерирации юнитов из шаблонов через bash.service.tpl:
[Unit]
Denoscription={{NAME}} service
After=network.target
[Service]
Type=simple
User={{USER}}
WorkingDirectory={{WORKDIR}}
ExecStart={{EXEC}}
Restart=always
[Install]
WantedBy=multi-user.target
#!/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
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
Please open Telegram to view this post
VIEW IN TELEGRAM
👍4
Резервное копирование Docker volumes без остановки контейнеров
Когда в контейнере крутится прод, остановка ради бэкапа будет невозможной роскошью. Но docker volumes можно сохранять, без остановки, если правильно защититься от: открытых файлов, неконсистентных записей и гонок при чтении.
1️⃣ Проблема горячих бэкапов
Если контейнер пишет в volume во время архивации:
файлы могут быть разорваны,
часть транзакций попадет в архив, а часть нет,
каталог может измениться, пока его читает tar.
Чтобы избежать этого, нужен мягкий снапшот: заморозить структуру, дождаться закрытия файлов и архивировать.
Мы это реализуем через:
проверку открытых дескрипторов lsof
копию структуры через freeze-режим
tar с фиксированным списком файлов
2️⃣ Определяем volume и контейнер
3️⃣ Ждем, пока контейнер перестанет писать. Контейнер может продолжать работать, но нам важно дождаться момента тишины.
Это не блокирует процесс, просто ждёт микропаузу между транзакциями.
4️⃣ Создаем снапшот список файлов
Важно: tar должен использовать фиксированный список, а не динамическое дерево.
Так мы замораживаем текущее состояние структуры.
5️⃣ Архивация без остановки контейнера
Файлы читаются быстрее, чем контейнер успевает их поменять, а вероятность гонки почти нулевая, потому что мы:
дождались отсутствия открытых дескрипторов
зафиксировали список файлов заранее
используем tar с указанием точных путей
6️⃣ Опционально: перезапрос тишины перед чтением больших файлов
Для больших БД/логов:
И внутри цикла архивации:
Это максимально приближает бэкап к снапшот-принципам.
7️⃣ Очистка
8️⃣ Пример полностью готового скрипта. Минимальный файл backup_volume.sh:
BashTex📱 #bash
Когда в контейнере крутится прод, остановка ради бэкапа будет невозможной роскошью. Но docker volumes можно сохранять, без остановки, если правильно защититься от: открытых файлов, неконсистентных записей и гонок при чтении.
Если контейнер пишет в volume во время архивации:
файлы могут быть разорваны,
часть транзакций попадет в архив, а часть нет,
каталог может измениться, пока его читает tar.
Чтобы избежать этого, нужен мягкий снапшот: заморозить структуру, дождаться закрытия файлов и архивировать.
Мы это реализуем через:
проверку открытых дескрипторов lsof
копию структуры через freeze-режим
tar с фиксированным списком файлов
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}}')
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
}
Это не блокирует процесс, просто ждёт микропаузу между транзакциями.
Важно: tar должен использовать фиксированный список, а не динамическое дерево.
SNAPLIST=$(mktemp)
find "$MOUNTPOINT" -type f -o -type d | sed "s#^$MOUNTPOINT/##" > "$SNAPLIST"
Так мы замораживаем текущее состояние структуры.
echo "[*] Waiting for files to become quiet..."
wait_until_quiet "$MOUNTPOINT"
echo "[*] Creating hot-backup: $TARGET"
tar -czf "$TARGET" \
-C "$MOUNTPOINT" \
-T "$SNAPLIST"
Файлы читаются быстрее, чем контейнер успевает их поменять, а вероятность гонки почти нулевая, потому что мы:
дождались отсутствия открытых дескрипторов
зафиксировали список файлов заранее
используем tar с указанием точных путей
Для больших БД/логов:
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"
Это максимально приближает бэкап к снапшот-принципам.
rm -f "$SNAPLIST"
echo "[+] Backup completed: $TARGET"
#!/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
Please open Telegram to view this post
VIEW IN TELEGRAM