1.4.1.13. fejezet, File modul és IO
A Ruby fájl kezelésre egy IO nevű osztályt jelölt ki, ami képes megnyitni és lezárni adatfolyamokat (streams), bájtok sorozatát, majd írhatjuk és olvashatjuk ezeket az osztály metódusaival. Például van egy textfile.txt állományunk, néhány sor szöveggel. Így nyitható meg a fájl, és olvasható be a szöveg:
IO.foreach("testfile.txt") {|line| print( line ) }
Itt a foreach az IO osztály metódusa, vagyis nem szükséges példányosítani az IO-t hogy használjuk, csak egy fájlnevet kell átadni, és a foreach kap egy blokkot, amivel az összes sort feldolgozza. Nem kell megnyitni majd lezárni a fájlt, mint más programnyelveknél, a Ruby.IO.foreach megteszi ezeket nekünk.
Az IO-nak rengeteg ehhez hasonló metódusa van. Például itt a readlines metódus, ami tömbbe olvassa a fájlt későbbi használatra:
lines = IO.readlines("testfile.txt") lines.each{|line| print( line )}
A File osztály egy leszármazottja az IO osztálynak, és a fenti eljárások átalakíthatók felhasználva a File osztályt:
File.foreach("testfile.txt") {|line| print( line ) } lines = File.readlines("testfile.txt") lines.each{|line| print( line )}
Fájl megnyitása és lezárása
Egy állomány a File példányosításán keresztül, a new metódussal, vagy az open metódussal nyitható meg. Első paramétere a fájl neve, második a megnyitás módja.
Mód | Jelentés |
"r" | Csak olvasásra, kezdés a file elején (alapértelmezett mód). |
"r+" | Írásra-olvasásra, kezdés a file elején |
"w" | Csak írásra, levágja a file-t nulla hosszúságra, vagy létrehoz egy új fájlt írásra. |
"w+" | Írásra-olvasásra, levágja a file-t nulla hosszúságra, vagy létrehoz egy új fájlt írásra-olvasásra. |
"a" | Csak írásra, kezdés a fájl végén, ha létezik, különben létrehoz egy új fájlt írásra |
"a+" | Írásra-olvasásra, kezdés a fájl végén, ha létezik, különben létrehoz egy új fájlt írásra-olvasásra |
"b" | (csak DOS/Windows) Bináris file mód (együtt használható a fenti karakterek közül valamelyikkel). |
Nézzünk egy példát egy teljes feldolgozásra.
f = File.new("myfile.txt", "w") f.puts( "I", "wandered", "lonely", "as", "a", "cloud" ) f.close
A lezárás nem csak a fájl mutató elengedését jelenti, hanem a buffer kiírását is. Most, hogy kiírtunk néhány sort, olvassuk vissza. Most történjen egy kis számolás is, számoljuk meg a karaktereket (ASCII fájlba egy bájt egy karakter), és a sorokat (újsor karakter az ASCII 10, amit a "\n" helyére kell írni, ha 1.8-as Ruby-val dolgozunk. Ebben az esetben ugyanis a getc() metódus FixNum típussal tér vissza).
charcount = 0 linecount = 0 f = File.new("myfile.txt", "r") while !( f.eof ) do c = f.getc() if ( ((c.class == String)&&(c == "\n")) || ((c.class == Fixnum)&&(c==10)) ) then linecount += 1 puts( " <End Of Line #{linecount}>" ) else putc( c ) charcount += 1 end end if f.eof then puts( "<End Of File>" ) end f.close puts("This file contains #{linecount} lines and #{charcount} characters." )
Eredmények (1.9-es Ruby-val):
I <End Of Line 1> wandered <End Of Line 2> lonely <End Of Line 3> as <End Of Line 4> a <End Of Line 5> cloud <End Of Line 6> <End Of File> This file contains 6 lines and 23 characters.
Fájlok és könyvtárak
A fájl vagy könyvtár létezését a File.exist? metódussal ellenőrizhetjük.
if File.exist?( "C:\\" ) then puts( "Yup, you have a C:\\ directory" ) else puts( "Eeek! Can't find the C:\\ drive!" ) end
Ha különbséget szeretnél tenni adatállomány és könyvtár között, használt a directory? metódust.
def dirOrFile( aName ) if File.directory?( aName ) then puts( "#{aName} is a directory" ) else puts( "#{aName} is a file" ) end end
Fájlok másolása
Másoljunk most egy könyvtárból fájlokat.
require( "fileutils" ) overwrite_prompt = true # get source directory puts( "FROM which directory would you like to copy the files?" ) sourcedir = gets().chomp() if !(File.directory?(sourcedir)) then puts( "A directory called #{sourcedir} cannot be found!" ) else # get target dir puts( "INTO which directory would you like to copy the files?" ) targetdir = gets().chomp() ok = true # if targetdir doesn't exist... if !(File.directory?(targetdir) ) then ok = false puts( "#{targetdir} cannot be found!" ) puts( "Would you like to create it?") answer = gets() if (answer[0,1].downcase == 'y') then FileUtils.mkdir( targetdir ) # create targetdir ok = true end end if ok then Dir.foreach( sourcedir ){ |f| filepath = "#{sourcedir}#{File::SEPARATOR}#{f}" if !(File.directory?(filepath) ) then if File.exist?("#{targetdir}#{File::SEPARATOR}#{f}") then puts("#{f} already exists in target directory (not copied)" ) else FileUtils.cp( filepath, targetdir ) puts("Copying... #{filepath}" ) end end } end end # if sourcedir was not found puts( "End" )
Ez a program nem másolja az egész könyvtár struktúrát, csak a megadott könyvtárat. Hiba a fenti programban, hogy fájl és könyvtár neveket nem alakítja át a rendszer számára biztonságos formára. Így a balra dőlő dupla perjel a könyvtárnév elválasztásánál kivételt generált Linuxon, ezért javítottam a mintaprogramba. A hozzáférhetőségi jogosultságokat sem ellenőrzi, ami szintén kivételt generál. Ez még TODO szakaszban van, legalább is a könyvben. Az interneten több helyen egy Bash script hívást egyszerűbb megoldásnak tartanak. Később látunk majd rekurzív eljárást, ami az egész könyvtár hierarchiát másolja.
Könyvtár tudakozó
Most, hogy másoltunk egy könyvtárat, nézzük meg egy másik által foglalt méretet. Ehhez le kell menni minden alsó könyvtárakba, és azok összes alkönyvtáraiba, és így tovább. Ezt rekurzióval csináljuk.
$dirsize = 0 # total size of all files located in entire directory tree def processfiles( aDir ) totalbytes = 0 Dir.foreach( aDir ){ # process all files in a directory |f| mypath = "#{aDir}#{File::SEPARATOR}#{f}" s = "" if File.directory?(mypath) then if f != '.' and f != '..' then bytes_in_dir = processfiles(mypath) puts( "<DIR> ---> #{mypath} contains [#{bytes_in_dir/1024}] KB" ) end else filesize = File.size(mypath) totalbytes += filesize puts ( "#{mypath} : #{filesize/1024}K" ) end } end dirname = ".." # <= initially this is set to the parent of current directory if !(File.directory?(dirname)) then puts( "#{dirname} is not a valid directory" ) else processfiles( dirname ) # <= This is where processfiles is first called printf( "Size of this directory and subdirectories is #{$dirsize} bytes, #{$dirsize/1024}K, %0.02fMB", "#{$dirsize/1048576.0}" ) end
Itt is a szóközök és a hozzáférési jogosultság okozhat kivételeket.
Sorba rendezés méret alapján
A fenti program névsorban adja vissza a könyvtárak és fájlok méretét, most viszont a fájlokat relatív mérete alapján rendezzük sorba, csökkenő méret szerint.
$dirsize = 0 $files = [] def processfiles( aDir ) totalbytes = 0 Dir.foreach( aDir ){ |f| mypath = "#{aDir}#{File::SEPARATOR}#{f}" if File.directory?(mypath) then if f != '.' and f != '..' then fsize = processfiles(mypath) / 1024 puts( "<DIR> --->#{mypath} contains [#{fsize}] KB" ) $files << fsize end else filesize = File.size(mypath) totalbytes += filesize end } $dirsize += totalbytes return totalbytes end dirname = ".." if !(File.directory?(dirname)) then puts( "#{dirname} is not a valid directory" ) else processfiles( dirname ) printf("Size of #{dirname} and subdirectories is #{$dirsize} bytes, #{$dirsize/1024}K, %0.02fMB\n\n", "#{$dirsize/1048576.0}" ) puts( "This is an unordered list of the file sizes..." ) p( $files ) puts( "This is an unordered list of the file sizes (with 0 byte entries deleted)..." ) $files.delete(0) p( $files ) puts( "This is a sorted list (low to high) of the file sizes" ) p( $files.sort ) puts( "This is a sorted list (high to low) of the file sizes" ) p( $files.sort.reverse ) end
Az egyetlen baj a fenti megoldással, hogy megvan ugyan a fájlok mérete, viszont a hozzá tartozó fájl neve hiányzik. Egy jobb megoldás a Hash tömbök használata tömbök helyett.
$dirsize = 0 $dirs = {} $files = {} def processfiles( aDir ) totalbytes = 0 Dir.foreach( aDir ){ |f| mypath = "#{aDir}#{File::SEPARATOR}#{f}" if File.directory?(mypath) then # if this is a directory if f != '.' and f != '..' then # recurse... dsize = processfiles(mypath) / 1024 $dirs[mypath] = dsize end else filesize = File.size(mypath) totalbytes += filesize $files[mypath] = filesize end } $dirsize += totalbytes return totalbytes end dirname = ".." if !(File.directory?(dirname)) then puts( "#{dirname} is not a valid directory" ) else processfiles( dirname ) printf("Size of this directory and subdirectories is #{$dirsize} bytes, #{$dirsize/1024}K, %0.02fMB\n\n", "#{$dirsize/1048576.0}" ) puts( "File sizes (ascending)...") $files = $files.sort{|a,b| a[1]<=>b[1]} $files.each{ |fname,fsize| puts( "#{fname} : #{fsize} bytes" ) } puts( "\nDirectory sizes (ascending)...") $dirs = $dirs.sort{|a,b| a[1]<=>b[1]} $dirs.each{ |dname,dsize| puts( "#{dname} : #{dsize}K" ) } end
- A hozzászóláshoz be kell jelentkezni