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

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

Реклама: @dad_admin
Download Telegram
Автоматическая ротация кастомных логов без 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
Ты не выйдешь

BashTex 📱 #юмор
Please open Telegram to view this post
VIEW IN TELEGRAM
😁17
Ограничение времени выполнения команды

Иногда команда зависает навсегда: сетевой запрос, бэкап, скрипт. timeout - это простой способ не дать ей убить автоматизацию.

▪️ Базовое использование


timeout 5s command


Если команда не завершилась за 5 секунд, то она будет остановлена.

Поддерживаемые единицы: 5s 2m 1h

▪️ В скриптах и cron. Пример с curl:


timeout 10s curl https://bashtex.com


В cron это особенно важно т.к зависшая задача может копиться часами.

▪️ Проверка результата


timeout 5s long_task
case $? in
0) echo "OK" ;;
124) echo "Timeout" ;;
*) echo "Ошибка" ;;
esac


Код 124 означает, что команда была прервана по таймауту.

▪️ Жесткое завершение

По умолчанию timeout шлёт SIGTERM.
Если процесс игнорирует его:


timeout -k 2s 10s command


10s - мягкое завершение
+2s - принудительный SIGKILL

BashTex 📱 #bash #utils
Please open Telegram to view this post
VIEW IN TELEGRAM
👍5
Проверка: существует ли файл, каталог или ссылка

В bash не нужно городить ls | grep. Для проверок есть встроенные тесты, более быстрые и надежные.

▪️ Обычный файл - -f


[ -f file.txt ] && echo "Файл существует"


Истина, если: файл есть и это не каталог

▪️ Каталог - -d


[ -d /etc/nginx ] && echo "Каталог существует"


Полезно перед: копированием, cd или очисткой директорий

▪️ Символическая ссылка - -L


[ -L /usr/bin/python ] && echo "Это symlink"


Работает даже если ссылка битая.

▪️ Просто что-то существует - -e


[ -e path ] || echo "Ничего нет"


Файл, каталог, ссылка - все подряд.

▪️ Шпаргалка

-f обычный файл
-d каталог
-L symlink
-e существует


BashTex 📱 #bash #utils
Please open Telegram to view this post
VIEW IN TELEGRAM
👍8
Захват вывода команды в переменную

Есть два способа сохранить вывод команды в переменную. Формально оба работают, но один из них устарел.

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


files=$(ls /etc)


Плюсы:

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


count=$(wc -l < file.txt)


▪️ Устаревший способ


files=`ls /etc


Минусы:

плохо читается
сложно экранировать
вложенность превращается в ад


# выглядит ужасно и ломается
result=`echo \`date\``


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

