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

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

Реклама: @dad_admin
Download Telegram
Замена части файла через временный буфер

Иногда sed слишком грубый инструмент: нужны условия, состояние, счетчики. В таких случаях проще пройтись по файлу построчно и собрать новый контент вручную.

▪️ Задача

Заменить блок между маркерами # BEGIN и # END, не трогая остальной файл.

▪️ Идея

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

🛠 Реализация


in_block=0
tmp=$(mktemp)

while IFS= read -r line; do
case "$line" in
"# BEGIN"*)
in_block=1
echo "$line" >>"$tmp"
echo "new content line 1" >>"$tmp"
echo "new content line 2" >>"$tmp"
;;
"# END"*)
in_block=0
echo "$line" >>"$tmp"
;;
*)
(( in_block )) || echo "$line" >>"$tmp"
;;
esac
done < config.conf

mv "$tmp" config.conf


▪️ Что здесь важно

IFS= read -r - сохраняет пробелы и \
mktemp - безопасный временный файл
mv - атомарная замена (без битых конфигов)

BashTex 📱 #bash #utils
Please open Telegram to view this post
VIEW IN TELEGRAM
👍5
Мониторинг логов с уведомлениями

Обычный tail -f - это пассивный просмотр. Но его легко превратить в активный мониторинг, который сам уведомит вас о проблеме.

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

▪️ Простой пример


LOG="/var/log/myapp/error.log"
PATTERN="ERROR|CRITICAL|FATAL"

tail -F "$LOG" | grep --line-buffered -E "$PATTERN" | while read -r line; do
echo "[$(date)] $line" | mail -s "Ошибка в логе" admin@bashtex.com
done


▪️ Десктоп-уведомление


tail -F "$LOG" | grep --line-buffered "ERROR" | while read -r line; do
notify-send "Ошибка в логе" "$line"
done


▪️ Полезные детали

-F - переживает logrotate и пересоздание файла
--line-buffered - мгновенная реакция, без задержек
grep -E - несколько шаблонов сразу
while read - обработка построчно без потери данных

BashTex 📱 #bash #utils
Please open Telegram to view this post
VIEW IN TELEGRAM
👍7
Автоочистка логов по размеру и времени

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

▪️ Логика

логи старше N дней перемещаем в .gz;
логи больше X МБ убираем в архив или удаление;
все работает без logrotate;

🛠 Скрипт


LOG_DIR="/var/log/myapp"
MAX_DAYS=7
MAX_SIZE_MB=100

find "$LOG_DIR" -type f -name "*.log" | while read -r file; do
size_mb=$(du -m "$file" | awk '{print $1}')

# Архивируем старые логи
if [[ $(find "$file" -mtime +$MAX_DAYS) ]]; then
gzip -f "$file"
continue
fi

# Архивируем слишком большие
if (( size_mb > MAX_SIZE_MB )); then
gzip -c "$file" > "${file}.gz"
: > "$file" # обнуляем без удаления
fi
done


▪️ Что здесь важно

find -mtime - контроль по времени
du -m - размер в мегабайтах
: > file - безопасное обнуление без потери inode
gzip -c - архив без удаления исходника

BashTex 📱 #bash #utils
Please open Telegram to view this post
VIEW IN TELEGRAM
👍8
Одной тайной меньше

BashTex 📱 #юмор
Please open Telegram to view this post
VIEW IN TELEGRAM
😁16👍1
Автоматическая очистка старых бэкапов по количеству копий

▪️ Исходные условия

Все бэкапы лежат в одном каталоге;
Имена содержат дату или время (важно для сортировки);
Нужно оставить, например, 7 последних копий.

🛠 Простой, но надежный скрипт


#!/bin/bash

BACKUP_DIR="/backups"
KEEP=7

cd "$BACKUP_DIR" || exit 1

ls -1t | tail -n +$((KEEP + 1)) | while read -r old_backup; do
echo "Удаляю старый бэкап: $old_backup"
rm -rf -- "$old_backup"
done


ls -1t - сортирует файлы по времени изменения (новые сверху)
tail -n +$((KEEP + 1)) - выбирает всё после N первых
rm -rf -- - безопасно удаляет, даже если имя начинается с -


▪️ Полезные улучшения

Проверка без удаления:


ls -1t | tail -n +$((KEEP + 1))


Фильтрация только нужных бэкапов:


ls -1t backup-* | tail -n +$((KEEP + 1))


Логирование:


