pathlib,osveshutilile dosya ve klasörleri güvenle oluşturmak, taşımak ve kopyalamakdatetimevetimedeltaile tarihleri biçimlendirmek, karşılaştırmak ve dosyaların yaşını hesaplamakargparseveloggingile profesyonel bir komut satırı betiği yazmak ve onu zamanlanmış olarak çalıştırmak
Leyla'nın İndirilenler klasöründe yüzlerce dosya karmakarışık duruyor ve o her akşam belgelerini elle yedekleme diskine kopyalıyor. Bu, her gün birkaç dakika ve bazen unutulan bir yedek demek. 25 satırlık bir Python betiği dosyaları saniyeler içinde klasörlere ayırır, sistem zamanlayıcısı da onu her gün kendiliğinden çalıştırır. Bu derste böyle bir betiğin tüm parçalarını kuracağız.
Dosyalar ve klasörler: pathlib, os, shutil
| Görev | Kod |
|---|---|
| klasör içeriği / alt klasörlerle birlikte | Path.iterdir(), Path.rglob('*.pdf'), os.walk() |
| klasör oluşturmak | Path.mkdir(parents=True, exist_ok=True) |
| dosya / klasör kopyalamak | shutil.copy2(src, dst), shutil.copytree(src, dst) |
| taşımak ya da yeniden adlandırmak | shutil.move(src, dst), Path.rename(new) |
| dosya / tüm klasörü silmek | Path.unlink(), shutil.rmtree(path) |
Aşağıdaki betik dosyaları uzantılarına göre klasörlere ayırır. Gerçek dosyalarına dokunmamak için tempfile.mkdtemp() ile geçici bir klasör oluşturup sonunda siliyoruz. Dikkat et: uzantıyı lower() ile küçük harfe çevirdiğimiz için .JPG ve .jpg aynı sayılır.
import shutil
import tempfile
from pathlib import Path
root = Path(tempfile.mkdtemp())
for name in ['report.pdf', 'photo1.jpg', 'photo2.JPG', 'notes.txt', 'data.csv', 'song.mp3']:
(root / name).write_text('demo')
folders = {'.pdf': 'documents', '.txt': 'documents', '.jpg': 'images', '.csv': 'data'}
for file in list(root.iterdir()):
if file.is_file():
target = root / folders.get(file.suffix.lower(), 'other')
target.mkdir(exist_ok=True)
shutil.move(file, target / file.name)
for path in sorted(root.rglob('*')):
print(path.relative_to(root).as_posix())
shutil.rmtree(root)
print(root.exists())▸ Beklenen çıktı
data data/data.csv documents documents/notes.txt documents/report.pdf images images/photo1.jpg images/photo2.JPG other other/song.mp3 False
list(root.iterdir()) listeyi önceden alır; böylece döngü sırasında klasörü değiştirmek güvenli olur. Tanınmayan uzantılar other klasörüne gider.Tarih ve saat
datetime modülünde date, datetime ve süreleri ifade eden timedelta vardır. strftime bir tarihi biçimli metne çevirir (%Y yıl, %m ay, %d gün, %H:%M saat), strptime ise tersine metni tarihe çevirir. Sonuçların tekrarlanabilir olması için örneklerde datetime.now() yerine sabit bir tarih kullanıyoruz.
from datetime import date, datetime, timedelta
now = datetime(2026, 9, 27, 14, 30)
print(now.strftime('%Y-%m-%d_%H-%M'))
print(now.strftime('%d.%m.%Y'))
deadline = now + timedelta(days=10, hours=3)
print(deadline.isoformat(sep=' '))
print((date(2026, 12, 31) - now.date()).days, 'days left in the year')
parsed = datetime.strptime('05.10.2026 09:15', '%d.%m.%Y %H:%M')
print(parsed, parsed.strftime('%A'))▸ Beklenen çıktı
2026-09-27_14-30 27.09.2026 2026-10-07 17:30:00 95 days left in the year 2026-10-05 09:15:00 Monday
Bir dosyanın son değiştirilme zamanını stat().st_mtime verir (1970'ten bu yana geçen saniyeler). Onu datetime.fromtimestamp ile tarihe çevirip bir sınırla karşılaştırırız. Örnekte dosyaların “yaşını” os.utime ile yapay olarak belirliyoruz:
import os
import tempfile
from datetime import datetime, timedelta
from pathlib import Path
root = Path(tempfile.mkdtemp())
now = datetime(2026, 9, 27, 12, 0)
for name, days in {'backup_old.zip': 45, 'log_week.txt': 7, 'log_today.txt': 0}.items():
f = root / name
f.write_text('x')
t = (now - timedelta(days=days)).timestamp()
os.utime(f, (t, t))
limit = now - timedelta(days=30)
for f in sorted(root.iterdir()):
modified = datetime.fromtimestamp(f.stat().st_mtime)
status = 'OLD' if modified < limit else 'ok'
print(f'{f.name:15} {modified:%Y-%m-%d} {status}')▸ Beklenen çıktı
backup_old.zip 2026-08-13 OLD log_today.txt 2026-09-27 ok log_week.txt 2026-09-20 ok
argparse ve logging
İyi bir betik yolları ve sayıları kodun içine yazmaz; onları komut satırından alır: python cleanup.py downloads --days 14 --dry-run. argparse konumsal argümanları (folder), seçenekleri (--days) ve bayrakları (--dry-run) ayrıştırır, türleri dönüştürür ve --help sayfasını kendisi oluşturur. Gerçek bir betikte parse_args() argümansız çağrılır; burada komut satırını bir listeyle taklit ediyoruz.
import argparse
parser = argparse.ArgumentParser(prog='cleanup', description='List or delete old files.')
parser.add_argument('folder', help='folder to scan')
parser.add_argument('--days', type=int, default=30, help='age limit in days')
parser.add_argument('--dry-run', action='store_true', help='only show what would happen')
args = parser.parse_args(['downloads', '--days', '14', '--dry-run'])
print(args)
print(args.folder, args.days, args.dry_run)
print(parser.parse_args(['photos']))▸ Beklenen çıktı
Namespace(folder='downloads', days=14, dry_run=True) downloads 14 True Namespace(folder='photos', days=30, dry_run=False)
--dry-run, dry_run özniteliğine dönüşür (kısa çizgi alt çizgiye çevrilir). İkinci çağrıda verilmeyen seçenekler varsayılan değerlerini aldı.Zamanlanmış bir betiğin ekranına kimse bakmaz; bu yüzden print yerine logging kullanılır: her iletinin bir düzeyi (DEBUG, INFO, WARNING, ERROR, CRITICAL) ve zamanı olur, iletiler bir dosyaya yazılabilir ve önemsizler süzülebilir. %s ve %d yer tutucuları iletiyi yalnızca gerçekten yazdırılacağı zaman biçimlendirir.
import logging
import sys
logging.basicConfig(stream=sys.stdout, level=logging.INFO,
format='%(levelname)-8s %(name)s: %(message)s', force=True)
log = logging.getLogger('backup')
log.debug('not shown: the level is INFO')
log.info('Backup started')
log.warning('Disk is 85% full')
try:
ratio = 10 / 0
except ZeroDivisionError:
log.error('Could not compute the ratio')
log.info('Copied %d files in %.1f s', 128, 3.42)▸ Beklenen çıktı
INFO backup: Backup started WARNING backup: Disk is 85% full ERROR backup: Could not compute the ratio INFO backup: Copied 128 files in 3.4 s
sys.stdout) yazılıyor; gerçek bir betikte filename='backup.log' verilir ve biçime %(asctime)s (zaman) eklenir. DEBUG düzeyindeki ileti INFO sınırının altında olduğu için görünmez.Tam bir betik ve zamanlanması
import argparse
import logging
import shutil
from datetime import datetime
from pathlib import Path
log = logging.getLogger('backup')
def backup(source, dest_root, dry_run=False):
stamp = datetime.now().strftime('%Y-%m-%d_%H-%M')
target = Path(dest_root) / f'{Path(source).name}_{stamp}'
log.info('Copying %s -> %s (dry run: %s)', source, target, dry_run)
if not dry_run:
shutil.copytree(source, target)
return target
if __name__ == '__main__':
parser = argparse.ArgumentParser(description='Back up a folder with a timestamp.')
parser.add_argument('source', help='folder to copy')
parser.add_argument('dest', help='where to put the copy')
parser.add_argument('--dry-run', action='store_true', help='only log, do not copy')
args = parser.parse_args()
logging.basicConfig(filename='backup.log', level=logging.INFO,
format='%(asctime)s %(levelname)s %(message)s')
backup(args.source, args.dest, args.dry_run)backup.py. if __name__ == '__main__': bloğu yalnızca dosya doğrudan çalıştırıldığında yürütülür; backup fonksiyonu başka betiklere ve testlere aktarılabilir. Kendi bilgisayarında: python backup.py Documents D:/Backup --dry-run, ardından backup.log dosyasına bak.Bir betiği düzenli olarak çalıştırmak için işletim sisteminin zamanlayıcısı kullanılır: Linux ve macOS'ta cron, Windows'ta Görev Zamanlayıcı (Task Scheduler, komut satırında schtasks). Bir cron satırında beş alan vardır: dakika, saat, ayın günü, ay ve haftanın günü; * “herhangi” demektir.
| cron ifadesi | Ne zaman |
|---|---|
0 21 * * * | her gün saat 21.00'de |
*/15 * * * * | her 15 dakikada bir |
0 9 * * 1-5 | hafta içi saat 09.00'da |
0 3 1 * * | her ayın 1'inde saat 03.00'te |
# Linux / macOS: open the table with `crontab -e` and add a line
0 21 * * * /home/leyla/.venv/bin/python /home/leyla/scripts/backup.py /home/leyla/Documents /mnt/backup
# Windows (Command Prompt): the same task every day at 21:00
schtasks /Create /SC DAILY /ST 21:00 /TN "Daily backup" /TR "C:\scripts\.venv\Scripts\python.exe C:\scripts\backup.py C:\Users\Leyla\Documents D:\Backup"schedule adlı bir Python kütüphanesi de vardır (pip install schedule), ama onunla betiğin sürekli açık kalması gerekir; sistem zamanlayıcısı ise bilgisayar yeniden başladıktan sonra da çalışmaya devam eder.Dosya adlarını uzantıya göre grupla: uzantıyı küçük harfle al, uzantısı olmayan dosyalar için '(none)' kullan. Uzantıları sıralı biçimde, her birini kendi sıralı dosya listesiyle birlikte ayrı satırlarda yazdır.
from pathlib import PurePath
files = ['report.PDF', 'photo.jpg', 'notes.txt', 'scan.pdf', 'song.mp3', 'README']
# group by lowercase extension, then print: extension [sorted names]▸ Beklenen çıktı
(none) ['README'] .jpg ['photo.jpg'] .mp3 ['song.mp3'] .pdf ['report.PDF', 'scan.pdf'] .txt ['notes.txt']
Bugün 2026-09-27. 30 günden eski her dosya için adını ve kaç gün önce değiştirildiğini yazdır (sözlükteki sırayla). Sonunda en yeni dosyayı newest: <ad> biçiminde yazdır.
from datetime import date
today = date(2026, 9, 27)
files = {'backup.zip': '2026-07-30', 'notes.txt': '2026-09-20',
'photo.jpg': '2026-08-15', 'log.txt': '2026-09-27'}
# print old files with their age in days, then the newest file▸ Beklenen çıktı
backup.zip 59 photo.jpg 43 newest: log.txt
Önemli noktalar
pathlibyollar ve dosyalar içindir,shutilkopyalama, taşıma ve klasör silme içindir; silinen dosyalar geri gelmez.datetime,timedelta,strftimevestrptime; dosya adlarında tarihiYYYY-MM-DDbiçiminde yaz.argparsekonumsal argümanlar, seçenekler, bayraklar ve otomatik bir--helpsağlar.- Zamanlanmış betiklerde
printyerine düzeyleri ve zamanı olanloggingkullan. - cron ya da Görev Zamanlayıcı betiği düzenli çalıştırır; mutlak yollar, tam
pythonyolu ve--dry-runalışkanlığı gerekir.
Kendini test et
10 soru. Her doğru cevap XP kazandırır.