İçeriğe geç
academia.sh

Ders 12 / 12

Dosya ve Girdi/Çıktı

File.open çağrısında sonucu bloğun, dosyanın kapanmasını ise yöntemin belirlediği ölçülür; blok istisna fırlatsa da dosya kapanır. Ders, kursun on iki gözleminin kapanış tablosunu da verir.

Yayın Güncelleme

Bu derste

  • File.read, File.readlines ve File.foreach ile dosyadan okur, File.write ile yazar
  • Bloklu ve bloksuz File.open çağrısının dönüş değerini ve closed? durumunu ayırt eder
  • Standart girdiyi bir borudan besleyip satır satır okur
  • Blok istisna fırlatsa da dosyanın kapandığını ölçer ve kaynak temizlemeyi bloğun değil yöntemin üstlendiğini söyler
İçindekiler

Önceki dersin sonunda ensure her durumda koşan bir dal olarak görüldü. Dosyalar bu yapının en sık kullanıldığı yerdir: açılan bir dosya kapatılmak zorundadır ve kapatmayı unutmak bir sızıntıdır. Ruby’de File.open çağrısına bir blok verilebilir; soru, kapatma işinin kime ait olduğudur. Girdi/çıktı (input/output), programın dış dünyayla, yani dosyalarla ve standart akışlarla veri alışverişidir.

Bu dersin yanıtlayanı bloktur: File.open iletisi aynı dosyaya iki farklı blokla iki farklı sonuç verir. Dersin sonunda kursun on iki gözleminin tamamı tek tabloda toplanır.

Dosya Okuma Gözlemi

Çekirdeğin on ikinci gözlemi dosya okuma’dır. Sınıflandırma yardımcısı yanit.rb kursun başından beri aynıdır ve burada yalnız diske yazılır.

# yanit.rb — bir iletiyi kimin yanitladigini siniflandiran yardimcilar

# Siniflandirma sirayla sorar: once ifade bir ileti mi (defined?),
# sonra iki farkli blok iki farkli sonuc veriyor mu, sonra yontemin sahibi kim.
def yanitlayan(tanim, sahip: nil, sonuc: nil, bloklu: nil)
  return ["sozdizimi", "defined? #{tanim}"] unless tanim == "method"

  if bloklu
    birinci, ikinci = bloklu.call
    return ["blok", "#{birinci.inspect} / #{ikinci.inspect}"] unless birinci == ikinci
  end

  tur = sahip.instance_of?(Class) ? "sinif" : "modul"
  [tur, "#{sahip}, sonuc #{sonuc.inspect}"]
end
# dosya_okuma.rb — ayni File.open iki blokla iki sonuc verir
require_relative "yanit"

File.write("not.txt", "ilk satir\nikinci satir\n")

tanim = defined?(File.open("not.txt"))
tur, kanit = yanitlayan(tanim, bloklu: -> {
  [File.open("not.txt") { |f| f.gets.chomp }, File.open("not.txt") { |f| f.readlines.size }]
})
puts tanim
puts "#{tur}  #{kanit}"
puts File.method(:open).owner.instance_of?(Class)
method
blok  "ilk satir" / 2
true

File.write iki satırlık not.txt dosyasını yazdı. defined? bloksuz çağrıyı "method" olarak bildirdi, yani ortada bir ileti var. İlk blok ilk satırı, ikinci blok satır sayısını döndürdü: aynı ileti, "ilk satir" ve 2. Yanıtlayan bloktur. File.open’ın sahibi bir sınıftır (instance_of?(Class) true); sahibin tekil sınıf olarak görünmesi ikinci kursun konusudur ve burada yalnız sonucu okunur. KB56. Ölçüm tek bir dosya ve iki blokla yapıldı.

Satır Okuma ve Yazma Akışları

Bloksuz kısa yollar, dosyayı açıp kapatma işini kendileri yapar. File.write yazılan bayt sayısını döndürür; File.readlines satırları bir dizi yapar ve chomp: true satır sonunu atar; File.foreach satırları tek tek verir. Olmayan bir dosya Errno::ENOENT fırlatır. Hata iletisi yol taşıdığı için yalnız sınıf basılır.

