Skip to content

File System ve IO (Dosya Sistemi)

File

IO sınıfından özellikler içeren File sınıfı, fiziki dosyalarla işlem yapmamızı sağlayan özellikleri sunar bize. Ruby’nin üzerinde çalıştığı işletim sistemine göre de file permission yani dosya üzerindeki yetki sistemi de devrededir.

Default olarak gelen Constant’ları:

File.constants # => [:Separator, :SEPARATOR, :ALT_SEPARATOR, :PATH_SEPARATOR, :Constants, :Stat, :WaitReadable, :WaitWritable, :EAGAINWaitReadable, :EAGAINWaitWritable, :EWOULDBLOCKWaitReadable, :EWOULDBLOCKWaitWritable, :EINPROGRESSWaitReadable, :EINPROGRESSWaitWritable, :SEEK_SET, :SEEK_CUR, :SEEK_END, :RDONLY, :WRONLY, :RDWR, :APPEND, :CREAT, :EXCL, :NONBLOCK, :TRUNC, :NOCTTY, :BINARY, :SYNC, :DSYNC, :NOFOLLOW, :LOCK_SH, :LOCK_EX, :LOCK_UN, :LOCK_NB, :NULL, :FNM_NOESCAPE, :FNM_PATHNAME, :FNM_DOTMATCH, :FNM_CASEFOLD, :FNM_EXTGLOB, :FNM_SYSCASE]

File::ALT_SEPARATOR  # => nil # Bu Ruby’nin çalıştığı platforma özeldir
File::PATH_SEPARATOR # => ":"
File::SEPARATOR      # => "/"
File::Separator      # => "/"

Örneğin Windows’da çalışan Ruby’de SEPARATOR ters slash \ şeklinde gelecektir.

Public Class Method’ları

absolute_path, expand_path, join, split

String olarak verilen path bilgisini absolute path’e çevirir. Eğer ikinci parametre verilmezse CWD (current working directory) yani o an için içinde çalıştığınız directory bilgisi kullanılır.

File.absolute_path("~")               # => "/~"
File.absolute_path(".gitignore", "~") # => "/~/.gitignore"

expand_path de bir nevi absolute path’e çevirir:

File.expand_path("~/.gitignore") # => "/Users/vigo/.gitignore"

Keza daha kompleks path bulma işlerinde de kullanılır. Bu durumda __FILE__ sabiti hayatımızı kolaylaştırır. O an Ruby script’inin çalıştığı dosyanın path’i __FILE__ sabitindedir. Örneğin aşağıdaki gibi bir directory yapısı olsa:

proje/
├── lib
│   └── users.rb
└── main.rb

ve lib/users.rb içinden, dışarıda bulunan main.rb dosyasının path’ine ulaşmak istesek;

File.expand_path("../../main.rb", __FILE__)

şeklinde kullanırız.

join kullanarak, Ruby’nin çalıştığı işletim sistemine bağlı olarak, File::SEPARATOR kullanarak geçilen string’leri birleştiririz:

File.join("usr", "local", "bin") # => "usr/local/bin"

Dizin ve dosya ayrıştırmasını da split ile yaparız:

File.split("usr/local/bin/foo")   # => ["usr/local/bin", "foo"]

atime, ctime, mtime

Dosyaya son erişilen tarihi atime ile, dosyada yapılmış olan son değişiklik tarihini de ctime ile, son değişiklik zamanını da mtime ile alırız.

File.atime("/Users/vigo/.gitignore") # => 2014-11-05 11:45:10 +0200
File.ctime("/Users/vigo/.gitignore") # => 2014-08-04 11:33:14 +0300
File.mtime("/Users/vigo/.gitignore") # => 2014-10-29 15:05:15 +0200

basename, dirname, extname

Path içinden dosya adını almak için basename kullanırız. Eğer parametre olarak atacağımız şeyi (örneğin extension olarak .gif, .rb gibi) geçersek bize sadece dosyanın adını verir.

File.basename("/Users/vigo/test.rb")        # => "test.rb"
File.basename("/Users/vigo/test.rb", ".rb") # => "test"

Bu işin tersini de dirname ile yaparız, yani directory adı gerekince:

File.dirname("/Users/vigo/test.rb") # => "/Users/vigo"

şekinde kullanırız. Dosyanın extension’ını öğrenmek için extname kullanırız.

