İçeriğe geç
Educora
İleri20 dk28 / 42

Otomasyon betikleri

Rutin işleri Python'a devret: dosyaları `pathlib` ve `shutil` ile düzenle, eski dosyaları `datetime` ile bul, `argparse` ile komut satırı arayüzü oluştur, `logging` ile kayıt tut ve betiği cron ya da Görev Zamanlayıcı ile zamanında çalıştır.

Kendini test et
Bu derste öğreneceklerin
  • pathlib, os ve shutil ile dosya ve klasörleri güvenle oluşturmak, taşımak ve kopyalamak
  • datetime ve timedelta ile tarihleri biçimlendirmek, karşılaştırmak ve dosyaların yaşını hesaplamak
  • argparse ve logging ile 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örevKod
klasör içeriği / alt klasörlerle birliktePath.iterdir(), Path.rglob('*.pdf'), os.walk()
klasör oluşturmakPath.mkdir(parents=True, exist_ok=True)
dosya / klasör kopyalamakshutil.copy2(src, dst), shutil.copytree(src, dst)
taşımak ya da yeniden adlandırmakshutil.move(src, dst), Path.rename(new)
dosya / tüm klasörü silmekPath.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.

Python
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.

Python
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:

Python
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.

Python
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.

Python
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
Burada kayıt ekrana (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ı

Python
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 ifadesiNe zaman
0 21 * * *her gün saat 21.00'de
*/15 * * * *her 15 dakikada bir
0 9 * * 1-5hafta içi saat 09.00'da
0 3 1 * *her ayın 1'inde saat 03.00'te
Terminal
# 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.
Alıştırma

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.

Alıştırma · Python
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']
Alıştırma

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.

Alıştırma · Python
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

  • pathlib yollar ve dosyalar içindir, shutil kopyalama, taşıma ve klasör silme içindir; silinen dosyalar geri gelmez.
  • datetime, timedelta, strftime ve strptime; dosya adlarında tarihi YYYY-MM-DD biçiminde yaz.
  • argparse konumsal argümanlar, seçenekler, bayraklar ve otomatik bir --help sağlar.
  • Zamanlanmış betiklerde print yerine düzeyleri ve zamanı olan logging kullan.
  • cron ya da Görev Zamanlayıcı betiği düzenli çalıştırır; mutlak yollar, tam python yolu ve --dry-run alışkanlığı gerekir.

Kendini test et

10 soru. Her doğru cevap XP kazandırır.

1 / 10
Hangi fonksiyon bir klasörü içindekilerle birlikte kalıcı olarak siler?