# akislar.rb — yazma, okuma ve eksik dosya
File.write("not.txt", "ilk satir\nikinci satir\n")

puts File.write("b.txt", "abc")
puts File.readlines("not.txt", chomp: true).inspect
puts File.foreach("not.txt").count
begin
  File.read("yok.txt")
rescue Errno::ENOENT => e
  puts e.class
end
3
["ilk satir", "ikinci satir"]
2
Errno::ENOENT

abc üç bayttır ve File.write 3 döndürdü. readlines iki dizgiden oluşan bir dizi verdi. foreach bloksuz çağrıldığında bir yineleyici döndürür; count onu tüketip 2 buldu. Eksik dosya Errno::ENOENT fırlattı ve dal onu yakaladı. KB57. File.write yalnız dizgi içerikle ölçüldü.

Açık bir dosyayla çalışırken satır okuma yöntemleri örneğin üzerinde çağrılır. Bunların sahibi de ölçülebilir.

# sahipler.rb — dosya ve standart cikti yontemlerinin sahibi
File.write("not.txt", "ilk satir\nikinci satir\n")

File.open("not.txt") do |f|
  puts f.class
  puts f.method(:gets).owner
  puts f.method(:each_line).owner
end
puts $stdout.method(:puts).owner
File
IO
IO
IO

Dosya nesnesi bir File‘dır, ama gets ve each_line onun kendi sınıfında değil IO sınıfında tanımlıdır. $stdout.puts de aynı IO sınıfındadır. Dosya ile standart çıktı aynı sınıf ailesinin üyesidir ve bu üçünde de yanıtlayan sınıftır. KB58. Sahipler Ruby 4.0.7’de ölçüldü; hangi yöntemin hangi sınıfta tanımlı olduğu sürümden sürüme taşınabilir.

Açık Kalan ve Kapanan Dosya

File.open bloksuz çağrılırsa dosya nesnesini döndürür ve dosya açık kalır; kapatmak çağıranın işidir. Bloklu çağrıda ise dosya nesnesi bloğa verilir, blok bitince dosya kapanır ve çağrının değeri bloğun son ifadesidir.

# kapanma.rb — bloksuz ve bloklu File.open
File.write("not.txt", "ilk satir\nikinci satir\n")

f = File.open("not.txt")
puts f.class
puts f.closed?
f.close
puts f.closed?

tutulan = nil
deger = File.open("not.txt") do |dosya|
  tutulan = dosya
  puts dosya.closed?
  42
end
puts deger
puts tutulan.closed?
File
false
true
false
42
true

Bloksuz çağrı File döndürdü, closed? false çıktı ve ancak elle close çağrılınca true oldu. Bloklu çağrıda dosya blok içinde açıktı. Blok 42 ile bitti: dönüş değeri 42 oldu, blok dışına taşınan dosya nesnesi ise artık kapalıydı. Sonuç bloktan, kapanma yöntemden gelir. KB59. Ölçüm yerel dosya ve tek bir blok için yapıldı.

Açma Kipleri

File.open ikinci argüman olarak bir kip alır. "w" dosyayı açarken sıfırlar, "a" mevcut içeriğin sonuna ekler. Fark satır sayısıyla ölçülür. Bloklu File.open çağrısının değeri bloğun son ifadesidir; write yazılan bayt sayısını verir.

# kipler.rb — w sifirlar, a sonuna ekler
File.write("kip.txt", "bir\niki\n")

File.open("kip.txt", "a") { |f| f.puts "uc" }
puts File.readlines("kip.txt").size

donus = File.open("kip.txt", "w") { |f| f.write("tek\n") }
puts donus
puts File.readlines("kip.txt").size

File.open("kip.txt", "a") { |f| puts f.method(:puts).owner }
3
4
1
IO

