Tarkibga o'tish

Loyiha: Log tozalash skripti

Disk to'lganda troubleshooting maqolasi eski log'lar diskni to'ldirib qo'yishi mumkinligini ko'rsatdi, Healthcheck skripti loyihasi esa disk bandligini avtomatik tekshirishni ko'rib chiqdi. Bu yakuniy loyiha ikkalasini birlashtiradi: joy tugashini kutmasdan, eski log fayllarni muntazam va xavfsiz o'chiradigan skript yozish. "Xavfsiz" so'zi bu yerda bejiz emas: e’tiborsiz yozilgan log tozalash skripti kerakli fayllarni yo'qotish xavfi eng yuqori bo'lgan skript turlaridan biri.

Maqsad va yakuniy natija

Skript berilgan log katalogida ko'rsatilgan yoshdan (kunlarda) eski .log kengaytmali fayllarni topadi. U har doim avval dry-run rejimida ishlaydi — hech narsani o'chirmaydi, faqat nima o'chirilishini ko'rsatadi. Haqiqiy o'chirishni esa faqat aniq --apply flag'i bilan chaqirilgandagina bajaradi. Bu real production skriptlarda keng qo'llaniladigan xavfsizlik shabloni.

Boshlang'ich holat

mkdir -p ~/lab/log-cleanup-project/logs
cd ~/lab/log-cleanup-project

for i in 1 5 10 20 35; do
    touch -d "${i} days ago" "logs/app-${i}d.log"
done

touch -d "3 days ago" logs/important.log.keep
ls -la logs

Bu yerda turli yoshdagi fayllar sun’iy ravishda yaratiladi. touch -d faylning o'zgartirilgan vaqtini o'tmishdagi ko'rsatilgan nuqtaga o'rnatadi. Bu find -mtime ga asoslangan tozalash mantiqini haqiqiy vaqt o'tishini kutmasdan sinab ko'rish imkonini beradi.