rm -rf -- "$old_backup" >> /var/log/backup-cleanup.log 2>&1


BashTex 📱 #bash #utils
Please open Telegram to view this post
VIEW IN TELEGRAM
👍6
Проверка доступности DNS и fallback на другой сервер

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

🛠 Базовый скрипт


#!/bin/bash

TEST_DOMAIN="google.com"
PRIMARY_DNS="8.8.8.8"
FALLBACK_DNS="1.1.1.1"
RESOLV="/etc/resolv.conf"
BACKUP="/tmp/resolv.conf.bak"

if dig +time=2 +tries=1 "$TEST_DOMAIN" @"$PRIMARY_DNS" > /dev/null; then
echo "DNS доступен"
exit 0
fi

echo "DNS недоступен, переключаемся на fallback"

cp "$RESOLV" "$BACKUP"

cat > "$RESOLV" <<EOF
nameserver $FALLBACK_DNS
EOF


dig - быстрая проверка реального DNS-запроса
+time и +tries - не зависаем надолго
resolv.conf сохраняется перед изменением


▪️ Автовосстановление (по cron)


if dig "$TEST_DOMAIN" @"$PRIMARY_DNS" > /dev/null && [ -f "$BACKUP" ]; then
mv "$BACKUP" "$RESOLV"
fi


⚠️ Обратить внимание

В системах с systemd-resolved или NetworkManager resolv.conf может перезаписываться;
Для серверов лучше использовать resolvectl или override конфигурации;
Скрипт требует root.

BashTex 📱 #bash
Please open Telegram to view this post
VIEW IN TELEGRAM
👍5
Проверка существования команды и версии утилиты

Скрипты часто падают не из-за логики, а из-за отсутствующей команды или неподходящей версии утилиты. Хорошая практика для часто переезжающих скриптов - проверять наличие команды до выполнения основной логики и иметь fallback.

▪️ Базовая проверка: есть ли команда


if ! command -v curl >/dev/null 2>&1; then
echo "curl не найден"
exit 1
fi


command -v в данном случае:

быстрее и безопаснее which;
корректно работает с alias и builtins;
не пишет лишнего в stdout.

▪️ Проверка с fallback


if command -v curl >/dev/null; then
DL="curl -fsSL"
elif command -v wget >/dev/null; then
DL="wget -qO-"
else
echo "Нет curl или wget"
exit 1
fi

$DL https://bashtex.com


Один интерфейс - разные реализации.

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


REQ="7.60"

CUR=$(curl -V | awk '{print $2}')

if [ "$(printf '%s\n' "$REQ" "$CUR" | sort -V | head -n1)" != "$REQ" ]; then
echo "curl слишком старый: $CUR"
exit 1
fi


sort -V корректно сравнивает версии (7.9 < 7.10).

▪️ Универсальная функция


require_cmd() {
command -v "$1" >/dev/null || {
echo "Требуется команда: $1"
exit 1
}
}


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


require_cmd awk
require_cmd tar


BashTex 📱 #bash #utils
Please open Telegram to view this post
VIEW IN TELEGRAM
👍9
Поиск файлов, измененных за последние 24 часа

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

▪️ Базовый вариант

find /path -type f -mtime -1


-mtime -1 - файлы, измененные менее суток назад. Работает по времени изменения содержимого (mtime)

▪️ Только конфиги или нужные расширения

find /etc -type f -mtime -1 \( -name "*.conf" -o -name "*.cfg" \)


Удобно после апдейтов или ручных правок.

▪️ С деталями (размер, дата, путь)

find /var/log -type f -mtime -1 -exec ls -lh {} +


Или аккуратнее через stat:

find . -type f -mtime -1 -exec stat --format '%y %s %n' {} +


▪️ Исключаем лишнее

find / -type f -mtime -1 \
! -path "/proc/*" \
! -path "/sys/*" \
! -path "/dev/*"


Актуально для системного аудита.

▪️ За последние N часов (точнее суток)

find /home -type f -mmin -1440


-mmin работает с минутами

BashTex 📱 #bash #utils
Please open Telegram to view this post
VIEW IN TELEGRAM
👍7
This media is not supported in your browser
VIEW IN TELEGRAM
Прости, мне очень жаль..Так надо

BashTex 📱 #юмор
Please open Telegram to view this post
VIEW IN TELEGRAM
😁8🗿3👨‍💻2
Работа с датами и временем