İki satırlık dosyaya "a" ile bir satır eklenince sayı 3 oldu. "w" ile açılış dosyayı sıfırladı: blok write değerini, yani 4 baytı döndürdü ve satır sayısı 1’e indi. puts yöntemi IO sınıfında tanımlıdır; yanıtlayan sınıftır. KB60. Ölçüm yalnız "a" ve "w" kipleriyle yapıldı; okuma ve yazma kipleri birlikte denenmedi.

puts ve print Farkı

Dosyaya yazarken puts satır sonu ekler, print eklemez. Fark, yazılan içeriğin inspect çıktısında görünür.

# yazim_farki.rb — puts satir sonu ekler, print eklemez
File.open("fark.txt", "w") do |f|
  f.puts "a"
  f.print "b"
  f.puts "c"
end
puts File.read("fark.txt").inspect
puts File.size("fark.txt")
File.open("fark.txt") { |f| puts f.method(:print).owner }
"a\nbc\n"
5
IO

Dosyada a, satır sonu, b, c ve yine satır sonu var; b ile c bitişik yazıldı. Toplam beş bayt. print de IO sınıfındadır; yanıtlayan sınıftır. KB61. İçerik ASCII dizgilerdi; çok baytlı karakterlerde bayt sayısı karakter sayısından ayrışır ve burada ölçülmedi.

Var Olma ve Boyut

File.exist? bir yolun var olup olmadığını, File.size bayt boyutunu verir. Açık bir dosya nesnesinin kendi size yöntemi de vardır ve sahibi File’dır.

# boyut.rb — var olma ve boyut
File.write("boyut.txt", "abcd")
puts File.exist?("boyut.txt")
puts File.exist?("yok.txt")
puts File.size("boyut.txt")
File.open("boyut.txt") do |f|
  puts f.size
  puts f.method(:size).owner
end
true
false
4
4
File

Var olan dosya için true, olmayan için false döndü. Boyut 4 bayttı ve açık nesne de aynı değeri verdi. Bu kez sahip IO değil File’dır: dosyaya özgü bilgi alt sınıfta durur, akış işleri üst sınıfta. KB62. Boş dosya ve silinmiş dosya ölçülmedi.

Satır Satır ve Enumerable

each_line blok verilmezse bir yineleyici döndürür ve chomp: true satır sonlarını atar. Yineleyicinin map yöntemi Enumerable modülünden gelir. Okuma, dönüştürme, süzme ve yazma bir akış olarak bağlanabilir.

# akis.rb — oku, map, select, yaz
File.write("girdi.txt", "elma\nkar\narmut\nay\n")

satirlar = []
File.open("girdi.txt") { |f| f.each_line(chomp: true) { |s| satirlar << s } }
puts satirlar.inspect

yineleyici = File.foreach("girdi.txt", chomp: true)
puts yineleyici.method(:map).owner

sonuc = yineleyici.map(&:upcase).select { |s| s.size > 3 }
yazilan = File.write("cikti.txt", sonuc.join("\n") + "\n")
puts yazilan
puts File.read("cikti.txt").inspect
["elma", "kar", "armut", "ay"]
Enumerable
11
"ELMA\nARMUT\n"

each_line dört satırı sondaki satır sonu olmadan verdi. File.foreach blok almadan çağrılınca bir yineleyici döndürdü ve map onun için Enumerable modülündedir; yanıtlayan modüldür. upcase sonrası select dört harften uzun olanları bıraktı: ELMA ve ARMUT. Yazılan ELMA, satır sonu, ARMUT, satır sonu on bir bayttır. KB63. Akış tek geçişli ve küçük bir girdiyle ölçüldü; büyük dosyada belleğe ne olduğu ölçülmedi.

Standart Girdiyi Borudan Okumak

Standart girdi, programın dışarıdan satır aldığı akıştır ve $stdin ile okunur. Ders blokları etkileşimli girdi beklemez; bu yüzden girdi bir boru (pipe) ile verilir: bir komutun çıktısı, sonraki komutun girdisi olur.