File.extname("test_file.rb")          # => ".rb"
File.extname("/foo/bar/test_file.rb") # => ".rb"
File.extname("test_file")             # => ""

chmod, chown, lchmod, lchown

Her iki komut da Unix’den gelir. Change mod ve Change owner işlerini yapmamızı sağlar. chmod ile Unix izinlerini ayarlarız:

-rw-r--r-- 1 vigo wheel 0 Aug 30 19:19 file-01.txt
||||||||||
|||||||||+--- Others, Execute    (x) 1 = 2^0
||||||||+---- Others, Write      (w) 2 = 2^1
|||||||+----- Others, Read       (r) 4 = 2^2
||||||+------ Group, Execute     (x) 1 = 2^0
|||||+------- Group, Write       (w) 2 = 2^1
||||+-------- Group, Read        (r) 4 = 2^2
|||+--------- Owner/User Execute (e) 1 = 2^0
||+---------- Owner/User Write   (w) 2 = 2^1
|+----------- Owner/User Read    (r) 4 = 2^2
+------------ Is Directory?      (d)

file-01.txt dosyasında, User (yani dosyanın sahibi) Read ve Write hakkına sahiptir. Group ve Others ise sadece Read hakkına sahiptir. Bu durumda varolan bu dosyanin chmod değeri:

Owner/User  : Read, Write  => 4 + 2 = 6
Group       : Read         => 4     = 4
Others      : Read         => 4     = 4
----------------------------------------
644 unix file permission

şeklindedir. Hatta Terminal’den; stat -f ’%A’ file-01.txt yaparsak 644 olduğunu da görebiliriz. Şimdi bu dosyayı Ruby ile sadece sahibi tarafından okunur ve yazılır yapıp, başka hiçbir kimse tarafından okunamaz ve yazılamaz hale getirelim:

File.chmod(0600, "file-01.txt")

Keza dosyanın sahibini de düzenlemek için chown kullanırız. Aynı terminaldeki gibi KULLANICI:GRUP şeklinde, toplamda üç parametre geçeriz. İlki kullanıcıyı belirler. nil ya da -1 geçtiğimiz taktirde ilgili şeyi set etmemiş oluruz. Yani sadece grubu değiştireceksek kullanıcı için nil ya da -1 geçebiliriz.

File.chown(nil, 20, "/tmp/file-01.txt") # => 1

Grup ID olarak 20 geçtik, OSX’deki id 20 karşılık olarak staff grubuna denk gelir.

lchmod ve lchown ile normal chmod,chown farkı, l ile başlayanlar sembolik linkleri takip etmezler.

ftype, stat, lstat, size

Dosyanın ne tür bir dosya olduğunu ftype ile anlarız:

File.ftype("/tmp/file-01.txt") # => "file"
File.ftype("/usr/")            # => "directory"
File.ftype("/dev/null")        # => "characterSpecial"

stat ile aynen biraz önce shell’den yaptığımız (stat -f ’%A’ file-01.txt) gibi aynı işi Ruby’den de yapabiliriz:

File.stat("/tmp/file-01.txt")       # => #<File::Stat dev=0x1000004, ino=1540444, mode=0100600, nlink=1, uid=501, gid=20, rdev=0x0, size=4, blksize=4096, blocks=8, atime=2014-11-12 14:41:49 +0200, mtime=2014-11-12 14:40:13 +0200, ctime=2014-11-12 14:45:28 +0200>
File.stat("/tmp/file-01.txt").uid   # => 501
File.stat("/tmp/file-01.txt").gid   # => 20
File.stat("/tmp/file-01.txt").mtime # => 2014-11-12 14:40:13 +0200

lstat da aynı işi yapar fakat aynı lchmod ve lchown daki gibi sembolik linkleri takip etmez!

Dosyanın byte cinsinden büyüklüğünü almak için sizekullanırız:

File.size("/Users/vigo/.gitignore") # => 323

delete, unlink, link, rename, readlink, symlink

Her ikisi de dosya silmeye yarar. Eğer dosya başarıyla silinirse 1 döner, aksi halde hata alırız!

File.delete("/tmp/foo.txt") # => 1 yani silindi
File.delete("/tmp/foo1.txt") # => No such file or directory

