Tarkibga o'tish

Loyiha: Backup skripti

Modul davomida ko'rib chiqilgan barcha qismlar — Skript asoslari, O'zgaruvchilar va argumentlar, Shartlar va sikllar, Funksiyalar, Exit kodlar va xatolarni qayta ishlash — endi bitta real vazifada birlashadi. Bu katalogni muntazam ravishda, sana belgisi bilan arxivlaydigan, eski nusxalarni avtomatik tozalaydigan va xatolar haqida aniq exit kod bilan xabar beradigan backup skripti. Bu modulning uchta majburiy amaliy loyihasidan birinchisi.

Maqsad va yakuniy natija

Skript berilgan manba katalogni tar bilan siqib arxivlaydi, arxiv nomiga sana qo'shadi, muvaffaqiyat yoki muvaffaqiyatsizlikni log'ga yozadi va belgilangan muddatdan eski arxivlarni avtomatik o'chiradi. Loyiha oxirida backup.sh argumentlarini tekshiradigan, xato bo'lganda to'g'ri kod bilan chiqadigan va cron orqali inson ishtirokisiz ishlay oladigan skriptga aylanadi.

Boshlang'ich holat

Bu lab uchun oddiy muhit yetarli — alohida VM kerak emas. Lekin haqiqiy tizim kataloglaridan ajratilgan test kataloglarida ishlash tavsiya etiladi:

mkdir -p ~/lab/backup-project/{data,backups}
cd ~/lab/backup-project
printf 'user data\n' > data/notes.txt
mkdir -p data/reports
printf 'report-2026-07\n' > data/reports/report.txt

Talablar

  • Bash 5.x (yoki 4.x), tar, gzip, find, date — Ubuntu Server LTS’da standart holatda o'rnatilgan;
  • manba va backup kataloglariga yozish huquqi;
  • ixtiyoriy: cron — skriptni jadval bo'yicha ishga tushirish uchun (cron asoslari maqolasida ko'rib chiqiladi).

Xavfsizlik va snapshot haqida eslatma

Skript eski fayllarni find ... -delete orqali o'chiradi — bu qaytarib bo'lmaydigan amal. Bu loyihani birinchi marta sinab ko'rayotganda:

  1. BACKUP_DIR ni haqiqiy backup katalogiga emas, yuqoridagi ~/lab/backup-project/backups kabi test katalogiga yo'naltiring;
  2. o'chirish qatorini avval -delete siz, faqat ro'yxat chiqarish sifatida sinab ko'ring (quyida ko'rsatilgan);
  3. natija aynan kutilganidek ekaniga ishonch hosil qilgandan keyingina skriptni haqiqiy ma’lumotlar kataloglariga qarshi ishga tushiring.

Bosqichma-bosqich qurish

1-qadam: skript skeleti va argumentlarni tekshirish

#!/bin/bash
set -euo pipefail

# backup.sh — archives a directory with a date stamp and cleans up old copies
# Usage: ./backup.sh <source_directory> <backup_directory> [days_to_keep]

SOURCE_DIR="${1:-}"
BACKUP_DIR="${2:-}"
RETENTION_DAYS="${3:-7}"
LOG_FILE="${BACKUP_DIR}/backup.log"

log() {
    # Write to stderr (not stdout) so log lines never pollute a value
    # captured with $(...) — see the note under Step 3.
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE" >&2
}

if [ -z "$SOURCE_DIR" ] || [ -z "$BACKUP_DIR" ]; then
    echo "Error: not enough arguments." >&2
    echo "Usage: $0 <source_directory> <backup_directory> [days_to_keep]" >&2
    exit 2
fi

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

mkdir -p "$BACKUP_DIR"

Tekshirish:

chmod +x backup.sh
./backup.sh

Kutilgan natija:

Error: not enough arguments.
Usage: ./backup.sh <source_directory> <backup_directory> [days_to_keep]
echo "Exit code: $?"
Exit code: 2

2-qadam: arxivlash funksiyasi