# oku.rb — standart girdiyi satir satir okur
ilk = $stdin.gets.chomp
kalan = $stdin.each_line.map(&:chomp)
puts ilk
puts kalan.inspect
puts $stdin.method(:gets).owner
# girdi.sh — oku.rb'yi borudan besler
printf 'bir\niki\nuc\n' | ruby oku.rb
bir
["iki", "uc"]
IO

gets ilk satırı okudu, each_line kalanları tüketti. $stdin.gets de IO sınıfındadır; dosyayı da klavyeyi de aynı sınıf okur. KB64. Girdi üç satırlık sabit bir dizgiydi; boş girdi ve çok büyük girdi ölçülmedi.

Aynı boru satır saymak için de kullanılır. each_line bloksuz çağrılınca bir yineleyici döndürür; count ona Enumerable modülünden gelir.

# say.rb — standart girdideki satirlari sayar
puts $stdin.each_line.count
puts $stdin.method(:each_line).owner
puts $stdin.each_line.method(:count).owner
# say.sh — say.rb'yi borudan besler
printf 'a\nb\nc\nd\n' | ruby say.rb
4
IO
Enumerable

Dört satırlık girdi 4 olarak sayıldı. each_line yöntemi IO sınıfındadır, yineleyicinin count yöntemi ise Enumerable modülündedir; bu iki yanıtlayan aynı ifadede art arda çalışır. KB65. Girdi sabit ve dört satırlıktı; son satırı satır sonsuz biten girdi ölçülmedi.

Sınırlayıcı Ölçüm

Bu dersin yanıtlayanı blok, ama bu cümle yarım kalırsa yanıltır: blok sonucu belirler, kaynağı temizleyen blok değildir. Kapatmayı bloklu File.open yöntemi üstlenir. Bunu ayırmak için bloğun içinde bir istisna fırlatılır. Dosya nesnesi bloğun dışında tutulur, istisna yakalanır ve ardından dosyanın durumu sorulur. Karşılaştırma için aynı istisna bloksuz açılmış bir dosyanın yanında fırlatılır.

# istisna_kapanis.rb — blok istisna firlatsa da dosya kapanir
File.write("not.txt", "ilk satir\nikinci satir\n")

tutulan = nil
begin
  File.open("not.txt") do |dosya|
    tutulan = dosya
    raise "blok koptu"
  end
rescue => e
  puts e.class
end
puts tutulan.closed?

elle = File.open("not.txt")
begin
  raise "elle acik"
rescue => e
  puts e.class
end
puts elle.closed?
elle.close
puts elle.closed?
RuntimeError
true
RuntimeError
false
true

İlk durumda blok RuntimeError fırlattı, dal onu yakaladı ve dosya yine de kapalıydı (true). İkinci durumda aynı istisna bloksuz açılmış dosyanın yanında fırlatıldı ve dosya açık kaldı (false) ta ki elle close çağrılana kadar. Fark bloğun içeriğinde değil, kapatma sorumluluğunun yerindedir: bloklu File.open, bloğu bir ensure içinde koşturur ve ne olursa olsun dosyayı kapatır. Bu, önceki dersteki kaynak temizleme yapısının dilin kitaplığına yerleştirilmiş biçimidir. Sonucu blok belirler, kaynak temizliğini File.open üstlenir. KB66. Ölçüm RuntimeError ile yapıldı; blokta break ya da throw ile çıkış denenmedi.

Özet

  • File.read, File.readlines ve File.foreach dosyayı kendileri açıp kapatır; File.write yazılan bayt sayısını döndürür; olmayan bir dosyayı okumak Errno::ENOENT fırlatır.
  • Bu dersin yanıtlayanı bloktur: aynı File.open iletisi bir blokla "ilk satir", öteki blokla 2 verdi.
  • Bloksuz File.open dosyayı açık bırakır; bloklu çağrıda blok bitince closed? true olur ve dönüş değeri bloğun değeridir.
  • gets, each_line ve $stdout.puts IO sınıfında tanımlıdır; standart girdi bir borudan beslenerek okunur.
  • "w" dosyayı sıfırlar, "a" sonuna ekler; puts satır sonu ekler, print eklemez; yineleyicinin map ve count yöntemleri Enumerable modülündedir.
  • Sınırlayıcı ölçüm: blok istisna fırlatsa da dosya kapanır, çünkü kapatmayı blok değil File.open yöntemi yapar.