Дата и время в bash нужны постоянно: бэкапы, логи, ротация, отчеты, cron и т.д. Хорошая новость - date умеет почти все, если знать приемы.

▪️ Форматированный вывод даты:


date +"%Y-%m-%d %H:%M:%S"


▪️ Типовые форматы:

%Y - год
%m - месяц
%d - день
%H:%M:%S - время
%s - Unix timestamp


now=$(date +"%F_%H-%M-%S")


▪️ Вчера / завтра


date -d "yesterday"
date -d "tomorrow"


С форматированием:


date -d "yesterday" +"%Y-%m-%d"


▪️ Смещение на N дней


date -d "+3 days"
date -d "-7 days"


Пример для бэкапов:


backup_date=$(date -d "+1 day" +"%F")


▪️ Часы, минуты, секунды


date -d "+2 hours"
date -d "-15 minutes"
date -d "+30 seconds"


Комбинирование:


date -d "+1 day +2 hours"


▪️ Начало и конец дня


date -d "today 00:00"
date -d "today 23:59:59"


Вчерашний день целиком:


date -d "yesterday 00:00"
date -d "yesterday 23:59:59"


▪️ Unix timestamp в человекочитаемо


date -d @1700000000


Обратно:


date +%s


▪️ Имена файлов и каталогов


logfile="app_$(date +%F).log"

mkdir "backup_$(date +%Y%m%d)"


▪️ Проверка возраста файлов (совместно с date)


file_ts=$(stat -c %Y file.txt)
now_ts=$(date +%s)

(( now_ts - file_ts > 86400 )) && echo "Файл старше суток"


BashTex 📱 #bash #utils
Please open Telegram to view this post
VIEW IN TELEGRAM
👍12
Безопасная работа с rm

rm - одна из самых опасных команд в linux ввиду отсутствия корзины. Ошибся путем, переменной или шаблоном и данные ушли навсегда. Но есть простые приемы, которые делают работу с удалением безопасной.

▪️ Простой способ - ls


ls -ld /path/to/delete/*


Если вывод выглядит корректно только потом:


rm -rf /path/to/delete/*


▪️Проверка через echo. Отлично работает со сложными шаблонами:


echo rm -rf /data/tmp/*


Пока есть echo - ничего не удалится.

▪️ Проверка через find


find /logs -type f -mtime +7 -print


Проверили список -> заменили -print на -delete:


find /logs -type f -mtime +7 -delete


▪️ Подтверждение перед удалением. Встроенный интерактивный режим


rm -ri /important/data


-i - спрашивает подтверждение
-r - рекурсивно

▪️ Свой безопасный wrapper


safe_rm() {
echo "Будут удалены:"
ls -ld "$@"
read -p "Продолжить? [y/N] " ans
[[ "$ans" == "y" ]] && rm -rf "$@"
}


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


safe_rm /path/to/delete


▪️ Проверка на пустые переменные (обязательно!)

Опасно:


rm -rf "$DIR"


Безопасно:


[[ -n "$DIR" ]] && rm -rf "$DIR"


BashTex 📱 #bash #utils
Please open Telegram to view this post
VIEW IN TELEGRAM
👍11
Зато ты можешь разбудить меня в любое время и я моментально выйду из Vim 😎

BashTex 📱 #юмор
Please open Telegram to view this post
VIEW IN TELEGRAM
😁7
Чтение файлов построчно без потери спецсимволов

Чтение файла строка за строкой в bash кажется простым… пока не начинают теряться символы.

▪️ Классическая ошибка


while read line; do
echo "$line"
done < file.txt


Проблемы:

\ - пропадает
начальные/конечные пробелы обрезаются
строки с \n, \t, \\ читаются некорректно

Для конфигов, путей и логов это критично.

▪️ Правильный способ


while IFS= read -r line; do
echo "$line"
done < file.txt


Это канонический вариант чтения строк.

▪️ Что здесь происходит

IFS=

отключает разделение по пробелам и табам;
сохраняет все пробелы в начале и конце строки.

read -r

запрещает интерпретацию \ как escape;
строка читается как есть.

▪️ Пример проблемы

Файл:


path=C:\Program Files\App\


Без -r:


path=C:Program FilesApp


С IFS= read -r:


path=C:\Program Files\App\


▪️ Чтение с обработкой строк


while IFS= read -r line; do
[[ "$line" =~ ^#|^$ ]] && continue
echo "Обработка: $line"
done < config.conf


▪️ Альтернатива: mapfile


mapfile -t lines < file.txt


Но есть один минус - mapfile загружает весь файл в память

BashTex 📱 #bash #utils
Please open Telegram to view this post
VIEW IN TELEGRAM
👍8
Поиск пользователей без пароля и с заблокированным shell

Со временем в системе накапливаются сервисные, забытые или некорректные аккаунты. Часть из них может быть: без пароля, с истекшим паролем
или с заблокированным shell. Быстрый аудит можно сделать обычным bash.

▪️ Пользователи без пароля. Пароль хранится в /etc/shadow, но факт его отсутствия легко проверить:


sudo awk -F: '($2==""){print $1}' /etc/shadow


Если вывод не пустой - это повод проверить, зачем аккаунт существует.

▪️ Пользователи с заблокированным паролем


sudo awk -F: '($2 ~ /^!|^\*/){print $1}' /etc/shadow


