Mundarija (21)
- 1. Kirish va motivatsiya
- 2. Nazariya — chuqur tushuntirish
- 2.1. Fayl nusxalash: uch daraja
- 2.2. copytree
- 2.3. move
- 2.4. rmtree
- 2.5. Arxivlar
- 2.6. Arxiv xavfsizligi
- 2.7. Tizim yordamchilari
- 3. Tez ma'lumotnoma
- 4. Batafsil misollar
- Misol 1 — Nusxalash va ko'chirish
- Misol 2 — Xavfsiz o'chirish
- Misol 3 — Arxivlar va xavfsizlik
- Misol 4 — Amaliy: aylanma zaxira nusxa vositasi
- 5. To'g'ri va noto'g'ri tushunishlar
- 6. Keng tarqalgan xatolar va yechimlari
- 7. Integratsiya — bu bilim qayerda kerak bo'ladi
- 8. Eng yaxshi amaliyotlar
- 9. Amaliy topshiriq
- Xulosa
15.13-dars: shutil va fayl amallari
15-QISM — STANDART KUTUBXONA · 13-dars
1. Kirish va motivatsiya
pathlib yo'llar va bitta fayl bilan ishlashni yaxshi qiladi. Lekin real vazifalar yuqori darajadagi amallarni talab qiladi: butun katalogni ma'lum fayllarsiz nusxalash, daraxtni o'chirish, arxiv yaratish va ochish, diskda joy borligini tekshirish, tizimda dastur o'rnatilganini aniqlash. Bular — shutil (shell utilities) moduli.
Real vaziyat. Zaxira skripti har kecha loyihani arxivlab, eski arxivlarni o'chirardi:
shutil.rmtree(os.path.join(ZAXIRA_KATALOGI, eski_nom))Bir kuni eski_nom bo'sh satr bo'lib qoldi. os.path.join(ZAXIRA_KATALOGI, "") — bu butun zaxira katalogi. Barcha zaxiralar o'chdi. Keyingi haftada disk to'lib, yangi zaxira yarim yozilgan holda qoldi — skript diskda joy borligini tekshirmagan edi. Boshqa jamoada esa foydalanuvchi yuklagan arxivni ochish serverdagi sozlama faylini ustidan yozib yubordi.
Bu darsda shutil ni to'liq o'rganamiz — va har bir "kuchli" amalni xavfsiz qilish usullarini.
Bu darsda:
-
copyfile,copy,copy2— nima nusxalanadi, nima yo'q -
copytree:ignore,dirs_exist_ok move— qachon nomlash, qachon nusxa + o'chirish-
rmtree: faqat o'qiladigan fayllar,onexc3.12-bob, himoya tekshiruvlari - Arxivlar:
make_archive,unpack_archive,root_dirvabase_dir - Arxiv xavfsizligi: zip slip va tar filtrlari (3.14 sukuti)
disk_usage,which,get_terminal_size- Amaliy: aylanma zaxira nusxa vositasi
2. Nazariya — chuqur tushuntirish
2.1. Fayl nusxalash: uch daraja
| Funksiya | Mazmun | Ruxsatlar (mode) | Vaqtlar va metama'lumot | dst katalog bo'lsa |
|---|---|---|---|---|
copyfile(src, dst) |
Xato | |||
copy(src, dst) |
Ichiga, shu nom bilan | |||
copy2(src, dst) |
(mtime, atime, imkon qadar kengaytirilgan atributlar) |
|||
Path.copy(dst) (3.14) |
(sukut) | Fayl yo'li kerak |
copyfile ga katalog berilsa: Linux'da IsADirectoryError, Windows'da PermissionError.
Zaxira va sinxronlash uchun — copy2: aks holda "fayl o'zgarganmi?" tekshiruvi (vaqt bo'yicha) buziladi.
Boshqa funksiyalar: copymode (faqat ruxsatlar), copystat (ruxsatlar va vaqtlar), copyfileobj(fsrc, fdst, length) — ochiq fayl obyektlari orasida bo'laklab.
2.2. copytree
shutil.copytree(
"loyiha", "zaxira/loyiha",
ignore=shutil.ignore_patterns("__pycache__", "*.pyc", ".git"),
dirs_exist_ok=True, # maqsad mavjud bo'lsa ham
copy_function=shutil.copy2, # sukut
)| Parametr | Ma'nosi |
|---|---|
ignore |
(katalog, nomlar) -> tashlanadigan_nomlar funksiyasi; ignore_patterns(...) tayyor |
dirs_exist_ok=False |
Maqsad mavjud bo'lsa FileExistsError |
symlinks=False |
Havolalar o'rniga ular ko'rsatgan fayllar nusxalanadi |
copy_function |
Har fayl uchun: copy2 (sukut), copy, o'z funksiyangiz |
2.3. move
| Holat | shutil.move(src, dst) |
|---|---|
dst mavjud katalog |
src uning ichiga ko'chiriladi |
| Bir fayl tizimida | os.rename — tez, atomar |
| Turli disklar / bo'limlar | Nusxa + o'chirish — sekin, atomar emas |
dst mavjud fayl |
Ustiga yozadi (platformaga bog'liq xulqlar bor) |
Natija sifatida yangi yo'l qaytariladi.
2.4. rmtree
shutil.rmtree(yol) — katalogni butun ichidagisi bilan o'chiradi. Qaytarib bo'lmaydi.
| Holat | Xulq |
|---|---|
Faqat o'qiladigan fayl (Windows, .git obyektlari) |
PermissionError |
onexc=funksiya (3.12+) |
Xatoda chaqiriladi: (funksiya, yol, istisno) — ruxsatni o'zgartirib qayta urinish |
onerror |
Eski nom, 3.12 dan eskirgan |
ignore_errors=True |
Xatolarni jim yutadi — yarim o'chirilgan katalog qolishi mumkin |
Himoya tekshiruvlari (rmtree oldidan):
- Yo'l bo'sh emas va
resolve()qilingan - Ildiz (
/,C:\), uy katalogi yoki loyiha ildizi emas - Ruxsat berilgan ota katalog ichida (
is_relative_to) - Kutilgan nom shakliga mos (masalan,
zaxira-2026-...)
2.5. Arxivlar
shutil.make_archive(base_name, format, root_dir=None, base_dir=None)
shutil.unpack_archive(fayl, maqsad, format=None, *, filter=None)| Format | Kengaytma |
|---|---|
zip |
.zip |
tar, gztar, bztar, xztar |
.tar, .tar.gz, .tar.bz2, .tar.xz |
zstdtar |
.tar.zst — 3.14+ |
| Parametr | Ma'nosi |
|---|---|
base_name |
Kengaytmasiz nom — format kengaytmasi qo'shiladi |
root_dir |
Arxiv ichidagi yo'llar shu katalogga nisbatan |
base_dir |
root_dir ichidan qaysi qism arxivlanadi — arxivda shu nom bilan saqlanadi |
make_archive("zaxira", "zip", root_dir="loyiha") # ichida: src/..., README.md
make_archive("zaxira", "gztar", root_dir=".", base_dir="loyiha") # ichida: loyiha/src/...2.6. Arxiv xavfsizligi
Arxiv ichidagi nom ../../etc/cron.d/x yoki absolyut yo'l bo'lishi mumkin (zip slip). Ochganda fayl maqsad katalogdan tashqariga yoziladi.
| Vosita | Xulq |
|---|---|
shutil.unpack_archive + zip |
.. yoki / bilan boshlanadigan nomlarni jim o'tkazib yuboradi |
tarfile / unpack_archive + tar (3.14) |
Sukut filtri "data": tashqariga yo'l, xavfli ruxsatlar, qurilma fayllari — OutsideDestinationError va boshqa xatolar |
filter="fully_trusted" |
Filtrsiz — faqat o'zingiz yaratgan arxivlar |
| Hajm portlashi (zip bomb) | Hech bir vosita cheklamaydi — ochishdan oldin infolist() bilan umumiy hajmni tekshiring |
Qoida: ishonchsiz arxivni alohida vaqtinchalik katalogga oching, hajm va fayllar sonini oldindan tekshiring, keyin kerakli fayllarni ko'chiring.
2.7. Tizim yordamchilari
| Funksiya | Qaytaradi |
|---|---|
disk_usage(yol) |
usage(total, used, free) — baytlarda |
which("git") |
Dastur yo'li yoki None (PATH bo'yicha; Windows'da PATHEXT ham) |
get_terminal_size((80, 24)) |
Terminal o'lchami; aniqlanmasa — berilgan sukut |
chown(yol, user, group) |
Egasini o'zgartirish (Unix) |
Katta yozuvdan oldin disk_usage(...).free ni tekshiring — yarim yozilgan arxiv yaroqsiz arxivdan ham yomonroq.
3. Tez ma'lumotnoma
import shutil
shutil.copy2(manba, maqsad) # metama'lumot bilan
shutil.copytree(a, b, ignore=shutil.ignore_patterns("__pycache__", ".git"), dirs_exist_ok=True)
shutil.move(a, b)
shutil.rmtree(yol, onexc=ruxsatni_tuzat) # xavfsizlik tekshiruvidan keyin!
arxiv = shutil.make_archive("zaxira-2026-09-17", "gztar", root_dir=ildiz)
shutil.unpack_archive(arxiv, vaqtinchalik_katalog)
shutil.disk_usage("/").free; shutil.which("git")Qoidalar
zaxira — copy2, copy emas
copytree maqsad mavjud — dirs_exist_ok=True
rmtree oldidan — yo'l tekshiruvi (bo'sh, ildiz, ruxsat etilgan katalog)
Windows faqat o'qiladigan fayllar — onexc
arxiv yo'llari — root_dir va base_dir
ishonchsiz arxiv — alohida katalog, hajm tekshiruvi
katta yozuv oldidan — disk_usage4. Batafsil misollar
Misol 1 — Nusxalash va ko'chirish
"""copyfile, copy, copy2 va metama'lumot; katalogga nusxalash; copytree ignore va dirs_exist_ok; o'z ignore funksiyasi; move xulqi."""
import os
import shutil
import tempfile
from pathlib import Path
ESKI_VAQT = 1_700_000_000
def main() -> None:
with tempfile.TemporaryDirectory() as vaqtinchalik:
r = Path(vaqtinchalik)
manba = r / "hisobot.txt"
manba.write_text("2026-yil hisoboti", encoding="utf-8")
os.utime(manba, (ESKI_VAQT, ESKI_VAQT))
print("=== 1. Uch daraja ===")
shutil.copyfile(manba, r / "copyfile.txt")
shutil.copy(manba, r / "copy.txt")
shutil.copy2(manba, r / "copy2.txt")
for nom in ("copyfile.txt", "copy.txt", "copy2.txt"):
yol = r / nom
print(f" {nom:13} mazmun bir xil: {yol.read_text(encoding='utf-8') == manba.read_text(encoding='utf-8')}, "
f"vaqt saqlandi: {yol.stat().st_mtime == ESKI_VAQT}")
print("\n=== 2. Katalogga nusxalash ===")
(r / "arxiv").mkdir()
natija = shutil.copy2(manba, r / "arxiv")
print(f" copy2(fayl, katalog) → {Path(natija).relative_to(r).as_posix()}")
try:
shutil.copyfile(manba, r / "arxiv")
except (IsADirectoryError, PermissionError) as xato:
print(f" copyfile(fayl, katalog) → {type(xato).__name__} (Linux'da IsADirectoryError)")
print("\n=== 3. copytree ===")
loyiha = r / "loyiha"
for nisbiy in ("src/app.py", "src/__pycache__/app.cpython-314.pyc", ".git/HEAD", "README.md",
"data/katta.csv", "logs/bugun.log"):
yol = loyiha / nisbiy
yol.parent.mkdir(parents=True, exist_ok=True)
yol.write_text("x" * (2000 if nisbiy.endswith(".csv") else 10), encoding="utf-8")
shutil.copytree(loyiha, r / "zaxira", ignore=shutil.ignore_patterns("__pycache__", ".git", "*.log"))
print(f" ignore_patterns: {sorted(p.relative_to(r / 'zaxira').as_posix() for p in (r / 'zaxira').rglob('*') if p.is_file())}")
def katta_fayllarsiz(katalog: str, nomlar: list[str]) -> set[str]:
return {n for n in nomlar if os.path.isfile(os.path.join(katalog, n)) and os.path.getsize(os.path.join(katalog, n)) > 1000}
shutil.copytree(loyiha, r / "yengil", ignore=katta_fayllarsiz)
print(f" o'z ignore funksiyasi (>1000 bayt): 'data/katta.csv' yo'q: {not (r / 'yengil' / 'data' / 'katta.csv').exists()}")
try:
shutil.copytree(loyiha, r / "zaxira")
except FileExistsError:
print(" qayta copytree → FileExistsError")
shutil.copytree(loyiha, r / "zaxira", dirs_exist_ok=True, ignore=shutil.ignore_patterns(".git"))
print(f" dirs_exist_ok=True bilan yangilandi, logs qo'shildi: {(r / 'zaxira' / 'logs' / 'bugun.log').exists()}")
print("\n=== 4. move ===")
(r / "qabul").mkdir()
yangi = shutil.move(r / "copy.txt", r / "qabul")
print(f" mavjud katalogga: {Path(yangi).relative_to(r).as_posix()}")
yangi = shutil.move(r / "copyfile.txt", r / "qabul" / "nomi_ozgardi.txt")
print(f" yangi nom bilan: {Path(yangi).relative_to(r).as_posix()}")
yangi = shutil.move(r / "yengil", r / "qabul")
print(f" katalog: {Path(yangi).relative_to(r).as_posix()}, ichida README: {(Path(yangi) / 'README.md').exists()}")
if __name__ == "__main__":
main()Natijaning muhim qismi:
=== 1. Uch daraja ===
copyfile.txt mazmun bir xil: True, vaqt saqlandi: False
copy.txt mazmun bir xil: True, vaqt saqlandi: False
copy2.txt mazmun bir xil: True, vaqt saqlandi: True
=== 2. Katalogga nusxalash ===
copy2(fayl, katalog) → arxiv/hisobot.txt
copyfile(fayl, katalog) → PermissionError (Linux'da IsADirectoryError)
=== 3. copytree ===
ignore_patterns: ['README.md', 'data/katta.csv', 'src/app.py']
o'z ignore funksiyasi (>1000 bayt): 'data/katta.csv' yo'q: True
qayta copytree → FileExistsError
dirs_exist_ok=True bilan yangilandi, logs qo'shildi: True
=== 4. move ===
mavjud katalogga: qabul/copy.txt
yangi nom bilan: qabul/nomi_ozgardi.txt
katalog: qabul/yengil, ichida README: TrueNima ko'rsatdi: 2.1, 2.2, 2.3-bo'limlar.
Misol 2 — Xavfsiz o'chirish
"""Bo'sh yo'l bilan join tuzog'i; rmtree himoya funksiyasi; faqat o'qiladigan fayllar va onexc; ignore_errors xavfi."""
import os
import shutil
import stat
import tempfile
from pathlib import Path
class XavfliOchirish(Exception):
pass
def xavfsiz_rmtree(yol: Path, ruxsat_ildizi: Path, nom_prefiksi: str) -> None:
yol, ruxsat_ildizi = yol.resolve(), ruxsat_ildizi.resolve()
if yol == ruxsat_ildizi:
raise XavfliOchirish("ruxsat ildizining o'zini o'chirib bo'lmaydi")
if yol == Path(yol.anchor) or yol == Path.home().resolve():
raise XavfliOchirish(f"tizim katalogi: {yol}")
if not yol.is_relative_to(ruxsat_ildizi):
raise XavfliOchirish("ruxsat etilgan katalogdan tashqarida")
if not yol.name.startswith(nom_prefiksi):
raise XavfliOchirish(f"nom {nom_prefiksi!r} bilan boshlanmaydi: {yol.name!r}")
def onexc(funksiya, xato_yoli, istisno):
if isinstance(istisno, PermissionError):
os.chmod(xato_yoli, stat.S_IWRITE)
funksiya(xato_yoli)
else:
raise istisno
shutil.rmtree(yol, onexc=onexc)
def main() -> None:
with tempfile.TemporaryDirectory() as vaqtinchalik:
zaxiralar = Path(vaqtinchalik) / "zaxiralar"
for nom in ("zaxira-2026-09-15", "zaxira-2026-09-16", "muhim-hujjatlar"):
(zaxiralar / nom).mkdir(parents=True)
(zaxiralar / nom / "fayl.txt").write_text(nom, encoding="utf-8")
print("=== 1. ⚠️ Bo'sh nom bilan join ===")
eski_nom = ""
print(f" os.path.join(zaxiralar, '') ildizning o'zi: {Path(os.path.join(zaxiralar, eski_nom)).resolve() == zaxiralar.resolve()}")
print(f" Path(zaxiralar) / '' ham: {(zaxiralar / eski_nom).resolve() == zaxiralar.resolve()}")
print("\n=== 2. Himoya tekshiruvlari ===")
sinovlar = [("bo'sh nom", zaxiralar / ""), ("boshqa katalog", zaxiralar / "muhim-hujjatlar"),
("tashqariga ..", zaxiralar / ".." / ".."), ("to'g'ri", zaxiralar / "zaxira-2026-09-15")]
for tavsif, yol in sinovlar:
try:
xavfsiz_rmtree(yol, zaxiralar, "zaxira-")
print(f" {tavsif:14} → o'chirildi")
except XavfliOchirish as xato:
print(f" {tavsif:14} → rad: {xato}")
print(f" qolganlar: {sorted(p.name for p in zaxiralar.iterdir())}")
print("\n=== 3. ⚠️ Faqat o'qiladigan fayllar ===")
git = Path(vaqtinchalik) / "klon" / ".git" / "objects"
git.mkdir(parents=True)
obyekt = git / "ab12"
obyekt.write_bytes(b"blob")
obyekt.chmod(stat.S_IREAD)
try:
shutil.rmtree(Path(vaqtinchalik) / "klon")
print(" oddiy rmtree o'chirdi (Linux/macOS'da ota katalog ruxsati yetarli)")
except PermissionError:
print(" oddiy rmtree → PermissionError (Windows)")
shutil.rmtree(Path(vaqtinchalik) / "klon", ignore_errors=True)
print(f" ignore_errors=True dan keyin katalog hali ham bor: {(Path(vaqtinchalik) / 'klon').exists()} ← jim yarim natija")
if (Path(vaqtinchalik) / "klon").exists():
(Path(vaqtinchalik) / "klon").rename(zaxiralar / "zaxira-klon")
xavfsiz_rmtree(zaxiralar / "zaxira-klon", zaxiralar, "zaxira-")
print(f" onexc bilan to'liq o'chirildi: {not (zaxiralar / 'zaxira-klon').exists()}")
if __name__ == "__main__":
main()Natijaning muhim qismi:
=== 1. ⚠️ Bo'sh nom bilan join ===
os.path.join(zaxiralar, '') ildizning o'zi: True
Path(zaxiralar) / '' ham: True
=== 2. Himoya tekshiruvlari ===
bo'sh nom → rad: ruxsat ildizining o'zini o'chirib bo'lmaydi
boshqa katalog → rad: nom 'zaxira-' bilan boshlanmaydi: 'muhim-hujjatlar'
tashqariga .. → rad: ruxsat etilgan katalogdan tashqarida
to'g'ri → o'chirildi
qolganlar: ['muhim-hujjatlar', 'zaxira-2026-09-16']
=== 3. ⚠️ Faqat o'qiladigan fayllar ===
oddiy rmtree → PermissionError (Windows)
ignore_errors=True dan keyin katalog hali ham bor: True ← jim yarim natija
onexc bilan to'liq o'chirildi: TrueNima ko'rsatdi: 2.4-bo'lim.
Misol 3 — Arxivlar va xavfsizlik
"""make_archive: root_dir va base_dir farqi; formatlar; unpack_archive; zip slip — jim o'tkazish; tar filtri 3.14; hajm tekshiruvi; disk_usage, which, terminal o'lchami."""
import io
import shutil
import tarfile
import tempfile
import zipfile
from pathlib import Path
def ichidagilar(arxiv: str) -> list[str]:
if arxiv.endswith(".zip"):
with zipfile.ZipFile(arxiv) as z:
return sorted(z.namelist())
with tarfile.open(arxiv) as t:
return sorted(t.getnames())
def main() -> None:
with tempfile.TemporaryDirectory() as vaqtinchalik:
r = Path(vaqtinchalik)
loyiha = r / "loyiha"
(loyiha / "src").mkdir(parents=True)
(loyiha / "src" / "app.py").write_text("print(1)\n", encoding="utf-8")
(loyiha / "README.md").write_text("# Loyiha\n", encoding="utf-8")
print("=== 1. root_dir va base_dir ===")
a = shutil.make_archive(str(r / "faqat_ichi"), "zip", root_dir=loyiha)
b = shutil.make_archive(str(r / "katalog_bilan"), "gztar", root_dir=r, base_dir="loyiha")
print(f" {Path(a).name:24} {ichidagilar(a)}")
print(f" {Path(b).name:24} {[n for n in ichidagilar(b) if n.endswith(('.py', '.md'))]}")
print(f" formatlar: {sorted(nom for nom, _ in shutil.get_archive_formats())}")
print("\n=== 2. Ochish ===")
shutil.unpack_archive(a, r / "ochildi")
print(f" {sorted(p.relative_to(r / 'ochildi').as_posix() for p in (r / 'ochildi').rglob('*') if p.is_file())}")
print("\n=== 3. ⚠️ Zip slip ===")
yomon_zip = r / "yomon.zip"
with zipfile.ZipFile(yomon_zip, "w") as z:
z.writestr("rasm.png", b"png")
z.writestr("../../tashqari.txt", b"hujum")
print(f" arxiv ichida: {ichidagilar(str(yomon_zip))}")
shutil.unpack_archive(yomon_zip, r / "zip_ochildi")
print(f" unpack_archive: xavfsiz fayl ochildi: {(r / 'zip_ochildi' / 'rasm.png').exists()}, "
f"tashqariga yozilmadi: {not (r.parent / 'tashqari.txt').exists() and not (r / 'tashqari.txt').exists()}")
print(" ⚠️ xavfli nom jim o'tkazib yuborildi — xabar yo'q")
print("\n=== 4. ⭐ tar filtri (3.14 sukuti 'data') ===")
yomon_tar = r / "yomon.tar"
with tarfile.open(yomon_tar, "w") as t:
malumot = b"hujum"
info = tarfile.TarInfo("../tashqari2.txt")
info.size = len(malumot)
t.addfile(info, io.BytesIO(malumot))
try:
shutil.unpack_archive(yomon_tar, r / "tar_ochildi")
except tarfile.OutsideDestinationError as xato:
print(f" {type(xato).__name__}: '{xato.tarinfo.name}' maqsaddan tashqarida")
print("\n=== 5. Hajm tekshiruvi (zip bomb) ===")
bomba = r / "bomba.zip"
with zipfile.ZipFile(bomba, "w", compression=zipfile.ZIP_DEFLATED) as z:
z.writestr("nol.bin", b"\x00" * 20_000_000)
with zipfile.ZipFile(bomba) as z:
ochilgan = sum(i.file_size for i in z.infolist())
siqilgan = bomba.stat().st_size
print(f" arxiv hajmi 100 KB dan kam: {siqilgan < 100_000}, ochilganda 20 MB: {ochilgan == 20_000_000}")
print(f" siqish nisbati 200 dan katta: {ochilgan / siqilgan > 200} → ochishdan oldin rad etish kerak")
print("\n=== 6. Tizim yordamchilari ===")
joy = shutil.disk_usage(r)
print(f" disk_usage: total > used > 0: {joy.total > joy.used > 0}, free > 0: {joy.free > 0}")
print(f" which('python') yoki which('py') topildi: {bool(shutil.which('python') or shutil.which('py'))}")
print(f" which('bunday-dastur-yoq'): {shutil.which('bunday-dastur-yoq')}")
print(f" get_terminal_size((80, 24)) turi: {type(shutil.get_terminal_size((80, 24))).__name__}")
if __name__ == "__main__":
main()Natijaning muhim qismi:
=== 1. root_dir va base_dir ===
faqat_ichi.zip ['README.md', 'src/', 'src/app.py']
katalog_bilan.tar.gz ['loyiha/README.md', 'loyiha/src/app.py']
formatlar: ['bztar', 'gztar', 'tar', 'xztar', 'zip', 'zstdtar']
=== 2. Ochish ===
['README.md', 'src/app.py']
=== 3. ⚠️ Zip slip ===
arxiv ichida: ['../../tashqari.txt', 'rasm.png']
unpack_archive: xavfsiz fayl ochildi: True, tashqariga yozilmadi: True
⚠️ xavfli nom jim o'tkazib yuborildi — xabar yo'q
=== 4. ⭐ tar filtri (3.14 sukuti 'data') ===
OutsideDestinationError: '../tashqari2.txt' maqsaddan tashqarida
=== 5. Hajm tekshiruvi (zip bomb) ===
arxiv hajmi 100 KB dan kam: True, ochilganda 20 MB: True
siqish nisbati 200 dan katta: True → ochishdan oldin rad etish kerak
=== 6. Tizim yordamchilari ===
disk_usage: total > used > 0: True, free > 0: True
which('python') yoki which('py') topildi: True
which('bunday-dastur-yoq'): None
get_terminal_size((80, 24)) turi: terminal_sizeNima ko'rsatdi: 2.5, 2.6, 2.7-bo'limlar.
Misol 4 — Amaliy: aylanma zaxira nusxa vositasi
Kirishdagi vaziyatning to'g'ri yechimi. Vosita: loyihani keraksiz fayllarsiz arxivlaydi, nomga vaqt belgisini qo'yadi (hozir parametridan), yozishdan oldin diskda joyni tekshiradi, arxivni vaqtinchalik nom bilan yaratib tekshirgandan keyin replace qiladi, arxivni ochib mazmunini xesh bilan solishtiradi va faqat oxirgi N ta zaxirani saqlaydi — o'chirish himoyalangan. Bir necha "kun" simulyatsiya qilinadi.
"""copytree ignore bilan tayyorlash; make_archive vaqt belgili nom; disk_usage tekshiruvi; vaqtinchalik nom + os.replace; ochib xesh bilan tekshirish; aylanish va himoyalangan o'chirish."""
import hashlib
import os
import shutil
import tempfile
from datetime import UTC, datetime, timedelta
from pathlib import Path
PREFIKS = "zaxira-"
OTKAZ = shutil.ignore_patterns("__pycache__", ".git", "*.log", "node_modules")
def xeshlar(ildiz: Path) -> dict[str, str]:
return {p.relative_to(ildiz).as_posix(): hashlib.sha256(p.read_bytes()).hexdigest()
for p in sorted(ildiz.rglob("*")) if p.is_file()}
def zaxira_ol(loyiha: Path, zaxiralar: Path, hozir: datetime, saqlash: int, min_bosh_joy: int) -> dict[str, object]:
zaxiralar.mkdir(parents=True, exist_ok=True)
nom = f"{PREFIKS}{hozir:%Y%m%d-%H%M%S}"
if shutil.disk_usage(zaxiralar).free < min_bosh_joy:
raise OSError("diskda joy yetarli emas")
with tempfile.TemporaryDirectory() as ish:
tayyor = Path(ish) / "loyiha"
shutil.copytree(loyiha, tayyor, ignore=OTKAZ)
asl = xeshlar(tayyor)
vaqtinchalik = shutil.make_archive(str(Path(ish) / nom), "gztar", root_dir=tayyor)
tekshiruv = Path(ish) / "tekshiruv"
shutil.unpack_archive(vaqtinchalik, tekshiruv)
if xeshlar(tekshiruv) != asl:
raise RuntimeError("arxiv mazmuni mos kelmadi")
yakuniy = zaxiralar / Path(vaqtinchalik).name
shutil.copy2(vaqtinchalik, zaxiralar / (yakuniy.name + ".tmp"))
os.replace(zaxiralar / (yakuniy.name + ".tmp"), yakuniy)
mavjud = sorted(p for p in zaxiralar.iterdir() if p.name.startswith(PREFIKS) and p.name.endswith(".tar.gz"))
ochirilgan = []
for eski in mavjud[:-saqlash]:
yol = eski.resolve()
if yol.parent == zaxiralar.resolve() and yol.name.startswith(PREFIKS):
yol.unlink()
ochirilgan.append(yol.name)
return {"arxiv": yakuniy.name, "fayllar": len(asl), "ochirilgan": ochirilgan}
def main() -> None:
with tempfile.TemporaryDirectory() as vaqtinchalik:
r = Path(vaqtinchalik)
loyiha, zaxiralar = r / "loyiha", r / "zaxiralar"
for nisbiy, mazmun in {"src/app.py": "v1", "src/__pycache__/app.pyc": "kesh", "README.md": "# L",
"server.log": "log", ".git/HEAD": "ref"}.items():
(loyiha / nisbiy).parent.mkdir(parents=True, exist_ok=True)
(loyiha / nisbiy).write_text(mazmun, encoding="utf-8")
(zaxiralar / "muhim.txt").parent.mkdir(parents=True)
(zaxiralar / "muhim.txt").write_text("boshqa fayl — tegmaslik kerak", encoding="utf-8")
print("=== 1. Besh kunlik zaxira, oxirgi 3 tasi saqlanadi ===")
boshlanish = datetime(2026, 9, 13, 2, 0, tzinfo=UTC)
for kun in range(5):
(loyiha / "src" / "app.py").write_text(f"v{kun + 1}", encoding="utf-8")
natija = zaxira_ol(loyiha, zaxiralar, boshlanish + timedelta(days=kun), saqlash=3, min_bosh_joy=1024)
print(f" {natija['arxiv']} fayllar={natija['fayllar']} o'chirildi={natija['ochirilgan']}")
print("\n=== 2. Holat ===")
print(f" zaxiralar katalogi: {sorted(p.name for p in zaxiralar.iterdir())}")
print(f" .tmp qolmadi: {not any(p.suffix == '.tmp' for p in zaxiralar.iterdir())}")
print(f" begona fayl saqlandi: {(zaxiralar / 'muhim.txt').exists()}")
print("\n=== 3. Eng oxirgi zaxirani tiklash ===")
oxirgi = sorted(zaxiralar.glob(f"{PREFIKS}*.tar.gz"))[-1]
tiklash = r / "tiklandi"
shutil.unpack_archive(oxirgi, tiklash)
print(f" tiklangan fayllar: {sorted(xeshlar(tiklash))}")
print(f" app.py oxirgi versiya: {(tiklash / 'src' / 'app.py').read_text(encoding='utf-8')!r}")
print(f" keraksizlar kirmadi (__pycache__, .git, *.log): {not any(k in s for s in xeshlar(tiklash) for k in ('__pycache__', '.git', '.log'))}")
print("\n=== 4. Diskda joy yetmasa ===")
try:
zaxira_ol(loyiha, zaxiralar, boshlanish + timedelta(days=10), saqlash=3, min_bosh_joy=10**18)
except OSError as xato:
print(f" OSError: {xato} — mavjud zaxiralar tegilmadi: {len(list(zaxiralar.glob(PREFIKS + '*')))} ta")
if __name__ == "__main__":
main()Natijaning muhim qismi:
=== 1. Besh kunlik zaxira, oxirgi 3 tasi saqlanadi ===
zaxira-20260913-020000.tar.gz fayllar=2 o'chirildi=[]
zaxira-20260914-020000.tar.gz fayllar=2 o'chirildi=[]
zaxira-20260915-020000.tar.gz fayllar=2 o'chirildi=[]
zaxira-20260916-020000.tar.gz fayllar=2 o'chirildi=['zaxira-20260913-020000.tar.gz']
zaxira-20260917-020000.tar.gz fayllar=2 o'chirildi=['zaxira-20260914-020000.tar.gz']
=== 2. Holat ===
zaxiralar katalogi: ['muhim.txt', 'zaxira-20260915-020000.tar.gz', 'zaxira-20260916-020000.tar.gz', 'zaxira-20260917-020000.tar.gz']
.tmp qolmadi: True
begona fayl saqlandi: True
=== 3. Eng oxirgi zaxirani tiklash ===
tiklangan fayllar: ['README.md', 'src/app.py']
app.py oxirgi versiya: 'v5'
keraksizlar kirmadi (__pycache__, .git, *.log): True
=== 4. Diskda joy yetmasa ===
OSError: diskda joy yetarli emas — mavjud zaxiralar tegilmadi: 3 taNima ko'rsatdi: 2.1, 2.2, 2.4, 2.5, 2.7-bo'limlar.
5. To'g'ri va noto'g'ri tushunishlar
| Noto'g'ri fikr | To'g'risi |
|---|---|
"shutil.copy hamma narsani nusxalaydi" |
Vaqtlar saqlanmaydi — copy2 |
"copytree mavjud katalogga qo'shadi" |
FileExistsError — dirs_exist_ok=True |
"move doim tez va atomar" |
Turli disklarda nusxa + o'chirish |
"rmtree hamma joyda bir xil ishlaydi" |
Windows'da faqat o'qiladigan fayllar — onexc |
"ignore_errors=True xavfsiz" |
Yarim o'chirilgan katalog jim qoladi |
"os.path.join(ildiz, nom) doim ildiz ichidagi narsa" |
nom="" — ildizning o'zi |
| "Arxivni ochish xavfsiz" | Zip slip, zip bomb — tekshiruv kerak |
"base_dir va root_dir bir xil" |
Arxiv ichidagi yo'llar boshqacha bo'ladi |
6. Keng tarqalgan xatolar va yechimlari
1. Himoyasiz rmtree
shutil.rmtree(os.path.join(ZAXIRA, nom)) # ❌ nom="" — hammasi
xavfsiz_rmtree(ZAXIRA / nom, ZAXIRA, "zaxira-") # ✅2. Zaxirada copy
shutil.copy(manba, zaxira) # ⚠️ vaqt yo'qoladi
shutil.copy2(manba, zaxira) # ✅3. Joyni tekshirmaslik
shutil.make_archive(...) # ⚠️ disk to'lsa — yaroqsiz arxiv
if shutil.disk_usage(katalog).free < kerak: raise OSError(...) # ✅4. Arxivni tekshirmay yakuniy nomga yozish
shutil.make_archive(ZAXIRA / nom, ...) # ⚠️ uzilsa — buzilgan "yangi" zaxira
# ✅ vaqtinchalik joyda yaratish, ochib tekshirish, os.replace5. Ishonchsiz arxivni to'g'ridan-to'g'ri ochish
shutil.unpack_archive(yuklangan, "/srv/app") # ❌
# ✅ vaqtinchalik katalog + hajm tekshiruvi + kerakli fayllarni ko'chirish6. filter="fully_trusted" ni odat qilish
tarfile.open(f).extractall(maqsad, filter="fully_trusted") # ❌ ishonchsiz arxivda7. which natijasini tekshirmaslik
subprocess.run([shutil.which("ffmpeg"), ...]) # ❌ None → TypeError
if (ffmpeg := shutil.which("ffmpeg")) is None: sys.exit("ffmpeg o'rnatilmagan") # ✅8. copytree bilan keraksiz fayllar
shutil.copytree(loyiha, zaxira) # ⚠️ node_modules, .git, loglar
shutil.copytree(loyiha, zaxira, ignore=shutil.ignore_patterns(...)) # ✅7. Integratsiya — bu bilim qayerda kerak bo'ladi
- 15.11–15.12-darslar (o'tilgan):
os.walk,os.replace,pathlib, path traversal - 15.14-dars:
subprocess—whichbilan topilgan dasturlarni ishga tushirish - 16-qism: fayl formatlari,
zipfilevatarfiletafsilotlari - 17-qism: testlarda vaqtinchalik kataloglar
- 19-qism: paket yig'ish — arxivlar
- 20.13-dars: fayl yuklash — zip slip, zip bomb
- 26-qism: avtomatlashtirish — zaxira, joylashtirish skriptlari
8. Eng yaxshi amaliyotlar
Zaxira va sinxronlash —
copy2.copytree— doimignorebilan.rmtree— faqat himoya funksiyasi orqali.Katta yozuv — joy tekshiruvi, vaqtinchalik nom, tekshiruv,
os.replace.Arxiv mazmunini ochib, xesh bilan tekshiring.
Ishonchsiz arxiv — alohida katalog, hajm va nisbat tekshiruvi, 3.14 filtrlari.
Tashqi dastur —
whichbilan oldindan tekshiring.Xavfli ommaviy amallarda — quruq ishga tushirish rejimi.
9. Amaliy topshiriq
Vazifa 1: Natijani bashorat qiling
import shutil, os
1. print(os.path.join("zaxira", "") in ("zaxira/", "zaxira\\"))
2. print(shutil.which("bunday_dastur_yoq_123"))
3. print(type(shutil.disk_usage(".")).__name__)
4. print(shutil.get_terminal_size((100, 30)).columns >= 1)
5. print("zip" in [n for n, _ in shutil.get_archive_formats()])
6. print("zstdtar" in [n for n, _ in shutil.get_archive_formats()])
7. print(callable(shutil.ignore_patterns("*.pyc")))
8. print(shutil.ignore_patterns("*.pyc")(".", ["a.py", "b.pyc", "c.pyc"]))
9. print(hasattr(shutil, "copy2"))
10. print(shutil.disk_usage(".").free <= shutil.disk_usage(".").total)
11. print(issubclass(FileExistsError, OSError))
12. print(sorted(shutil.ignore_patterns("__pycache__", "*.log")(".", ["__pycache__", "x.log", "y.txt"])))Javoblar
TrueNoneusageTrueTrueTrue— 3.14True{'b.pyc', 'c.pyc'}TrueTrueTrue['__pycache__', 'x.log']
Vazifa 2: Xatolarni tuzating
1. def tozala(eski_nom):
shutil.rmtree(os.path.join("/srv/zaxiralar", eski_nom))
2. def sinxronla(manba, maqsad):
shutil.copytree(manba, maqsad) # har kuni ishga tushadi
3. def arxivla(katalog):
return shutil.make_archive("/srv/zaxira/oxirgi", "zip", katalog)
4. def ochish(yuklangan_zip):
shutil.unpack_archive(yuklangan_zip, "/srv/app/plugins")
5. def video_siq(fayl):
subprocess.run([shutil.which("ffmpeg"), "-i", fayl, "chiqish.mp4"])Javoblar
1. ILDIZ = Path("/srv/zaxiralar")
def tozala(eski_nom):
if not eski_nom:
raise ValueError("nom bo'sh")
xavfsiz_rmtree(ILDIZ / eski_nom, ILDIZ, "zaxira-")
2. def sinxronla(manba, maqsad):
shutil.copytree(manba, maqsad, dirs_exist_ok=True,
ignore=shutil.ignore_patterns(".git", "__pycache__"))
3. def arxivla(katalog):
with tempfile.TemporaryDirectory() as ish:
vaqtinchalik = shutil.make_archive(os.path.join(ish, "oxirgi"), "zip", katalog)
# ... ochib tekshirish
shutil.copy2(vaqtinchalik, "/srv/zaxira/oxirgi.zip.tmp")
os.replace("/srv/zaxira/oxirgi.zip.tmp", "/srv/zaxira/oxirgi.zip")
4. def ochish(yuklangan_zip):
with zipfile.ZipFile(yuklangan_zip) as z:
if sum(i.file_size for i in z.infolist()) > 50_000_000 or len(z.infolist()) > 1000:
raise ValueError("arxiv juda katta")
with tempfile.TemporaryDirectory() as ish:
shutil.unpack_archive(yuklangan_zip, ish)
# ... fayllarni tekshirib, kerakligini /srv/app/plugins ga ko'chirish
5. def video_siq(fayl):
ffmpeg = shutil.which("ffmpeg")
if ffmpeg is None:
raise RuntimeError("ffmpeg o'rnatilmagan")
subprocess.run([ffmpeg, "-i", fayl, "chiqish.mp4"], check=True)Vazifa 3: Katalog sinxronizatori
sinxronla(manba, maqsad, oyna=False):
- Yangi va o'zgargan fayllarni (
st_mtimeva hajm)copy2bilan nusxalang --ochirbilan manbada yo'q fayllarni maqsaddan o'chiring (himoya bilan)ignorenaqshlari fayldan (.sinxignore) o'qilsin- Quruq rejimda reja chiqarilsin
- Ikkinchi ishga tushirishda hech narsa nusxalanmasligini tekshiring
Vazifa 4: Xavfsiz arxiv ochuvchi
xavfsiz_och(arxiv, maqsad, maks_hajm, maks_fayllar, maks_nisbat):
zipvatarni qo'llasin- Ochishdan oldin: umumiy hajm, fayllar soni, siqish nisbati, absolyut va
..yo'llar - Har rad etish sababini
stderrga yozsin - Symlink va qurilma fayllarini rad etsin
- Zip slip, zip bomb va oddiy arxiv bilan testlang
Vazifa 5: Disk hajmi hisoboti
- Berilgan katalog uchun eng katta 10 ta ichki katalog (rekursiv hajm)
disk_usagebilan diskning umumiy holati va foizi- Terminal kengligiga moslashgan gistogramma (
get_terminal_size) --min-hajm=100Mkabi birliklarni tahlil qilish
Vazifa 6: Joylashtirish (deploy) skripti
- Yangi versiyani
releases/2026-09-17-1400/ga nusxalang (copytree,ignore) currentsymlink ini atomar almashtiring (vaqtinchalik symlink +os.replace)- Oxirgi 5 ta relizni saqlang
- Qaytarish (rollback) buyrug'i
- Windows'da symlink ruxsati bo'lmasa — aniq xabar
Vazifa 7: O'ylash
rm -rf / — Unix tarixidagi eng mashhur halokatli buyruqlardan biri. Zamonaviy GNU rm endi --preserve-root ni sukut bo'yicha qo'llaydi. Python'ning shutil.rmtree esa bunday himoyaga ega emas. Nega standart kutubxona "xavfli" funksiyalarni himoyasiz qoldiradi va himoya qayerda bo'lishi kerak: kutubxonadami, ilovadami yoki operatsion tizimdami?
Javob
Qisqa javob: Kutubxona funksiyasi ilovaning kontekstini bilmaydi: /tmp/build ni o'chirish bir dasturda odatiy, boshqasida halokatli. Umumiy himoya (/ ni rad etish) faqat eng aniq holatni qamraydi, qolganlarida esa soxta xavfsizlik hissini beradi. Shuning uchun himoya qatlamli bo'lishi kerak: ilova — kontekstli tekshiruvlar, operatsion tizim — ruxsatlar va izolyatsiya, jarayon — minimal huquqlar va zaxiralar.
1. --preserve-root tarixi
- Klassik
rm -rf /tizimni o'chirardi - GNU coreutils 2006-yildan
--preserve-rootni sukut qildi - Lekin
rm -rf /*,rm -rf "$BOSH/"(bo'sh o'zgaruvchi) — baribir xavfli - Mashhur hodisa: Steam for Linux o'rnatish skriptida
rm -rf "$STEAMROOT/"*— o'zgaruvchi bo'sh bo'lganda foydalanuvchi fayllari o'chgan
2. Nega kutubxona himoyasiz
| Sabab | Tafsilot |
|---|---|
| Kontekst yo'q | Qaysi katalog "muhim" — faqat ilova biladi |
| Umumiy qoida yetarli emas | / ni bloklash /home/aziz yoki /srv/data ni himoya qilmaydi |
| Soxta xavfsizlik | "Kutubxona himoya qiladi" degan ishonch — tekshiruvlarni kamaytiradi |
| Qonuniy foydalanish | Konteyner ichida /app ni tozalash — odatiy |
| Oddiylik | Standart kutubxona funksiyalari bitta vazifani bajaradi |
3. Qatlamli himoya
| Qatlam | Nima qiladi | Misol |
|---|---|---|
| Ilova | Kontekstli tekshiruvlar | xavfsiz_rmtree: ruxsat ildizi, nom prefiksi, bo'sh nom |
| Kiritish | Yo'lni tekshirish va tozalash | resolve(), is_relative_to() (15.12) |
| Jarayon | Minimal huquqlar | Zaxira skripti faqat zaxira katalogiga yozish huquqiga ega foydalanuvchi |
| Operatsion tizim | Ruxsatlar, faqat o'qish uchun o'rnatish | Konteyner fayl tizimi read-only, chattr +i |
| Ma'lumot | Zaxiralar, snapshotlar | O'chirilgan narsani qaytarish imkoni |
| Jarayon (odamlar) | Quruq ishga tushirish, tasdiqlash | --dry-run, "o'chirilsinmi?" |
4. Muhandislik tamoyillari
- Xavfli amal — tor interfeys orqali: to'g'ridan-to'g'ri
rmtreeemas,zaxirani_ochir(sana) - Sukut — xavfsiz:
--ochirbayrog'isiz faqat reja - Chuqur himoya: bitta tekshiruvga ishonmaslik
- Qaytariluvchanlik: o'chirish o'rniga "savat"ga ko'chirish, keyin muddat bilan tozalash
- Kuzatuv: har o'chirishni jurnalga yozish
5. Xulosa
- Standart kutubxona mexanizm beradi, siyosatni ilova belgilaydi
- Kutubxona darajasidagi umumiy himoya faqat eng aniq holatni qamraydi
- To'g'ri himoya — qatlamli: ilova, huquqlar, OS, zaxiralar
- Halokatli amallar — tor, tekshirilgan va jurnalga yoziladigan funksiyalar orqali
Nimani mustahkamlaydi: 2.1–2.7-bo'limlar.
Xulosa
Bu darsda fayl tizimidagi yuqori darajadagi amallarni va ularni xavfsiz bajarishni o'rgandik.
Eng muhim uch fikr:
Nusxalash darajalari muhim.
copyfilefaqat mazmunni,copyruxsatlarni ham,copy2vaqtlar va metama'lumotni ham nusxalaydi — zaxira va sinxronlash uchuncopy2.copytreedoimignorebilan (__pycache__,.git, loglar) va mavjud kataloggadirs_exist_ok=Truebilan ishlatiladi;movebir diskda tez nomlash, turli disklarda esa nusxa va o'chirish.rmtree— himoya funksiyasi orqali. Bo'sh nom bilanjoinildizning o'zini beradi — va butun katalog o'chadi. Himoya:resolve(), ruxsat ildizi ichida (is_relative_to), ildiz yoki uy katalogi emas, kutilgan nom prefiksi. Windows'da faqat o'qiladigan fayllaronexc3.12-bob bilan hal qilinadi;ignore_errors=Trueesa yarim natijani jim qoldiradi.Arxivlar: yaratish ishonchli, ochish ehtiyotkor.
make_archivedaroot_dirvabase_dirarxiv ichidagi yo'llarni belgilaydi; 3.14 dazstdtarformati qo'shildi. Ishonchli zaxira — joy tekshiruvi, vaqtinchalik nom, ochib xesh bilan solishtirish vaos.replace. Ishonchsiz arxivda zip slip nomlariunpack_archiveda jim o'tkazib yuboriladi, tar da esa 3.14 ningdatafiltriOutsideDestinationErrorberadi; hajm portlashidan esa hech biri himoya qilmaydi — ochishdan oldininfolist()bilan tekshiring.
Keyingi darsda tashqi dasturlarni ishga tushirishni o'rganamiz: subprocess — run, chiqish kodlari, oqimlarni ushlash, muddatlar va shell injection xavfi.
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!