Kurs Kapanışı

Kursun her dersi aynı soruyu sordu: bu iletiyi kim yanıtlıyor. Tablo, on iki dersin ölçtüğü iletiyi, o dersin yanıtlayanını ve sınırlayıcı ölçümünün söylediği şeyi yan yana koyar. Yanıtlayan sütunu her dersin ## Özet bölümünde yazılı olandır.

Ders Ölçülen ileti Yanıtlayan Sınırlayıcı ölçüm
Ruby’nin Tasarım Felsefesi 5.between?(1, 10) modül between? Comparable’da, <=> alıcının sınıfında
Nesne Modeli Sezgisi 5 + 3 sınıf Integer.=== iletisini Module yanıtlar
Değişkenler ve Sabitler x = 5 sözdizimi y += 1 atamadır, içindeki toplamayı Integer yanıtlar
Denetim Akışı (1..10) === 7 sınıf case, dal nesnesinin sınıfına sorar
Yöntemler format("%03d", 7) modül üst düzey def Object’e yazılır ve özeldir
Semboller ve Dizgiler "ad".to_sym sınıf sihirli yorum sahibi değiştirmez, dosya başına geçerlidir
Diziler ve Karma Yapılar Hash.new { 0 }[:x] blok blok yalnız anahtar yokken yanıtlar
Bloklar sayilar.map { ... } blok each iki blokla da [1, 2] verir, sahibi Array
Enumerable Modülü (1..5).each_slice(2) modül sum aralıkta modülde, dizide Array’de
Proc ve Lambda ->(n) { n * 2 }.call(3) sınıf sahip aynı, fark lambda? durumunda
İstisnalar begin ... rescue ... end sözdizimi dalın eşleşmesi === iletisiyle yapılır
Dosya ve Girdi/Çıktı File.open("not.txt") { ... } blok kapatmayı blok değil yöntem yapar

On iki dersin yanıtlayanı dağılımı: 4 sınıf · 3 modül · 3 blok · 2 sözdizimi. Bu, çekirdeğin on iki gözleminin dağılımıyla aynıdır, çünkü çekirdek her ders için bir gözlem taşır. İki sonuç tablodan okunur. Birincisi, yanıtlayan çoğu zaman tek başına çalışmadı: sınırlayıcı ölçümlerin çoğu, dersin yanıtlayanının yanında başka bir yanıtlayanın da çalıştığını gösterdi. İkincisi, yanıtlayan sorusunun cevabı iletiye değil, iletinin alıcısına, verilen bloğa ve ifadenin biçimine bağlıdır. KB67. Tablo bu kursun on iki gözleminin dağılımını verir; Ruby’nin tamamının dağılımı hakkında bir iddia taşımaz.

Kurs burada kapanır. Ruby’de bir işlemin sonucunu üreten kod dört yerde durur: alıcının sınıfında, karıştırılmış bir modülde, çağıranın verdiği blokta ya da dilin kendi sözdiziminde. Bu kurs bu dört yeri yalnız sonucu ölçerek ayırdı; bir yöntemin neden belirli bir sınıfta ya da modülde bulunduğu sorusu açık kaldı. Sıradaki kursun konu alanı sınıf tanımı, kalıtım, modüller ve karışımlar, yöntem arama zinciri ve metaprogramlamadır; bu derslerde bugünkü ölçümlerin arkasındaki düzenek ele alınacaktır.

İlerlemeni kaydetmek ve not almak için Giriş yap

Notlarım

Not almak için giriş yapmalısın.

Aramak için yazmaya başlayın.

↑↓ ↵ Esc gezin · aç · kapat