! или * в поле пароля значит, что вход запрещен
Обычно это нормально для сервисных аккаунтов.

▪️ Пользователи с отключенным shell


awk -F: '($7 ~ /(nologin|false)$/){print $1, $7}' /etc/passwd


Типично для:

www-data
nginx
backup

Если обычный пользователь с таким shell - стоит проверить.

▪️ Проверка статуса пароля через chage


sudo chage -l username


Полезно для поиска:

истекших паролей;
аккаунтов без политики смены.

▪️ Массово:


for u in $(cut -d: -f1 /etc/passwd); do
chage -l "$u" 2>/dev/null | grep -q "never" && echo "$u"
done


▪️ Быстрый отчет:


awk -F: '{print $1 ":" $7}' /etc/passwd


BashTex 📱 #bash #utils
Please open Telegram to view this post
VIEW IN TELEGRAM
👍8
This media is not supported in your browser
VIEW IN TELEGRAM
Прекрасное чувство, прям камень с души

BashTex 📱 #юмор
Please open Telegram to view this post
VIEW IN TELEGRAM
😁9
Разница между "$@" и "$*", и почему это ломает скрипты

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

Предположим, скрипт запущен так:


./script.sh "file one.txt" "file two.txt"


▪️ "$@" - правильный вариант


for arg in "$@"; do
echo "[$arg]"
done


Вывод:


[file one.txt]
[file two.txt]


Каждый аргумент сохраняется как отдельная сущность.
Это то, чего вы ожидаете в 99% скриптов.

▪️ "$*" - частая ловушка


for arg in "$*"; do
echo "[$arg]"
done


Вывод:


[file one.txt file two.txt]


Все аргументы склеены в одну строку через IFS (по умолчанию пробел).

▪️ Почему это ломает скрипты

Передача аргументов дальше:


rm "$*"


rm получает один путь, которого не существует.

Обработка файлов:


cp "$*" /backup


копирование падает или ведет себя странно.

Работа с пробелами:

один аргумент = несколько файлов (по мнению bash).

BashTex 📱 #bash #utils
Please open Telegram to view this post
VIEW IN TELEGRAM
👍10
Автоматический сбор логов одного сервиса в архив

Бывает такое, что нужно быстро собрать логи сервиса и отправить в тикет, например. Делать это вручную - долго и неудобно. Решается одним bash-скриптом.

▪️ Базовый вариант. Собираем логи systemd-сервиса и сразу архивируем:


SERVICE=nginx
OUT="/tmp/${SERVICE}-logs-$(date +%F_%H-%M).tar.gz"

journalctl -u "$SERVICE" --no-pager > /tmp/${SERVICE}.log
tar -czf "$OUT" -C /tmp "${SERVICE}.log"
rm /tmp/${SERVICE}.log


Результат: один .tar.gz с логами сервиса за все время.

▪️ Ограничение по времени. Чаще нужны не все логи, а, например, последние сутки:


journalctl -u nginx --since "24 hours ago" --no-pager > nginx.log
tar -czf nginx-logs.tar.gz nginx.log


Можно и точнее:


--since "2026-01-20 10:00" --until "2026-01-20 12:00"


▪️ Полезные флаги

-o short-iso - удобный формат времени
-p err - только ошибки
--boot - текущая загрузка

Пример:


journalctl -u nginx -p err --since today -o short-iso


BashTex 📱 #bash
Please open Telegram to view this post
VIEW IN TELEGRAM
👍11
Массовая проверка доступности серверов

▪️ Базовый пример. Есть файл со списком хостов: servers.txt

