Windows ve Linux otomasyon farkları: aynı betiği iki tarafta da çalıştırmak
PC'de çalışıp sunucuda patlayan betiklerin sebepleri: yol ayracı, CRLF/LF, kod sayfası, dosya kilidi, Task Scheduler ile cron farkları ve taşınabilir çözümler.
Windows ve Linux otomasyon farkları, masamda en sık karşılaştığım arıza sınıfının adı. Senaryo hep aynı: betik PC'de kusursuz çalışıyor, sunucuya taşınıyor ve ya hiç başlamıyor ya da daha kötüsü, hata vermeden yanlış iş yapıyor. Aradaki fark nadiren "Linux zor" ya da "Windows garip" diye özetlenebilecek bir şey; neredeyse her seferinde beş altı somut mekanizmadan birine iniyor.
Bu yazıda o mekanizmaları tek tek açıyorum: yol ayracı ve sürücü harfi, satır sonu karakterleri, varsayılan kod sayfası, dosya kilitleme, planlayıcıların çalışma dizini ve ortam değişkeni davranışı. Her birinin yanına iki tarafta da aynı şekilde çalışan bir çözüm koyuyorum. Amacım "dikkat edin" demek değil, kopyalayıp kullanabileceğiniz bir taşınabilir dosya işleyicisi ve bir planlayıcı yapılandırma çifti bırakmak.
Örnekler Python üzerinden gidiyor çünkü iki platformda da aynı yorumlayıcı var; ama satır sonu ve planlayıcı bölümleri kabuk betikleri için de birebir geçerli. PowerShell ile sistem izleme betiği yazısındaki yaklaşımı Linux'a taşırken aynı tuzakların çoğuna ben de düşmüştüm; bu liste o deneyimden çıktı.
Yol ayracı, sürücü harfi ve büyük-küçük harf
En bilinen fark en az sorun çıkaranı: ters bölü ile düz bölü. Python'da elle yazılmış bir veri\rapor.csv yolu Windows'ta çalışır, Linux'ta ise adının içinde ters bölü olan tek bir dosya arar ve bulamaz. Asıl sinsi olanlar şunlar:
- Sürücü harfi:
E:\verigibi bir kök Linux'ta karşılıksızdır. Betiğin içine mutlak yol yazdığınız anda taşınabilirliği kaybedersiniz; kökü yapılandırma dosyasından ya da betiğin kendi konumundan türetin. - Büyük-küçük harf: Windows dosya sistemi varsayılan olarak harfe duyarsızdır, ext4 duyarlıdır. PC'de
Rapor.CSVdiye açılan dosya sunucudarapor.csvolarak duruyorsa "böyle bir dosya yok" hatası alırsınız. Bu hata veri dosyalarından çok kodun içindeki modül ve şablon adlarında çıkar. - Ev dizini ve geçici dizin:
%USERPROFILE%ile$HOMEfarklı değişkenlerdir;Path.home()vetempfile.gettempdir()ikisini de doğru çözer.
Çözüm pathlib: bölü operatörüyle yol kurar, ayracı platforma göre seçer ve resolve() ile sürücü harfini de içeren mutlak yolu verir. Kuralım basit: betiğin içinde tek bir ters bölü bile geçmez, tüm yollar Path(__file__).resolve().parent gibi tek bir kökten türetilir.
CRLF, LF ve varsayılan kod sayfası
Satır sonu farkı iki ayrı yerde patlar. Birincisi betiğin kendisi: Windows'ta kaydedilmiş bir .sh dosyasının ilk satırı #!/bin/bash\r olarak biter ve Linux çekirdeği bash\r adında bir yorumlayıcı arar. Hata mesajı "bad interpreter" der ama dosyada görünür hiçbir şey yoktur. İkincisi veri: CRLF'li bir CSV'yi Linux'ta satır satır okuyup yalnızca \n karakterini kırparsanız her alanın sonunda görünmez bir \r kalır; karşılaştırmalar sessizce başarısız olur, anahtarlar eşleşmez, sayılar dönüştürülemez.
Kod sayfası daha da sessizdir. Linux'ta Python'un varsayılan metin kodlaması pratikte UTF-8'dir; Windows'ta ise sistem yerel ayarından gelir ve Türkçe kurulumda bu cp1254'tür. open() çağrısına encoding vermediğinizde iki taraf aynı dosyayı farklı okur: PC'de doğru görünen "ş" ve "ğ" sunucuda bozuk çıkar ya da tam tersi. Python 3.10 ve üzeri -X warn_default_encoding bayrağıyla bu çağrıları tek tek uyarır; PYTHONUTF8=1 ortam değişkeni de Windows'u UTF-8'e zorlar. PEP 686 bu varsayılanı UTF-8'e çeviriyor ama sahadaki sürümlerde yerel ayar hâlâ geçerli; en sağlam yol her open() çağrısında kodlamayı açıkça yazmaktır.
Aşağıdaki işleyici iki sorunu birlikte kapatıyor: yolları pathlib ile kurar, her zaman UTF-8 ile okur, satır sonlarını okurken normalize eder, yazarken LF'e sabitler ve çıktıyı önce geçici dosyaya yazıp sonra yerine koyar.
from pathlib import Path
import os, tempfile
KOK = Path(__file__).resolve().parent
GIRDI = KOK / "veri" / "gelen.csv"
CIKTI = KOK / "veri" / "temiz.csv"
def satirlari_oku(yol: Path):
# newline=None: \r\n, \r ve \n hepsi \n olarak gelir
with yol.open("r", encoding="utf-8", newline=None) as f:
for satir in f:
yield satir.rstrip("\n")
def guvenli_yaz(yol: Path, satirlar):
yol.parent.mkdir(parents=True, exist_ok=True)
fd, gecici = tempfile.mkstemp(dir=yol.parent, suffix=".tmp")
with os.fdopen(fd, "w", encoding="utf-8", newline="\n") as f:
for s in satirlar:
f.write(s + "\n")
os.replace(gecici, yol) # ayni diskte atomik degistirme
if __name__ == "__main__":
temiz = (s for s in satirlari_oku(GIRDI) if s.strip())
guvenli_yaz(CIKTI, temiz)
print("yazildi:", CIKTI)
newline="\n" parametresi olmadan Python, Windows'ta yazarken her \n karakterini \r\n olarak çevirir. Dosyayı PC'de üretip sunucuda okuyan bir zincirde bu tek parametre CRLF sorununun kaynağını kurutur.
Dosya kilitleme: aynı kod, iki farklı sonuç
Yukarıdaki os.replace satırı iki platformda da çalışır ama davranışı aynı değildir. Linux'ta açık bir dosyanın üzerine yeniden adlandırma yapabilirsiniz; eski dosyayı okuyan süreç eski içeriği görmeye devam eder, yeni açanlar yeni içeriği alır. Windows'ta ise başka bir süreç dosyayı açık tutuyorsa yeniden adlandırma PermissionError ile reddedilir. Bu "başka süreç" çoğu zaman Excel'de açık bırakılmış bir CSV, bir antivirüs taraması ya da dosyayı henüz kapatmamış önceki çalıştırmadır.
PC'de bunu görmezsiniz çünkü kendi masanızda dosyayı kimse açmaz; Linux sunucuda da görmezsiniz çünkü çekirdek bu şekilde kilitlemez. Ama hedef bir Windows sunucusuysa, ki sahada hâlâ yaygındır, ilk gece WinError 32 ile karşılaşırsınız. Yaklaşımım:
- Yazma işlemini her zaman geçici dosya artı
os.replacedeseniyle yapmak; yarım yazılmış dosya asla gerçek adıyla ortada durmaz. - Windows'ta
PermissionErroriçin kısa aralıklarla birkaç kez yeniden denemek; antivirüs kilitleri genellikle saniyeler içinde açılır. - Yeniden denemeler de tükenirse hatayı yutmamak: günlük dosyasına yazıp sıfırdan farklı çıkış koduyla bitirmek. Aksi hâlde planlayıcı "başarılı" der ve siz sabaha kadar eksik dosyayla yaşarsınız.
Task Scheduler ile cron: çalışma dizini ve ortam
Betik elle çalıştırınca sorunsuz, planlayıcıdan çalışınca patlıyorsa sebep neredeyse her zaman iki şeyden biridir: çalışma dizini ya da ortam değişkenleri. cron işleri kullanıcının ev dizininde ve çok kısıtlı bir ortamla başlar; PATH çoğu dağıtımda yalnızca /usr/bin:/bin içerir, .bashrc okunmaz, dolayısıyla terminalde tanımladığınız hiçbir takma ad ve değişken orada yoktur. Task Scheduler'da ise "Başlangıç yeri" boş bırakılırsa görev System32 içinde başlar ve veri/gelen.csv gibi göreli yollar orada aranır.
Bu yüzden betiğe koyduğum ilk satır çalışma dizinini betiğin konumuna sabitler; yukarıdaki KOK değişkeni tam olarak bunu yapar. İkinci kural, betiğin ihtiyaç duyduğu her ortam değişkenini planlayıcı tanımında açıkça vermek. Aynı işi iki planlayıcıda tanımlayan yapılandırma çifti şöyle:
# Linux — crontab -e (her gece 02:15)
# PATH ve PYTHONUTF8 acikca verilir; cd ile calisma dizini sabitlenir
PATH=/usr/local/bin:/usr/bin:/bin
PYTHONUTF8=1
15 2 * * * cd /opt/rapor && /usr/bin/python3 isle.py >> /var/log/rapor.log 2>&1
# Windows — ayni gorev, PowerShell ile kayit
$eylem = New-ScheduledTaskAction -Execute "C:\Python312\python.exe" `
-Argument "-X utf8 isle.py" -WorkingDirectory "E:\rapor"
$tetik = New-ScheduledTaskTrigger -Daily -At "02:15"
Register-ScheduledTask -TaskName "rapor-isle" -Action $eylem -Trigger $tetik
İki tanımın ortak noktaları: yorumlayıcı tam yoluyla yazılır, çalışma dizini açıkça verilir, UTF-8 zorlanır. Windows tarafında görev başına ortam değişkeni tanımlamak cron kadar rahat değildir; bu yüzden -X utf8 gibi ayarları argüman olarak geçmek ya da betiğin başında os.environ.setdefault ile varsayılan vermek daha güvenilirdir. Çıktının bir günlük dosyasına yönlendirilmesi de şart: planlayıcıda çalışan betiğin ekranı yoktur, yazdığı her şey kaybolur.
"Çıplak bash" tuzağı ve sessiz bozulmayı yakalamak
Windows'ta Git for Windows kuruluysa bir bash komutu vardır; WSL etkinse C:\Windows\System32\bash.exe de vardır ve hangisinin çalışacağı PATH sırasına göre makineden makineye değişir. Bir planlayıcı görevine bash senkron.sh yazdığınızda betik Git Bash yerine WSL'de açılabilir; orada E:\ yerine /mnt/e/ vardır, Python farklı bir kurulumdur ve SSH anahtarlarınız başka bir ev dizinindedir. Betik "çalışır", çıkış kodu sıfırdır, ama yanlış dünyada çalışmıştır. Kural: Windows'ta kabuk yorumlayıcısını hiçbir zaman çıplak adıyla değil, C:\Program Files\Git\bin\bash.exe gibi tam yoluyla çağırın.
Satır sonu bozulması da aynı sınıfa girer: hata vermez, yanlış sonuç üretir. Bunu betiğe güvenerek değil, denetleyerek yakalamanız gerekir. Kullandığım kontroller:
file betik.shçıktısında "with CRLF line terminators" ifadesini aramak;grep -c $'\r' betik.shsıfırdan büyük bir sayı veriyorsa dosya kirlidir.- Depoya
.gitattributeskoymak:*.sh text eol=lfve*.py text eol=lfsatırları, dosya hangi makinede kaydedilirse kaydedilsin çalışma kopyasını LF ile teslim eder.core.autocrlfayarına güvenmeyin; makineden makineye değişir. - Veri dosyasının ilk birkaç yüz baytını
yol.read_bytes()[:200]ile ham hâlde yazdırmak; çıktıda\r\ngörüyorsanız dosya CRLF'lidir ve okuma katmanının bunu normalize ettiğinden emin olmanız gerekir. - Betiği sunucuya taşıdıktan sonra PC ile sunucudaki kopyanın özet değerini karşılaştırmak; satır sonu dönüşümü yapan bir aktarım aracı iki kopyanın özetini sessizce ayrıştırır.
Bu denetimleri SQLite ile küçük ama sağlam veri boru hatları yazısındaki gibi bir işleme zincirinin en başına koymak, sonradan saatlerce "veri neden eşleşmiyor" diye aramaktan çok daha ucuzdur.
Taşınabilir betik, "her yerde çalışır" iddiasıyla yazılan değil, iki platformun farklı davrandığı noktaları tek tek bilerek yazılan betiktir. Kontrol listesi kısa: yollar pathlib ile tek kökten türetilir, her open() çağrısı kodlamayı ve satır sonunu açıkça söyler, yazma işlemi geçici dosya üzerinden gider, planlayıcı tanımı çalışma dizinini ve ortamı kendisi verir, kabuk yorumlayıcısı tam yoluyla çağrılır. Bu beş kuralı uygulayan bir betiği PC'den sunucuya taşımak artık bir "umarım çalışır" anı değil, sıradan bir kopyalama işidir.
Elinizde PC'de çalışıp sunucuda tökezleyen bir otomasyon varsa, danışmanlık hizmetlerim tam olarak bu tür taşıma ve sağlamlaştırma işlerini kapsıyor; iletişim bölümünden yazın, betiği birlikte iki tarafta da çalışır hâle getirelim.