create_backup() {
    local timestamp
    timestamp=$(date +%Y%m%d-%H%M%S)
    local archive_name="${BACKUP_DIR}/$(basename "$SOURCE_DIR")-${timestamp}.tar.gz"

    if tar -czf "$archive_name" -C "$(dirname "$SOURCE_DIR")" "$(basename "$SOURCE_DIR")"; then
        log "Backup successful: $archive_name"
        return 0
    else
        log "ERROR: archiving failed"
        return 1
    fi
}

-C "$(dirname "$SOURCE_DIR")" tar ga arxivlashdan oldin manba katalogning ota katalogiga o'tishni aytadi. Bu arxiv ichida absolyut yo'l (/home/user/data/...) emas, faqat katalog nomining o'zi (data/...) saqlanishini ta’minlaydi. Bu arxiv boshqa joyga ochilganda yo'llar chalkashib ketmasligi uchun keng tarqalgan amaliyot.

Asosiy skript oxiriga shu funksiyani chaqirishni qo'shing:

create_backup

Tekshirish:

./backup.sh ~/lab/backup-project/data ~/lab/backup-project/backups
ls -lh ~/lab/backup-project/backups

Kutilgan natija (sana va hajm tizimga qarab farq qiladi):

[2026-07-26 14:10:03] Backup successful: /home/<username>/lab/backup-project/backups/data-20260726-141003.tar.gz
-rw-r--r-- 1 <username> <username> 412 Jul 26 14:10 data-20260726-141003.tar.gz
-rw-r--r-- 1 <username> <username>  63 Jul 26 14:10 backup.log

3-qadam: arxiv butunligini tekshirish

Arxiv yaratilgani uni keyin xatosiz o'qib bo'lishini kafolatlamaydi (to'lib qolgan disk yoki uzilish uni buzib qo'yishi mumkin). tar -tzf arxivni ochmasdan, uning ro'yxatini o'qish orqali butunligini tekshiradi:

verify_backup() {
    local archive_name="$1"

    if tar -tzf "$archive_name" > /dev/null 2>&1; then
        log "Verification OK: $archive_name is intact"
        return 0
    else
        log "ERROR: $archive_name is corrupted or incomplete"
        return 1
    fi
}

create_backup ni arxiv nomini qaytaradigan qilib yangilang, keyin uni tekshiring:

create_backup() {
    local timestamp
    timestamp=$(date +%Y%m%d-%H%M%S)
    local archive_name="${BACKUP_DIR}/$(basename "$SOURCE_DIR")-${timestamp}.tar.gz"

    if tar -czf "$archive_name" -C "$(dirname "$SOURCE_DIR")" "$(basename "$SOURCE_DIR")"; then
        log "Backup successful: $archive_name"
        echo "$archive_name"
        return 0
    else
        log "ERROR: archiving failed"
        return 1
    fi
}

archive_path=$(create_backup)
verify_backup "$archive_path"

Warning

archive_path=$(create_backup) funksiya stdout ga chiqargan narsanigina ushlaydi va u yerda arxiv yo'lidan boshqa hech narsa bo'lmasligi kerak. Yuqoridagi log() o'z chiqishini aynan shuning uchun stderr ga (>&2) yuboradi. Agar log stdout ga yozganida, uning [timestamp] Backup successful: ... qatori yo'l bilan birga ushlanib qolardi va archive_path ikki qatorli satrga aylanardi. Bunday satrni verify_backup (va keyinroq tar -tzf) ocha olmaydi. Command substitution orqali qiymat qaytaradigan funksiya o'z stdout’ini toza saqlashi kerak — har bir log/diagnostika qatorini stderr ga yuboring. Bu Exit kodlar va xatolarni qayta ishlash maqolasidagi "qiymat stdout’da, xabarlar stderr’da" qoidasining funksiyalarga qo'llanishi.

Tekshirish:

./backup.sh ~/lab/backup-project/data ~/lab/backup-project/backups
tail -n 3 ~/lab/backup-project/backups/backup.log

Kutilgan natija:

