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:
BACKUP_DIRni haqiqiy backup katalogiga emas, yuqoridagi~/lab/backup-project/backupskabi test katalogiga yo'naltiring;- o'chirish qatorini avval
-deletesiz, faqat ro'yxat chiqarish sifatida sinab ko'ring (quyida ko'rsatilgan); - 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:
Kutilgan natija:
Error: not enough arguments.
Usage: ./backup.sh <source_directory> <backup_directory> [days_to_keep]
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:
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:
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:
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.-Cva nisbiy yo'l to'g'ri ishlatilganda bu ogohlantirish umuman chiqmasligi kerak. Agar chiqsa,create_backupdagi-C/basenameqatorlarini qayta tekshiring. - Backup katalogi o'z-o'zini arxivlab qo'ymoqda — agar
BACKUP_DIRSOURCE_DIRichida 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_backupshech narsani o'chirmayapti —RETENTION_DAYSqiymatini vafindning-mtimeshartini-deletesiz, alohida sinab ko'ring (yuqoridagi 4-qadamga qarang).- Skript
cronostida ishlamaydi, lekin qo'lda ishlaydi — odatda sababicronning qisqaroq$PATHi yoki nisbiy yo'l ishlatilgani bo'ladi. Skriptdagi barcha yo'llarni absolyut qilib yozing.cronsozlamalari cron muhiti va log'lari maqolasida ko'rib chiqiladi.
Orqaga qaytarish va tozalash
Lab tugagach, test kataloglarini butunlay o'chirib tashlash mumkin:
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:
Yakuniy baholash mezoni
Quyidagilarning hammasi bajarilsa, loyiha muvaffaqiyatli hisoblanadi:
- argumentsiz ishga tushirilganda skript
2exit kodi bilan foydalanish (usage) xabarini chiqaradi; - mavjud bo'lmagan manba katalogga qarshi ishga tushirilganda
1exit kodi bilan aniq xato beradi; - muvaffaqiyatli holatda arxiv yaratiladi,
tar -tzfbilan tasdiqlanadi vabackup.logga yoziladi; RETENTION_DAYSdan eski arxivlar avtomatik o'chiriladi, lekin faqat ataylab tasdiqlangan-mtimesharti 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.