` конфликтует с кавычками
ошибки трудно отлаживать
в больших скриптах быстро становится нечитаемо

▪️ Общая ловушка: переносы строк. Оба способа схлопывают переводы строк в пробелы:


list=$(cat file)


Если важны строки:


mapfile -t list < file


BashTex 📱 #bash #utils
Please open Telegram to view this post
VIEW IN TELEGRAM
👍7
Работа с массивами

Массивы - это простой способ хранить списки без awk и временных файлов. Главное знать базовые приемы.

▪️ Добавление элементов


arr=(one two)
arr+=(three)
arr[5]=six


bash сам раздвигает индексы, дыры допустимы.

▪️ Удаление элементов


unset arr[1]


Элемент удаляется, но индексы не сдвигаются:


echo "${!arr[@]}" # индексы


Удалить весь массив:


unset arr


▪️ Перебор элементов

Правильно:


for item in "${arr[@]}"; do
echo "$item"
done


Неправильно (ломает пробелы):


for item in ${arr[@]}; do


▪️ Перебор с индексами


for i in "${!arr[@]}"; do
echo "$i => ${arr[$i]}"
done


▪️ Размер массива


echo "${#arr[@]}"


BashTex 📱 #bash
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥8👍3
Не завидуйте ему

BashTex 📱 #юмор
Please open Telegram to view this post
VIEW IN TELEGRAM
😁10
Проверка времени отклика сервисов

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

▪️ Самый простой замер


time curl -s https://bashtex.com > /dev/null


Показывает общее время выполнения запроса и быстро понять, тормозит или нет.

▪️ Точнее: только сетевое время


curl -s -o /dev/null -w "time_total: %{time_total}\n" https://bashtex.com


Полезные метрики:

time_namelookup
time_connect
time_starttransfer
time_total


Пример:


curl -w "DNS:%{time_namelookup} CONNECT:%{time_connect} TTFB:%{time_starttransfer} TOTAL:%{time_total}\n" \
-o /dev/null -s https://bashtex.com


▪️ Проверка нескольких сервисов


for url in https://a.ru https://b.ru; do
curl -o /dev/null -s -w "$url %{time_total}\n" "$url"
done


▪️ Таймаут обязателен


curl --connect-timeout 3 --max-time 5 https://bashtex.com


Без таймаута любой мониторинг бесполезен.

BashTex 📱 #bash #utils
Please open Telegram to view this post
VIEW IN TELEGRAM
👍7
Поиск забытых .ssh директорий

После чистки пользователей в системе нередко остаются их .ssh-каталоги. Это мусор + потенциальная дыра: старые ключи могут лежать годами.

▪️ Быстрый поиск по системе


find / -type d -name .ssh 2>/dev/null


Найдет все .ssh, включая:

/home/*/.ssh
/root/.ssh

нестандартные каталоги

▪️ Проверяем, существует ли владелец


find / -type d -name .ssh -exec stat -c '%U %n' {} \;


Если владелец:

UNKNOWN
или пользователь отсутствует в /etc/passwd

то каталог подозрительный.

▪️ Поиск .ssh без пользователя


while read user path; do
id "$user" &>/dev/null || echo "Лишний: $path"
done < <(find / -type d -name .ssh -exec stat -c '%U %n' {} \;)


⚠️ Перед удалением лучше сделать архив

BashTex 📱 #bash #utils
Please open Telegram to view this post
VIEW IN TELEGRAM
👍6
Циклы while vs for - где что правильно

В bash оба цикла нужны, но для разных задач. Путаница между ними - источник проблем.

▪️ for - перебор готового списка. Используй, когда список уже есть.


for f in *.log; do
echo "$f"
done


файлы;
аргументы "$@";
элементы массива.

⚠️ Пример ошибок:


for f in $(ls *.log); do # ломается на пробелах


▪️ while - поток данных. Идеален для чтения ввода.


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


строки с пробелами;
вывод команд;
большие файлы.

⚠️ Частая ошибка:


cat file | while read line; do
count=$((count+1)) # переменная пропадёт
done


(цикл в subshell!)

for не является универсальным, а while безопаснее для данных. Перед созданием цикла нужно задавать себе вопрос: список или поток.

BashTex 📱 #bash
Please open Telegram to view this post
VIEW IN TELEGRAM
👍7
Контроль свободных inode на дисках

Можно иметь 50% свободного места и при этом… не создать ни одного файла. Причина: закончились inode.

▪️ Проверка inode на дисках


df -i


Ключевые колонки:

Inodes - всего
IUsed - использовано
IFree - свободно
IUse% - процент


▪️ Когда это становится проблемой

миллионы мелких файлов (cache, maildir, tmp)
Docker overlay
/var/spool, /tmp, /var/lib

Типичная ошибка:


No space left on device


…при пустом диске.

▪️ Поиск пожирателей inode


find /var -xdev -type f | wc -l


Или по каталогам:


for d in /var/*; do
echo "$(find "$d" -type f | wc -l) $d"
done


▪️ Что делать

чистить cache и tmp
пересоздать FS с большим inode ratio (если хронически)

BashTex 📱 #bash
Please open Telegram to view this post
VIEW IN TELEGRAM
👍6🔥2
+2 бесполезных часа активированы

BashTex 📱 #юмор
Please open Telegram to view this post
VIEW IN TELEGRAM
👍5😁1🗿1
Контроль роста каталогов во времени

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

1️⃣ Делаем снимок. Сохраняем размеры верхнеуровневых директорий:


du -x --max-depth=1 /var 2>/dev/null | sort -n > /tmp/var.size.today


-x - не уходим на другие FS
--max-depth=1 - только первый уровень

сортировка сразу по размеру

2️⃣ Сравниваем со вчера. Если есть предыдущий снимок:


diff -u /tmp/var.size.yesterday /tmp/var.size.today


Или удобнее, показать только рост:


join -1 2 -2 2 \
<(sort -k2 /tmp/var.size.yesterday) \
<(sort -k2 /tmp/var.size.today) \
| awk '{delta=$3-$1; if (delta>0) printf "%+dK %s\n", delta, $2}' \
| sort -n


На выходе список каталогов, которые реально выросли, с дельтой.

▪️ Зачем это нужно

быстро найти runaway-логи;
поймать тихий рост кешей;
понять, кто ест диск, а не гадать

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

В linux у каждого файла есть не только размер, но и время. И на этом можно строить полезную автоматику.

▪️ Какие метки бывают


stat file.txt


Главные поля:

Modify (mtime) - когда менялось содержимое
Access (atime) - когда читали
Change (ctime) - менялись права / владелец


⚠️ ctime - не дата создания

▪️ Получаем время в виде числа. Для сравнений удобнее epoch:


stat -c %Y file.txt # mtime в секундах


Текущее время:


now=$(date +%s)


▪️ Пример: файл старше N минут


mtime=$(stat -c %Y file.log)
(( now - mtime > 600 )) && echo "Файл старше 10 минут"


▪️ Сравнение двух файлов


f1=$(stat -c %Y a.conf)
f2=$(stat -c %Y b.conf)

(( f1 > f2 )) && echo "a.conf новее"


Полезно для: автопересборки конфигов или выбора актуального бэкапа

▪️ Практика: очистка старых файлов


for f in /tmp/*; do
(( now - $(stat -c %Y "$f") > 86400 )) && rm -f "$f"
done


BashTex 📱 #bash #utils
Please open Telegram to view this post
VIEW IN TELEGRAM
👍8
Автоперезапуск сервиса только если он упал больше N раз

Иногда сервис падает один раз - это нормально. Но если он падает 10 раз за минуту, бесконечный Restart=always только усугубляет проблему.

Задача: перезапускать сервис только если он упал больше N раз за период.

1️⃣ Считаем падения через systemd. У systemd есть счетчик рестартов:


systemctl show nginx -p NRestarts


Вывод:


NRestarts=3


2️⃣ Простая логика в bash


SERVICE=nginx
LIMIT=5

restarts=$(systemctl show "$SERVICE" -p NRestarts --value)

if (( restarts >= LIMIT )); then
echo "Сервис падал $restarts раз — перезапуск"
systemctl restart "$SERVICE"
else
echo "Падений мало ($restarts) — не трогаем"
fi


▪️ Учитываем период времени

Счетчик накапливается с момента запуска systemd.
Чтобы учитывать последний час, можно смотреть journal:


journalctl -u nginx --since "1 hour ago" | grep -c "Started"


Или искать Main process exited.

BashTex 📱 #bash #utils
Please open Telegram to view this post
VIEW IN TELEGRAM
👍8
Please open Telegram to view this post
VIEW IN TELEGRAM
😁8
Маунт есть, а доступа нет

Представим ситуацию, mount показывает, что файловая система подключена, но при заходе в каталог: Permission denied.

1️⃣ Проверяем права на каталог


ls -ld /mnt/data


Важно:

владелец
группа
права (rwx)

Даже если диск смонтирован, права на точку монтирования остаются критичны.

Исправление:


chown user:group /mnt/data
chmod 755 /mnt/data


2️⃣ Проверяем UID/GID (часто с NFS). На NFS или при миграции: пользователь есть, но UID отличается

Проверка:


id user
ls -ln /mnt/data


Если UID не совпадает, то и доступ будет запрещен.

Решение:

синхронизировать UID
или использовать anonuid/anongid (для NFS)

3️⃣ Смотрим опции монтирования


mount | grep /mnt/data


Частые виновники:

ro - read-only
noexec
nosuid
nodev

Перемонтировать:


mount -o remount,rw /mnt/data


4️⃣ SELinux (тихий убийца доступа). Если права нормальные, но доступ запрещен:


getenforce


Проверка контекста:


ls -Z /mnt/data


Исправление:


restorecon -Rv /mnt/data


5️⃣ Root_squash (NFS-специфика). На NFS root может стать nobody. Проверка экспорта:


exportfs -v


Если включен root_squash, root не всесилен.

6️⃣ ACL могут перекрывать права


getfacl /mnt/data


ACL могут запретить доступ даже при chmod 777.

BashTex 📱 #bash
Please open Telegram to view this post
VIEW IN TELEGRAM
👍9🔥1
Поиск сервисов без логов

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

1️⃣ Проверяем, куда вообще пишет сервис


systemctl status myservice


Смотрим:

Loaded
Active
путь к unit-файлу

Далее:


systemctl show myservice -p StandardOutput -p StandardError


Если:

StandardOutput=null
StandardError=null

это означает, что логирование отключено.

2️⃣ Проверяем journal


journalctl -u myservice --since "1 hour ago"


Если пусто, а сервис активен:


systemctl is-active myservice


3️⃣ Проверяем, есть ли вообще лог-файлы. Иногда сервис пишет в файл, а не в journal:


lsof -p $(pgrep -x myservice) | grep log


Или:


ls -l /var/log | grep myservice


4️⃣ Проверяем stdout/stderr процесса


ls -l /proc/$(pgrep -x myservice)/fd


Если дескрипторы 1 и 2 указывают в /dev/null - сервис глухой.

5️⃣ Смотрим конфиг самого приложения. Часто в конфиге:


log_level = none
logging = false
daemon = true


6️⃣ Мини-детектор тихих сервисов. Проверяем активные сервисы без событий в journal за час:


for s in $(systemctl list-units --type=service --state=running --no-legend | awk '{print $1}'); do
count=$(journalctl -u "$s" --since "1 hour ago" | wc -l)
(( count == 0 )) && echo "Тихий сервис: $s"
done


BashTex 📱 #bash #utils
Please open Telegram to view this post
VIEW IN TELEGRAM
👍9
Само обнаружение

BashTex 📱 #юмор
Please open Telegram to view this post
VIEW IN TELEGRAM
😁5
Почему сервис не стартует?

1️⃣ Быстрая проверка статуса


systemctl status myservice -n 20 --no-pager


Смотрим:

Loaded
Active
ExecStart
последние строки логов


2️⃣ Смотрим реальные ошибки из journal


journalctl -u myservice -xe --no-pager


Частые сообщения:

Permission denied
No such file or directory
Address already in use
Failed to bind
Unit entered failed state


3️⃣ Проверяем unit-файл


systemctl cat myservice


Ошибки часто здесь:

неправильный путь в ExecStart
забыли daemon-reload
лишний User=
отсутствующая рабочая директория


Проверка синтаксиса:


systemd-analyze verify /etc/systemd/system/myservice.service


4️⃣ Типовые причины, которые встречаются чаще всего

🤩 Неверный путь к бинарнику: ExecStart=/usr/bin/app
Файл не существует и сервис не стартует.

🤩 Порт уже занят


ss -lntp | grep 8080


🤩 Неправильные права


ls -l /path/to/app


Нужно проверить владельца и execute-бит.

🤩 Ошибка в конфиге приложения. Попробуй запустить вручную:


sudo -u serviceuser /usr/bin/app


🛠 Небольшой скрипт диагностики


#!/bin/bash
SERVICE="$1"

echo "=== STATUS ==="
systemctl status "$SERVICE" -n 10 --no-pager

echo
echo "=== LAST ERRORS ==="
journalctl -u "$SERVICE" -n 20 --no-pager

echo
echo "=== UNIT FILE ==="
systemctl cat "$SERVICE"

echo
echo "=== PORT CHECK ==="
ss -lntp | grep "$(systemctl show "$SERVICE" -p ExecStart --value | awk '{print $1}')" 2>/dev/null


Запуск:


./diag.sh nginx


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