[2026-07-26 14:15:41] Backup successful: /home/<username>/lab/backup-project/backups/data-20260726-141541.tar.gz
[2026-07-26 14:15:41] Verification OK: /home/<username>/lab/backup-project/backups/data-20260726-141541.tar.gz is intact

4-qadam: eski arxivlarni tozalash (avval ro'yxat, keyin o'chirish)

Eng xavfli qism — shuning uchun u avval -delete bilan emas, faqat ro'yxat sifatida sinab ko'riladi:

find "$BACKUP_DIR" -name "*.tar.gz" -mtime "+${RETENTION_DAYS}"

Bu buyruq hech narsani o'chirmaydi — u faqat RETENTION_DAYS dan eski .tar.gz fayllarni ko'rsatadi. Natija to'g'ri ekaniga ishonch hosil qilgandan keyingina tozalash funksiyasini qo'shish kerak:

cleanup_old_backups() {
    local deleted_count
    deleted_count=$(find "$BACKUP_DIR" -name "*.tar.gz" -mtime "+${RETENTION_DAYS}" -print -delete | wc -l)

    if [ "$deleted_count" -gt 0 ]; then
        log "Cleaned up: $deleted_count archive(s) older than $RETENTION_DAYS days removed"
    else
        log "Cleanup: no old archives found"
    fi
}

Funksiyani asosiy oqimga qo'shing:

cleanup_old_backups

Tekshirish (test uchun sun’iy ravishda eski fayl yaratish):

touch -d "10 days ago" ~/lab/backup-project/backups/old-test.tar.gz
./backup.sh ~/lab/backup-project/data ~/lab/backup-project/backups 7
ls ~/lab/backup-project/backups

Kutilgan natija: old-test.tar.gz ro'yxatda endi ko'rinmasligi kerak, chunki u 7 kundan eski deb belgilangan. backup.log da esa o'chirish haqida yozuv paydo bo'ladi.

Danger

-mtime +N faylning yaratilgan vaqtini emas, oxirgi o'zgartirilgan vaqtini tekshiradi. Agar arxivlar boshqa joydan nusxalab olingan bo'lsa, ularning mtime qiymati asl faylning vaqt belgisini saqlab qolgan bo'lishi mumkin. Bu ularning kutilganidan erta yoki kech o'chirilishiga olib keladi. find ... -mtime +N natijasini -delete bilan birlashtirishdan oldin har doim alohida ko'rib chiqing.

To'liq skript

#!/bin/bash
set -euo pipefail

# backup.sh — archives a directory with a date stamp, verifies its integrity, and cleans up old copies
# Usage: ./backup.sh <source_directory> <backup_directory> [days_to_keep]

SOURCE_DIR="${1:-}"
BACKUP_DIR="${2:-}"
RETENTION_DAYS="${3:-7}"
LOG_FILE="${BACKUP_DIR}/backup.log"

log() {
    # Write to stderr (not stdout) so log lines never pollute a value
    # captured with $(...) — see the note under Step 3.
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE" >&2
}

create_backup() {
    local timestamp
    timestamp=$(date +%Y%m%d-%H%M%S)
    local archive_name="${BACKUP_DIR}/$(basename "$SOURCE_DIR")-${timestamp}.tar.gz"

    if tar -czf "$archive_name" -C "$(dirname "$SOURCE_DIR")" "$(basename "$SOURCE_DIR")"; then
        log "Backup successful: $archive_name"
        echo "$archive_name"
        return 0
    else
        log "ERROR: archiving failed"
        return 1
    fi
}

verify_backup() {
    local archive_name="$1"

    if tar -tzf "$archive_name" > /dev/null 2>&1; then
        log "Verification OK: $archive_name is intact"
        return 0
    else
        log "ERROR: $archive_name is corrupted or incomplete"
        return 1
    fi
}