Talablar

  • find, du, date — Ubuntu Server LTS’da standart holatda mavjud;
  • log katalogiga yozish (va o'chirish) huquqi;
  • skript qanday tuzilganini tushunish uchun Shartlar va sikllar va Exit kodlar maqolalari bilan tanish bo'lish tavsiya etiladi.

Xavfsizlik bo'yicha eslatma

Bu skript fayllarni qaytarib bo'lmaydigan tarzda o'chiradi. Bu loyihani birinchi marta sinab ko'rayotganda:

  1. uni faqat yuqorida yaratilgan test katalogiga (~/lab/log-cleanup-project/logs) qarshi ishga tushiring, hech qachon /var/log ga qarshi emas;
  2. uni har doim avval flag'siz (dry-run rejimida) ishga tushiring va chiqishini diqqat bilan o'qing;
  3. --apply flag'ini faqat dry-run natijasi to'g'ri ekaniga to'liq ishonch hosil qilgandan keyin qo'shing;
  4. production katalogda ishlatishdan oldin muhim fayllarni avval Backup skripti bilan arxivlang.

Bosqichma-bosqich qurish

1-qadam: argumentlar va dry-run flag'i

#!/bin/bash
set -euo pipefail

# log_cleanup.sh — finds old log files and deletes them (only with --apply)
# Usage: ./log_cleanup.sh <log_directory> <days> [--apply]

LOG_DIR="${1:-}"
MAX_AGE_DAYS="${2:-}"
APPLY_MODE="${3:-}"

if [ -z "$LOG_DIR" ] || [ -z "$MAX_AGE_DAYS" ]; then
    echo "Error: not enough arguments." >&2
    echo "Usage: $0 <log_directory> <days> [--apply]" >&2
    exit 2
fi

if [ ! -d "$LOG_DIR" ]; then
    echo "Error: directory not found: $LOG_DIR" >&2
    exit 1
fi

Tekshirish:

chmod +x log_cleanup.sh
./log_cleanup.sh

Kutilgan natija:

Error: not enough arguments.
Usage: ./log_cleanup.sh <log_directory> <days> [--apply]

2-qadam: dry run — nima o'chirilishini ko'rsatish

find_old_logs() {
    # "$@" forwards any extra find actions (e.g. -print -delete) to the SAME
    # search conditions, so the dry-run and the real deletion can never drift.
    find "$LOG_DIR" -maxdepth 1 -name "*.log" -mtime "+${MAX_AGE_DAYS}" "$@"
}

report_dry_run() {
    local files
    files=$(find_old_logs)

    if [ -z "$files" ]; then
        echo "No .log files older than ${MAX_AGE_DAYS} days were found."
        return 0
    fi

    echo "The following files are candidates for deletion (older than ${MAX_AGE_DAYS} days):"
    echo "$files" | while IFS= read -r file; do
        local size
        size=$(du -h "$file" | cut -f1)
        echo "  $file ($size)"
    done

    local total_size
    total_size=$(find_old_logs | xargs du -ch 2>/dev/null | tail -n 1 | cut -f1)
    echo "Total space that would be freed: ${total_size:-0}"
}

Uni asosiy oqimga qo'shing:

report_dry_run

Tekshirish:

./log_cleanup.sh ~/lab/log-cleanup-project/logs 15

Kutilgan natija:

The following files are candidates for deletion (older than 15 days):
  /home/<username>/lab/log-cleanup-project/logs/app-20d.log (0B)
  /home/<username>/lab/log-cleanup-project/logs/app-35d.log (0B)
Total space that would be freed: 0B

(Fayllar bo'sh qilib yaratilgani uchun hajm 0B ko'rinadi — haqiqiy log'lar uchun bu yerda haqiqiy hajm chiqadi.) E’tibor bering, app-1d.log, app-5d.log, app-10d.log va important.log.keep ro'yxatda yo'q: dastlabki uchtasi 15 kundan yosh, oxirgisi esa .log bilan emas, .log.keep bilan tugaydi, shuning uchun pattern’ga mos kelmaydi.

3-qadam: --apply bilan haqiqiy o'chirish

apply_cleanup() {
    local deleted_count
    deleted_count=$(find_old_logs -print -delete | wc -l)
    echo "Deleted: $deleted_count file(s)"
}

Bu yerda find_old_logs -print -delete -print -delete ni qo'shimcha find amallari sifatida uzatadi. find_old_logs ichidagi "$@" ularni dry run ishlatgan aynan o'sha moslik shartlariga qo'shib qo'yadi. Funksiya aynan shuning uchun "$@" bilan yozilgan: usiz qo'shilgan -print -delete tashlab yuborilardi va apply_cleanup hech narsani o'chirmasa ham, sonni xabar qilib turaverardi. Bu xavfli "ishlaganga o'xshaydi" xatosi. Ikkala rejim bitta funksiyani chaqirgani uchun dry-run ko'rsatgan ro'yxat va haqiqiy o'chirish aynan bir xil fayllar to'plamiga qaratilishi kafolatlanadi.

Asosiy oqimni yangilang — --apply siz faqat hisobot, u bilan esa haqiqiy o'chirish:

if [ "$APPLY_MODE" = "--apply" ]; then
    report_dry_run
    echo "--- --apply mode: deletion starting ---"
    apply_cleanup
else
    report_dry_run
    echo "--- This was only a preview. Add the --apply flag to actually delete files. ---"
fi

Tekshirish (avval flag'siz, keyin --apply bilan):

./log_cleanup.sh ~/lab/log-cleanup-project/logs 15
./log_cleanup.sh ~/lab/log-cleanup-project/logs 15 --apply
ls ~/lab/log-cleanup-project/logs

Kutilgan natija: birinchi chaqiruv faqat ro'yxatni ko'rsatadi va hech narsani o'chirmaydi. Ikkinchisi esa app-20d.log va app-35d.log ni haqiqatan o'chiradi, app-1d.log, app-5d.log, app-10d.log va important.log.keep esa saqlanib qoladi.

Danger

find ... -delete qaytarib bo'lmaydi va rm ga teng kuchli. -delete ni har doim dry-run’da sinab ko'rilgan aynan o'sha find chaqiruviga — o'sha -maxdepth, -name va -mtime shartlariga — qarshi ishlating. Dry-run va haqiqiy chaqiruvda shartlarni turlicha yozish (masalan, ulardan birida -maxdepth 1 ni unutish) butunlay boshqa fayllar to'plamini o'chirib yuborishi mumkin.

Amaliy vaziyat: cron orqali muntazam ishga tushirish

Muammo: log tozalash qo'lda emas, har kuni avtomatik bajarilishi kerak, lekin muvaffaqiyatsizlik jimgina o'tib ketmasligi, balki xabar qilinishi kerak.

0 3 * * * /home/<username>/scripts/log_cleanup.sh /var/log/myapp 30 --apply >> /var/log/myapp/cleanup-cron.log 2>&1

Bu crontab yozuvi skriptni har kuni soat 03:00 da --apply rejimida ishga tushiradi va uning butun chiqishini (muvaffaqiyat va xato xabarlarini birga) cleanup-cron.log ga yozadi. cron sintaksisi va uning muhiti interaktiv shell’dan qanday farq qilishi cron asoslari va cron muhiti va log'lari maqolalarida chuqur ko'rib chiqiladi. Bu yerda muhimi shuki, production’da bunday skript avval qo'lda, keyin cron ostida dry-run rejimida sinab ko'rilishi va shundan keyingina --apply bilan jadvalga qo'yilishi kerak.

To'liq skript

#!/bin/bash
set -euo pipefail

# log_cleanup.sh — finds old log files and deletes them (only with --apply)
# Usage: ./log_cleanup.sh <log_directory> <days> [--apply]

LOG_DIR="${1:-}"
MAX_AGE_DAYS="${2:-}"
APPLY_MODE="${3:-}"

if [ -z "$LOG_DIR" ] || [ -z "$MAX_AGE_DAYS" ]; then
    echo "Error: not enough arguments." >&2
    echo "Usage: $0 <log_directory> <days> [--apply]" >&2
    exit 2
fi

if [ ! -d "$LOG_DIR" ]; then
    echo "Error: directory not found: $LOG_DIR" >&2
    exit 1
fi

find_old_logs() {
    # "$@" forwards any extra find actions (e.g. -print -delete) to the SAME
    # search conditions, so the dry-run and the real deletion can never drift.
    find "$LOG_DIR" -maxdepth 1 -name "*.log" -mtime "+${MAX_AGE_DAYS}" "$@"
}

report_dry_run() {
    local files
    files=$(find_old_logs)

    if [ -z "$files" ]; then
        echo "No .log files older than ${MAX_AGE_DAYS} days were found."
        return 0
    fi

    echo "The following files are candidates for deletion (older than ${MAX_AGE_DAYS} days):"
    echo "$files" | while IFS= read -r file; do
        local size
        size=$(du -h "$file" | cut -f1)
        echo "  $file ($size)"
    done

    local total_size
    total_size=$(find_old_logs | xargs du -ch 2>/dev/null | tail -n 1 | cut -f1)
    echo "Total space that would be freed: ${total_size:-0}"
}

apply_cleanup() {
    local deleted_count
    deleted_count=$(find_old_logs -print -delete | wc -l)
    echo "Deleted: $deleted_count file(s)"
}

if [ "$APPLY_MODE" = "--apply" ]; then
    report_dry_run
    echo "--- --apply mode: deletion starting ---"
    apply_cleanup
else
    report_dry_run
    echo "--- This was only a preview. Add the --apply flag to actually delete files. ---"
fi

Troubleshooting bo'yicha maslahatlar

  • Dry-run va --apply turli fayllarni ko'rsatadi — ikkala rejim bir xil find_old_logs funksiyasidan foydalanishi kerak. Agar shartlar ikki joyda alohida yozilsa, ular osongina bir-biridan farqlanib ketadi. Shuning uchun bu skriptda shart faqat bitta funksiyada, bir marta yozilgan.
  • important.log.keep kabi fayl kutilmaganda o'chib ketdi — -name "*.log" pattern’i nom aynan .log bilan tugashini talab qiladi. Agar u kengaytirilsa (masalan, -name "*log*" ga), boshqa nomli fayllar ham mos kelib qolishi mumkin. Pattern’ni har doim imkon qadar tor va aniq saqlang.
  • Katalog ichidagi quyi kataloglar ham tozalanib ketdi — -maxdepth 1 qidiruvni faqat berilgan katalogning o'zi bilan cheklaydi. Uni olib tashlash find ga quyi kataloglarga ham tushish imkonini beradi. Agar aynan shu ataylab kerak bo'lmasa, -maxdepth 1 ni saqlab qoling.
  • total_size bo'sh chiqadi — du ning ba’zi versiyalari xargs siz boshqacha ishlaydi yoki fayllar ro'yxati bo'sh bo'lganda xato beradi. 2>/dev/null va ${total_size:-0} aynan shuning uchun bo'sh holat uchun zaxira qiymatni ta’minlaydi.

Orqaga qaytarish va tozalash

Agar kerakli fayl xato tufayli o'chib ketgan bo'lsa, uni faqat oldindan olingan backup’dan tiklash mumkin (log fayllarda odatda "savat" mexanizmi bo'lmaydi). Production katalogda ishga tushirishdan oldin Backup skripti bilan arxivlash aynan shuning uchun tavsiya qilingan edi. Lab tugagach:

rm -rf ~/lab/log-cleanup-project

Yakuniy baholash mezoni

Quyidagilarning hammasi bajarilsa, loyiha muvaffaqiyatli hisoblanadi:

  • flag'siz chaqirilganda skript hech narsani o'chirmaydi, faqat nomzodlar ro'yxati va umumiy hajmni ko'rsatadi;
  • --apply bilan chaqirilganda faqat dry-run’da ko'rsatilgan aynan o'sha fayllar o'chiriladi;
  • .log bilan tugamaydigan fayllarga (masalan, important.log.keep) hech qachon tegilmaydi;
  • yosh chegarasidan yoshroq fayllar (misolda app-1d.log, app-5d.log, app-10d.log) saqlanib qoladi;
  • argumentsiz yoki mavjud bo'lmagan katalog bilan chaqirilganda skript mos exit kod (2 yoki 1) bilan aniq xato beradi.

Xulosa

"14. Bash scripting" moduli shu yerda yakunlanadi: u Asoslar dan boshlanib, O'zgaruvchilar va argumentlar, Shartlar va sikllar, Funksiyalar va Exit kodlar orqali o'tdi va uchta production skriptda — backup, healthcheck va log tozalashda — birlashdi. Uchalasida bitta g'oya takrorlanadi: xavfli amaldan oldin har doim o'qish/tekshirish bosqichini qo'ying (dry run, verify_backup, -delete dan oldin -print). Keyingi "15. Rejalashtirish, backup va avtomatlashtirish" moduli aynan shu skriptlarni olib, ularni cron va systemd timer’lar orqali inson ishtirokisiz, jadval bo'yicha ishga tushirishni boshlaydi.

Manbalar