link ile HARD LINK oluşturuyoruz. Bunu dosyanın bir kopyası / yansıması gibi düşünebilirsiz. Orijinal dosya değiştikçe linklenmiş dosya da güncel içeriğe sahip olur. Link’in hangi dosyaya bağlı olduğunu da readlink ile okuruz:

File.link("orijinal_dosya", "linklenecek_dosya")
File.readlink("linklenecek_dosya") # => "orijinal_dosya"

Sembolik link yani symlink için;

File.symlink("foo.txt", "bar.txt") # => 0
File.readlink("bar.txt")           # => "foo.txt"

Komut satırından bakınca;

-rw-r--r--   1 vigo wheel    0 Dec 13 16:08 foo.txt
lrwxr-xr-x   1 vigo wheel    7 Dec 13 16:08 bar.txt -> foo.txt

şeklinde bar.txt dosyasının foo.txt dosyasına linklendiğini görürüz.

Dosya ismini değiştirmek için rename kullanırız.

File.rename("/tmp/file-01.txt", "/tmp/file-01.txt.bak") # => 0

file?, directory?, executable?, exist?, identical?, readable?, size?,

Dosya gerçekten fiziki bir dosya mı? ya da directory mi? ya da bu dosya var mı?

# file?
File.file?("/tmp/file-01.txt")      # => true (evet)
File.file?("/tmp/file-02.txt")      # => false

# directory?
File.directory?("/tmp/file-02.txt") # => false
File.directory?("/tmp/test_folder") # => true (evet)

# dosya var mı?
File.exist?("/tmp/file-01.txt") # => true (var)
File.exist?("/tmp/file-02.txt") # => false

Daha önce dosya izinlerinden bahsetmiş, bazı dosyaların executable olduğunu söylemiştik. Acaba dosya çalıştırılabilir yani executable mı?

File.executable?("/tmp/file-01.txt")      # => false
-rw-r--r-- 1 vigo wheel   0 Nov 15 10:39 file-01.txt


File.executable?("/tmp/execuatable_file") # => true
-rwxr-xr-x 1 vigo wheel   0 Nov 15 10:43 execuatable_file

Executable olan dosyada x flag aktif gördüğünüz gibi :)

new, open

open method’u ile new aynı işi yapar. Dosya açmaya yarar. Dosyayı açarken hangi duruma göre açacağımızı yani okumak için mi? yazmak için mi? yoksa varolan dosyaya ek yapmak için mi? belirtmemiz gerekir.

f = File.new("/tmp/test.txt", "w")
f.puts "Merhaba"
f.close

/tmp/test.txt adlı bir dosya oluşturup içine puts ile Merhaba yazdık. Eğer cat /tmp/test.txt yaparsanız kontrol edebilirsiniz. Dikkat ettiyseniz mode olarak "w" kullandık. Bu mode’lar neler?

Mode Açıklama
r Read-only, sadece okumak için. Bu default mode’dur.
r+ Read+write, hatta read + prepend, pointer’ı başa alır, yani bu method’la bişi yazarsanız, yazdığınız şey dosyanın başına eklenir.
w Write-only, sadece yazmak içindir. Eğer dosya yoksa hata verir!
w+ Read+Write, hatta read + append, pointer’ı dosyanın sonuna alır ve yazdıklarınızı sona ekler. Eğer dosya yoksa hata verir!
a Write-only, Eğer dosya varsa pointer’ı sona alır ve sona ek yapar, dosya yoksa sıfırdan yeni dosya üretir.
a+ Read+write, Aynı a gibi çalışır, sona ek yapar, hem okumaya hem de yazmaya izin verir.
b Binary mode
t Text mode

fnmatch, fnmatch?

File Name Match yani dosya adı eşleştirmek. RegEx pattern’ine göre dosya adı yakalamak / kontrol etmek için kullanılır. 2 zorunlu ve 1 opsiyonel olmak üzere 3 parametre alabilir. Pattern, dosya adı ve opsiyonal olarak Flag’ler...

File.fnmatch(foo, foobar.rb)                       # => false
File.fnmatch(foo*, foobar.rb)                      # => true
File.fnmatch(*foo*, test_foobar.rb)                # => true

Şimdi;

File.fnmatch(**.rb, ./main.rb)                     # => false

Bu işlemin true dönemsi için FNM_DOTMATCH flag’ini kullanacağız:

File.fnmatch(**.rb, ./main.rb, File::FNM_DOTMATCH) # => true
0:0 1:0
FNM_DOTMATCH Nokta ile başlayan dosyalarda * kullanımına izin ver
FNM_EXTGLOB {a,b,c} gibi paternlerde global aramaya izin ver
FNM_PATHNAME Path ayraçlarında * kullanımını engelle
FNM_CASEFOLD Case in-sensitive yani büyük/küçük harf ayırt etme!
File::FNM_NOESCAPE ESCAPE kodu kullan
File.fnmatch(*, /, File::FNM_PATHNAME)             # => false
File.fnmatch(FOO*, foo.rb, File::FNM_CASEFOLD)     # => true
File.fnmatch(f{o,a}o*, foo.rb, File::FNM_EXTGLOB)  # => true
File.fnmatch(f{o,a}o*, fao.rb, File::FNM_EXTGLOB)  # => true
File.fnmatch(\foo*, \foo.rb)                       # => false
File.fnmatch(\foo*, \foo.rb, File::FNM_NOESCAPE)   # => true

IO

Tüm giriş/çıkış (Input/Output) işlerinin kalbi burada atar. File sınıfı da IO’nun alt sınıfıdır. Binary okuma/yazma işlemleri, multi tasking işlemler (Process spawning, async işler) hep bu sınıf sayesinde çalışır.

Ruby 101 seviyesi için biraz karmaşık olsa dahi, sadece fikriniz olması açısından, en bilinen ve kullanılan birkaç method’a değinmek istiyorum.

binread

Binary read, yani byte-byte okuma işlemi için kullanılır. Opsiyonel olarak geçilen 2.parametre, kaç byte okumak istediğimizi, 3.parametre de offset yani kaç byte öteden okumaya başlamak gerek bunu bildirir. Yani elinizde bir dosya olsun, dosyanın ilk 100 byte’ını 20.byte’tan itibaren okumanız gerekirse kullanacağınız method budur :)

# 1 byte atlayarak 3 byte okuduk ve
IO.binread("test.png", 3, 1) # => "PNG"

binwrite

Tahmin edeceğiniz gibi binread in tersi, yani Binary olarak yazma işini yapan method. Aynı şekilde opsiyonel 2 ve 3.parametreleri kullanabilirsiniz.

copystream

Birebir kopya yapmaya yarar. İlk parametre SOURCE yani neyi kopyalacaksınız, ikinci parametre DESTINATION yani nereye kopyalacaksınız, eğer kullanırsanız 3.parametre kopyalanacak byte adedi, eğer 4.parametre kullanırsanız aynı read/write daki gibi offset değeri olarak kullanabilirsiniz.

foreach

Elimizde test-file.txt olsun ve içinde;

satır 1
satır 2

yazsın... Satır-satır içinde dolaşmak için;

IO.foreach("test-file.txt"){|x| print "bu satır: ",x}

Dediğimizde çıktı;

bu satır: satır 1
bu satır: satır 2

şeklinde içeriye block pas edip kullanabiliriz.

popen

Subprocess yani alt işlemler açmak için kullanılır. Özellikle Ruby üzerinden SHELL komutları çağırmak için çok kullanılan bir yöntemdir. Asenkron işler.

# Bu işlem asenkron/alt işlem olarak çalışır...
IO.popen("date") do |response|
  system_date = response.gets
  puts "system_date: #{system_date}"
end

/tmp/ dizinini listeleyelim:

p = IO.popen("ls /tmp/")
p.pid       # => 52389
p.readlines # => ["D8D75028-234B-4F49-9358-C4C4775B4A08_IN\n", "D8D75028-234B-4F49-9358-C4C4775B4A08_OUT\n", "F7C71944B49B446081C0603DE90E4855_IN\n", "F7C71944B49B446081C0603DE90E4855_OUT\n", "KSOutOfProcessFetcher.501.OlaJUhhgKAnFsX7fZ0FyXTFxIgg=\n", "com.apple.launchd.4H4RVax25p\n", "com.apple.launchd.Kn8Wcx4NQX\n", "fo\n", "lilo.12159\n", "swtag.log\n", "test-file.txt\n"]

Gördüğünüz gibi pid yani Process ID : 52389, eğer shell’den;

ps ax | grep 52389

derseniz;

52389   ??  Z      0:00.00 (ls)

gibi ilgili işlemi görürsünüz.