cleanup_old_backups() {
    local deleted_count
    deleted_count=$(find "$BACKUP_DIR" -name "*.tar.gz" -mtime "+${RETENTION_DAYS}" -print -delete | wc -l)

    if [ "$deleted_count" -gt 0 ]; then
        log "Cleaned up: $deleted_count archive(s) older than $RETENTION_DAYS days removed"
    else
        log "Cleanup: no old archives found"
    fi
}

if [ -z "$SOURCE_DIR" ] || [ -z "$BACKUP_DIR" ]; then
    echo "Error: not enough arguments." >&2
    echo "Usage: $0 <source_directory> <backup_directory> [days_to_keep]" >&2
    exit 2
fi

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

mkdir -p "$BACKUP_DIR"

archive_path=$(create_backup)
verify_backup "$archive_path"
cleanup_old_backups

log "Backup process complete"

Troubleshooting bo'yicha maslahatlar

  • "tar: Removing leading '/' from member names" — arxivlanayotgan yo'l absolyut / bilan boshlanadi. -C va nisbiy yo'l to'g'ri ishlatilganda bu ogohlantirish umuman chiqmasligi kerak. Agar chiqsa, create_backup dagi -C/basename qatorlarini qayta tekshiring.
  • Backup katalogi o'z-o'zini arxivlab qo'ymoqda — agar BACKUP_DIR SOURCE_DIR ichida joylashgan bo'lsa (masalan, data/backups), har bir keyingi backup oldingi arxivlarni ham o'z ichiga oladi va arxiv hajmi tez o'sadi. Backup katalogini har doim manba katalogdan tashqarida saqlang.
  • cleanup_old_backups hech narsani o'chirmayapti — RETENTION_DAYS qiymatini va find ning -mtime shartini -delete siz, alohida sinab ko'ring (yuqoridagi 4-qadamga qarang).
  • Skript cron ostida ishlamaydi, lekin qo'lda ishlaydi — odatda sababi cron ning qisqaroq $PATH i yoki nisbiy yo'l ishlatilgani bo'ladi. Skriptdagi barcha yo'llarni absolyut qilib yozing. cron sozlamalari cron muhiti va log'lari maqolasida ko'rib chiqiladi.

Orqaga qaytarish va tozalash

Lab tugagach, test kataloglarini butunlay o'chirib tashlash mumkin:

rm -rf ~/lab/backup-project

Agar skript haqiqiy ma’lumotlar katalogida sinab ko'rilgan bo'lsa-yu, noto'g'ri fayllar o'chib ketgan bo'lsa, backups katalogidagi eng so'nggi, tekshirilgan (verify_backup tasdiqlagan) arxivdan tiklang:

tar -xzf /backup/data-20260726-141541.tar.gz -C /tmp/restore-test

Yakuniy baholash mezoni

Quyidagilarning hammasi bajarilsa, loyiha muvaffaqiyatli hisoblanadi:

  • argumentsiz ishga tushirilganda skript 2 exit kodi bilan foydalanish (usage) xabarini chiqaradi;
  • mavjud bo'lmagan manba katalogga qarshi ishga tushirilganda 1 exit kodi bilan aniq xato beradi;
  • muvaffaqiyatli holatda arxiv yaratiladi, tar -tzf bilan tasdiqlanadi va backup.log ga yoziladi;
  • RETENTION_DAYS dan eski arxivlar avtomatik o'chiriladi, lekin faqat ataylab tasdiqlangan -mtime sharti asosida;
  • skriptni qayta-qayta ishga tushirish har safar bir xil, xatosiz natija beradi (idempotentlik).

Xulosa

Bu loyiha modulning barcha qismlarini — argumentlarni tekshirish, funksiyalar, exit kodlar, set -euo pipefail va xavfli amalni avval find orqali "quruq sinov" (dry run) bilan tekshirishni — bitta ishlaydigan backup skriptiga birlashtirdi. Keyingi loyiha, Healthcheck skripti, xuddi shunday tuzilmani — funksiyalar, exit kodlar, log yozish — fayllarni arxivlash o'rniga tizim resurslarini (disk, servislar, tarmoq) tekshirishga qo'llaydi.

Manbalar