Проверяем доступность всех сразу:


while read -r host; do
(
ping -c1 -W1 "$host" &>/dev/null \
&& echo "[OK] $host" \
|| echo "[FAIL] $host"
) &
done < servers.txt

wait


Все ping идут параллельно, вывод появляется почти мгновенно.

▪️ Ограничение параллелизма. Чтобы не зафлудить сеть:


MAX=10
i=0

while read -r host; do
(
ping -c1 -W1 "$host" &>/dev/null \
&& echo "[OK] $host" \
|| echo "[FAIL] $host"
) &
((i++))
((i % MAX == 0)) && wait
done < servers.txt

wait


▪️ Только упавшие хосты


ping -c1 -W1 "$host" &>/dev/null || echo "$host DOWN"


Будет полезно тем, у кого нет мониторинга, но есть 2 и более сервера.

BashTex 📱 #bash #utils
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥7👍3
Перестань оправдываться

BashTex 📱 #юмор
Please open Telegram to view this post
VIEW IN TELEGRAM
😁12
Автоматическая ротация кастомных логов без logrotate

Не все логи живут в /var/log. Самописные сервисы часто пишут сюда:


/opt/app/logs/
/srv/project/log/
/data/custom/*.log


Реализуем для них контролируемую ротацию на bash.

💚 Задача

архивировать логи старше N дней;
удалять архивы старше M дней;
не ломать активный файл;
не удалять то, что еще пишется.

1️⃣ Поиск логов старше N дней


LOG_DIR="/opt/app/logs"
DAYS=7

find "$LOG_DIR" -type f -name "*.log" -mtime +$DAYS


-mtime +7 - старше 7 дней.

⚠️ mtime - это время последнего изменения.

2️⃣ Архивирование с датой. Добавим gzip и текущую дату:


ARCHIVE_DIR="/opt/app/archive"
mkdir -p "$ARCHIVE_DIR"

find "$LOG_DIR" -type f -name "*.log" -mtime +7 -print0 |
while IFS= read -r -d '' file; do
base=$(basename "$file")
gzip -c "$file" > "$ARCHIVE_DIR/${base}_$(date +%F).gz" && rm -f "$file"
done


Здесь важно:

-print0 + read -d '' - безопасно для пробелов
gzip -c - сначала создаем архив
&& rm - удаляем только если архив создан

Никаких rm до успешного gzip.

3️⃣ Защита от удаления активного лога. Если сервис продолжает писать в файл, удалять его напрямую нельзя. Проверка через lsof:


if lsof "$file" >/dev/null 2>&1; then
echo "Файл используется, пропускаем: $file"
continue
fi


Альтернатива: ротировать только логи, которые не менялись X часов:


-mmin +1440


4️⃣ Удаление старых архивов. Храним архивы 30 дней:


find "$ARCHIVE_DIR" -type f -name "*.gz" -mtime +30 -delete


Лучше сначала проверить:


find "$ARCHIVE_DIR" -type f -mtime +30 -print


Только потом добавлять -delete.

5️⃣ Безопасный вариант с flock (чтобы не запустить дважды)


(
flock -n 200 || exit 1

# код ротации здесь

) 200>/var/lock/custom-log-rotate.lock


Теперь крон не создаст гонку.

▪️ Пример запуска через cron


0 3 * * * /usr/local/bin/custom_rotate.sh


BashTex 📱 #bash #utils
Please open Telegram to view this post
VIEW IN TELEGRAM
👍8
Проверка, какие команды доступны через sudo

Перед тем как что-то ломать или усиливать безопасность, полезно понять: что именно пользователь может запускать через sudo.

▪️ Базовая проверка


sudo -l


Показывает:

от какого пользователя можно выполнять команды
какие команды разрешены
есть ли NOPASSWD

Минус: в выводе много мусора.

▪️ Убираем лишнее. Только команды:


sudo -l | sed -n '/may run the following commands/,/^$/p'


Или короче:


sudo -l | grep -E '^\s*\('


▪️ Быстро найти опасные права

Команды с NOPASSWD


sudo -l | grep NOPASSWD


Полный доступ (ALL)


sudo -l | grep '(ALL)'


Запуск shell’ов


sudo -l | grep -E 'bash|sh|zsh'


▪️ Проверка другого пользователя (root)


sudo -l -U username


BashTex 📱 #bash
Please open Telegram to view this post
VIEW IN TELEGRAM
👍9