Dosyalar
Ne kadar güçlü olsalar da, önceki bölümde uç birimlerde kullanıldıklarını gördüğümüz >
, <
, ve |
karakterleri her duruma uygun değildir. Çünkü her program işini yalnızca standart giriş ve çıkışla etkileşerek yapamaz.
Örneğin öğrenci kayıtları ile ilgilenen bir program, standart çıkışını kullanıcıya bir komut menüsü göstermek için kullanıyor olabilir. Standart girişini de kullanıcıdan komut almak için kullandığını düşünürsek, böyle bir programın kayıtlarını tuttuğu öğrenci bilgilerini yazmak için en az bir dosyaya ihtiyacı olacaktır.
Bu bölümde dosya sisteminin klasörlerde barındırdığı dosyalara yazmayı ve dosyalardan okumayı öğreneceğiz.
Temel kavramlar
Dosya işlemleri için std.stdio
modülünde tanımlanmış olan File
yapısı kullanılır. Henüz yapıları göstermediğim için File
nesnelerinin kurulma söz diziminin ayrıntısına girmeyeceğim ve şimdilik bir kalıp olarak kabul etmenizi bekleyeceğim.
Kullanımlarına geçmeden önce dosyalarla ilgili temel kavramların açıklanması gerekir.
Karşı taraf
Bu bölümdeki bilgilerle oluşturulan dosyaların başka ortamlarda hemen okunabileceklerini düşünmeyin. Dosyayı oluşturan taraf ile dosyayı kullanan tarafın en azından dosya düzeni konusundan anlaşmış olmaları gerekir. Örneğin öğrenci numarasının ve isminin dosyaya yazıldıkları sırada okunmaları gerekir.
Bir dosya oluşturup içine bilgiler yazmak, o dosyanın başka bir ortamda açılıp okunması için yeterli olmayabilir. Dosyayı yazan tarafla okuyan tarafın belirli konularda anlaşmış olmaları gerekir. Örneğin dosyaya char[]
olarak yazılmış olan bir bilginin wchar[]
olarak okunması yanlış sonuç doğurur.
Ek olarak, aşağıdaki kodlar dosyaların başına BOM belirtecini yazmazlar. Bu, dosyalarınızın BOM belirteci gerektiren ortamlarda doğru olarak açılamamasına neden olabilir. ("Byte order mark"ın kısası olan BOM, karakterleri oluşturan UTF kod birimlerinin dosyaya hangi sırada yazılmış olduklarını belirtir.)
Dosya erişim hakları
Dosya sistemi dosyaları programlara çeşitli erişim haklarıyla sunar. Erişim hakları hem performans hem de dosya sağlığı açısından önemlidir.
Konu dosyadan okumak olunca; aynı dosyadan okumak isteyen birden fazla programa aynı anda okuma izni verilmesi, programlar birbirlerini beklemeyecekleri için hız kazancı sağlar. Öte yandan, konu dosyaya yazmak olunca; dosyanın içeriğinin tutarlılığı açısından dosyaya belirli bir anda ancak tek bir programın yazmasına izin verilmelidir; yoksa iki programın birbirlerinden habersiz olarak yazmaları sonucunda dosyanın içeriği tutarsız hale gelebilir.
Dosya açmak
Programın standart giriş ve çıkış akımları olan stdin
ve stdout
, program başladığında zaten açılmış ve kullanıma hazır olarak gelirler; onları kullanmaya başlamadan önce özel bir işlem gerekmez.
Dosyaların ise diskteki isimleri ve istenen erişim hakları bildirilerek program tarafından açılmaları gerekir. Aşağıdaki örneklerde de göreceğimiz gibi, oluşturulan bir File
nesnesi, belirtilen isimdeki dosyanın açılması için yeterlidir:
File dosya = File("ogrenci_bilgisi", "r");
Dosya kapatmak
Açılan dosyaların mutlaka kapatılmaları da gerekir. Ancak, File
nesneleri kendileri sonlanırken erişim sağlamakta oldukları asıl dosyaları da kapattıkları için, normalde bu işin programda açıkça yapılması gerekmez. Dosya, File
nesnesinin içinde bulunduğu kapsamdan çıkılırken kendiliğinden kapatılır:
if (bir_koşul) { // File nesnesi burada oluşturulmuş ve kullanılmış olsun // ... } // ← Dosya bu kapsamdan çıkılırken otomatik olarak // kapatılır. Açıkça kapatmaya gerek yoktur.
Bazen aynı File
nesnesinin başka dosyayı veya aynı dosyayı farklı erişim haklarıyla kullanması istenir. Böyle durumlarda dosyanın kapatılıp tekrar açılması gerekir:
dosya.close(); // dosyayı kapatır dosya.open("ogrenci_bilgisi", "r"); // dosyayı açar
Dosyaya yazmak ve dosyadan okumak
Dosyalar da karakter akımları olduklarından, writeln
ve readf
gibi işlevler onlarla da kullanılabilir. Farklı olan, dosya değişkeninin isminin ve bir noktanın da yazılmasının gerekmesidir:
writeln("merhaba"); // standart çıkışa yazar stdout.writeln("merhaba"); // yukarıdakinin uzun yazımıdır dosya.writeln("merhaba"); // dosyaya yazar
Dosya sonu için eof()
Bir dosyadan okurken dosyanın sonuna gelinip gelinmediği, "dosya sonu" anlamına gelen "end of file"ın kısaltması olan eof()
üye işleviyle denetlenir. Bu işlev dosya sonuna gelindiğinde true
döndürür.
Örneğin, aşağıdaki döngü dosyanın sonuna gelene kadar devam eder:
while (!dosya.eof()) { // ... }
Klasör işlemleri için std.file
modülü
Klasör işlemleri ile ilgili olan std.file
modülünün belgesinde işinize yarayacak işlevler bulabilirsiniz. Örneğin, exists
belirtilen isimdeki dosyanın mevcut olup olmadığını bildirir:
if (exists(dosya_ismi)) { // dosya mevcut } else { // dosya mevcut değil }
std.stdio.File
yapısı
File
, C dilindeki standart fopen
işlevinin kullandığı erişim belirteçlerini kullanır:
Belirteç | Anlamı |
---|---|
r | okuma erişimi dosya başından okunacak şekilde hazırlanır |
r+ | okuma ve yazma erişimi dosya başından okunacak ve başına yazılacak şekilde hazırlanır |
w | yazma erişimi dosya yoksa: boş olarak oluşturulur dosya zaten varsa: içi boşaltılır |
w+ | okuma ve yazma erişimi dosya yoksa: boş olarak oluşturulur dosya zaten varsa: içi boşaltılır |
a | sonuna yazma erişimi dosya yoksa: boş olarak oluşturulur dosya zaten varsa: içeriği korunur ve sonuna yazılacak şekilde hazırlanır |
a+ | okuma ve sonuna yazma erişimi dosya yoksa: boş olarak oluşturulur dosya zaten varsa: içeriği korunur; başından okunacak ve sonuna yazılacak şekilde hazırlanır |
Yukarıdaki erişim haklarının sonuna 'b' karakteri de gelebilir ("rb" gibi). O karakter binary mode açma durumunu destekleyen platformlarda etkili olabilir ama POSIX ortamlarında gözardı edilir.
Dosyaya yazmak
Dosyanın önce yazma erişimi ile açılmış olması gerekir:
import std.stdio; void main() { File dosya = File("ogrenci_bilgisi", "w"); dosya.writeln("İsim : ", "Zafer"); dosya.writeln("Numara: ", 123); dosya.writeln("Sınıf : ", "1A"); }
Dizgiler bölümünden hatırlayacağınız gibi, "ogrenci_bilgisi" gibi bir dizginin türü string
'dir ve değişmezdir. Yani File
, dosya ismini ve erişim hakkını string
türü olarak kabul eder. Bu yüzden, yine o bölümden hatırlayacağınız gibi, File
örneğin char[]
türünde bir dizgi ile kurulamaz; kurmak gerektiğinde o dizginin .idup
niteliğinin çağrılması gerekir.
Yukarıdaki program, çalıştırıldığı klasör içinde ismi ogrenci_bilgisi
olan bir dosya oluşturur veya var olan dosyanın üzerine yazar.
Not: Dosya ismi olarak dosya sisteminin izin verdiği her karakteri kullanabilirsiniz. Ben bu kitapta dosya isimlerinde yalnızca ASCII harfler kullanacağım.
Dosyadan okumak
Dosyanın önce okuma erişimi ile açılmış olması gerekir:
import std.stdio; import std.string; void main() { File dosya = File("ogrenci_bilgisi", "r"); while (!dosya.eof()) { string satır = strip(dosya.readln()); writeln("Okuduğum satır -> |", satır); } }
Yukarıdaki program, çalıştırıldığı klasör içindeki ogrenci_bilgisi
isimli dosyanın içindeki satırları başından sonuna kadar okur ve standart çıkışa yazdırır.
Problem
Yazacağınız program kullanıcıdan bir dosya ismi alsın, o dosyanın içindeki boş olmayan bütün satırları, dosyanın ismine .bak
eklenmiş başka bir dosyaya yazsın. Örneğin, verilen dosyanın ismi deneme.txt
ise, boş olmayan satırlarını deneme.txt.bak
dosyasına yazsın.