ファイルアクセス
- ファイルアクセス
- ファイルをオープンする
- テキストファイルをオープンして内容を出力する
- 読み込む長さを指定する
- ファイルの内容を一度に読み込む
- 1行ずつ読み込みを行う
- テキストファイルの特定の行を読み込む
- 一時ファイルを作成する
- 固定長レコードを読む
- ファイルに書き込む
- ファイルをコピーする
- フィルタ系のコマンドを作成する
- ファイルタイプを取得する
- ファイルの詳細情報を取得する
- ファイルモードを変更する
- ファイルの所有者とグループを変更する
- ファイルの最終アクセス時刻と最終更新日時を変更する
- 相対パスから絶対パスを求める
- ファイル名解析
ファイルアクセス
ファイルアクセスはFileクラスを使用します。Fileクラスではファイル名やモードの変更などのファイル操作、ファイルの入出力を行うための機能を提供しています。Fileクラスは基本的な入出力機能を提供するIOクラスを継承しています。
ファイルをオープンする
組込み関数のopen、File::openメソッドによりファイルをオープンすることができます。指定できるファイルオープンモードは基本的に以下の6種類ですが、それぞれバイナリモードでのオープンもありますので、合計で12種類のモードがあることになります。
- "r"
- 読み込みモード
- "r+"
- 読み書き両用モード
- "w"
- 新規作成書き込みモード
- "w+"
- 新規作成読み書き両用モード
- "a"
- 追加書き込みモード
- "a+"
- 追加書き込み読み書き両用モード
- "rb","r+b","wb","w+b","ab","a+b"
- 上記6パターンのバイナリモード
f = open("index,txt") # モード省略時は "r" でオープンされる
f = open("index.html", "w") # 書き込みモード
f = File::open("log.txt", "a") # 追加モード
組込み関数のopenでは"|"で始まるパスを指定すると以降をコマンドとして実行し、そのプロセスとのストリームを返します。以下の例ではsortコマンドでfooの中身をソートした結果をFileクラスのインスタンスfでアクセスすることができます。
f = open("|sort foo")
テキストファイルをオープンして内容を出力する
ファイルをオープンするには組込み関数のopen、File::openメソッドを、ファイルの読み込みは組込み関数のIO#eachメソッド、IO#getsメソッドを使用することができます。
#
# IO#eachを使う例
#
f = open("foo")
f.each {|line| print line}
f.close
#
# IO#getsを使う例
#
f = open("foo")
while line = f.gets
print line
end
f.close
また、File::openメソッドにブロックを渡すと、ファイルオブジェクトを与えられてブロックが実行され、ブロック終了後にファイルが自動的にクローズされるため、上記の例は以下のように記述することもできます。
#
# IO#eachを使う例
#
File::open("index.html") {|f|
f.each {|line| print line}
}
#
# IO#getsを使う例
#
File::open("index.html") {|f|
while line = f.gets
print line
end
}
読み込む長さを指定する
IO#readメソッドは引数で読み込む長さを指定することができます。以下の例はfooをオープンし、100バイト読み込んで内容を標準出力へ出力します。
f = open("foo")
print f.read(100)
f.close
ファイルの内容を一度に読み込む
IO#readメソッドは引数で読み込む長さを指定できますが、省略するとファイルの内容を一度に入力することができます。下記の例はfooの内容を一度に読み込み、内容を標準出力へ出力します。
f = open("foo")
print f.read
f.close
1行ずつ読み込みを行う
IO#getsメソッドによりテキストファイルから1行ずつ読み込むことができます。IO#getsメソッドはファイルの終わり(EOF)に達すると、nilを返します。下記の例はfoo.csvから1行ずつ読み込み、行数と総フィールド数をカウントするものです。
lines = fields = 0
open("foo.csv") {|file|
while l = file.gets
lines += 1
fields += l.split(',').size
end
}
puts "Total #{lines} lines, #{fields} fields"
テキストファイルの特定の行を読み込む
IO#readlinesメソッドは全ての行を一度に読み込み、配列へ格納して返却します。以下に、"foo"の100行目を標準出力へ出力する例を示します。
open("foo") {|file|
print file.readlines[99]
}
配列の沿字は0から始まりますので、100行目を参照するために99を指定しています。
一時ファイルを作成する
Tempfileクラスを使用すると一時ファイルの作成、読み込みなどを行うことができます。作成された一時ファイルは、以下のタイミングで削除されます。
- GC(ガベージコレクション)時
- インタプリタ終了時
- Tempfile#closeメソッドを引数に真を指定して呼び出す
TempfileクラスはFileクラスの全てのインスタンスメソッドを使用することができます。なお、Tempfileクラスを使用するためにはrequire 'tempfile'する必要があります。
下記は一時ファイルを使ってfoo.txtというファイルの中身を全て大文字に変換する例です。
require 'tempfile'
temp = Tempfile::new("foobar", "/home/take/tmp")
open("foo.txt") {|f|
f.each {|line|
line.upcase!
temp.puts(line)
}
}
temp.close
temp.open
open("foo.txt", "w") {|f| temp.each {|line| f.puts(line) }}
temp.close(true)
大まかな処理の流れを以下に示します。
- /home/take/tmp配下へ"foobar"をベースネームとした一時ファイルを作成する
- foo.txtの中身を全て大文字に変換して一時ファイルへ出力する
- 一時ファイルを再オープンし、foo.txtの内容を上書きする
- Tempfile#closeを引数trueで呼び出し、作成した一時ファイルを削除する
固定長レコードを読む
固定長レコードを処理する場合、レコードを処理するためのクラスを定義するとプログラムを簡潔に記述することができます。また、クラス定義によりレコード形式が変更になった場合でもトータルとしてのプログラム修正量が減り、保守性が向上するというメリットもあります。
では、例として以下のような固定長レコード形式のファイルを処理するケースを考えてみましょう。
■レコード形式 従業員番号 6桁|氏名 10桁|部課コード 4桁|入社年度 4桁 ■レコード例 100001鈴木一郎太12342001 従業員番号: 100001 氏名: 鈴木一郎太 部課コード: 1234 入社年度: 2001
このレコードを処理するためのRecordクラスを定義してみましょう。
class Record
def initialize(record)
@record = record
end
def empno
@record[0..5]
end
def name
@record[6..15]
end
def deptno
@record[16..19]
end
def year
@record[20..23]
end
end
Recordクラスではインスタンス生成時、渡されたレコードをインスタンス変数@recordに格納します。emono、name、deptno、yearメソッドはそれぞれ@recordの決まった位置からフィールドを切り出し、Stringクラスのインスタンスとして返却します。
では、Recordクラスを使用してfooという従業員情報が格納されているファイルのレコードを、入社年度でソートして標準出力へ出力するスクリプトを作成してみます。
class Record
def initialize(record)
@record = record
end
def empno
@record[0..5]
end
def name
@record[6..15]
end
def deptno
@record[16..19]
end
def year
@record[20..23]
end
end
# (1)
emp = Array::new
# (2) - (4)
open("foo") {|file|
while l = file.gets
r = Record::new(l)
emp << {"empno"=>r.empno, "name"=>r.name, "deptno"=>r.deptno, "year"=>r.year}
end
}
# 入社年度でソート
emp.sort! {|a, b| a["year"] <=> b["year"]}
emp.each {|h|
puts "従業員番号:" + h['empno']
puts "氏名:" + h['name']
puts "部課コード:" + h['deptno']
puts "入社年度:" + h['year']
puts '-' * 20
}
このスクリプトの処理は大まかに以下のようになります。
- 従業員情報を格納するための配列empを確保
- file#getsメソッドでファイルを1行ずつ読み込む
- Recordクラスのインスタンスrを生成
- 1レコード分のHashを作成し、配列に追加
ファイルに書き込む
Fileクラスのメソッドを使って、ファイルをオープンした後、組み込みメソッドのprintまたはputを使って、ファイルにデータを書き込みます。
foo = File.open("foo.txt",'w')
foo.puts 'bar'
foo.close
これで、foo.txtに'bar'と書き込まれます。
ファイルをコピーする
IO::readとIO::writeメソッドを使用してファイルのコピーを行う例を以下に示します。
source = open("foo")
dest = open("bar", "w")
contents = source.read
dest.write(contents)
dest.close
source.close
このスクリプトはファイル"foo"をファイル"bar"へコピーします。イテレータを使って以下のように記述することもできます。
open("foo") {|source|
open("bar", "w") {|dest|
dest.write(source.read)
}
}
Ruby 1.7 以降は fileutils.rb を利用すると以下のように簡単に記述できます。
FileUtils.cp( src, dest, *options )
FileUtils? には他にも便利な機能がたくさんあるので、覚えておくとよいと思います。
フィルタ系のコマンドを作成する
コマンドライン引数または標準入力から渡されるファイルの内容になんらかの処理を施して、標準出力へ出力するような、いわゆるフィルタ系のコマンドを作成する場合、組込み関数のgets、readlineが便利です。
gets、readlineはスクリプトのコマンドライン引数で与えられたファイルから1行読み込み、文字列を返します。もし、コマンドライン引数が与えられなければ標準入力から読み込みます。使用例として、ファイルの内容を標準出力へ出力する簡易版のcatコマンド(cat.rb)を以下に示します。
while s = gets print s end
以下のようにするとfoo.txtとbar.txtの内容を標準出力へ出力します。
% ruby cat.rb foo.txt bar.txt
以下のようにするとnkfコマンドで漢字コードをEUCに変換したfoo.txtの内容を標準入力から受け取り、標準出力へ出力します。
% nkf -euc foo.txt | ruby cat.rb
ファイルタイプを取得する
File::ftypeメソッドを使用する事で、ファイルの種類を取得する事ができます。
File.ftype("/etc/passwd") #=> "file"
File.ftype("/etc" ) #=> "directory"
また文字列ではなく、真偽を返すメソッドも、FileTest?モジュールにより提供されています。
FileTest.exists?("/etc") #=> true
FileTest.directory?("/etc") #=> true
FileTest.file?("/etc") #=> false
これらは、Fileメソッドからでも使用可能です。
File.exists?("/etc") #=> true
File.directory?("/etc") #=> true
File.file?("/etc") #=> false
ファイルの詳細情報を取得する
File::statメソッドを呼び出すとFile::Statオブジェクトが返却されます。File::Statオブジェクトの各種メソッドを呼び出すことで、ファイルの詳細情報を取得することができます。
s = File::stat("/etc/passwd")
p s.dev # デバイス番号
p s.ino # i-node番号
p s.mode # ファイルモード
p s.nlink # ハードリンクの数
p s.uid # ファイル所有者のユーザID
p s.gid # ファイル所有者のグループID
p s.size # ファイルサイズ
p s.blocks # 割り当てられているブロック数
p s.atime # 最終アクセス時刻
p s.mtime # 最終更新時刻
p s.ftype # ファイルタイプ
ファイルモードを変更する
File::chmodメソッドを使うとファイルモードを変更することができます。
s = File::stat("/home/take/public_html/index.html")
p "%o" % s.mode #=> "100664"
File::chmod(0100666, "/home/take/public_html/index.html")
p "%o" % s.mode #=> "100666"
ファイルの所有者とグループを変更する
File::chownメソッドを使うとファイルの所有者とグループを変更することができます。ただし、実際に変更できるのはスーパーユーザだけです。所有者だけを変更したい場合は、グループにnilまたは-1を指定します。同様に、グループだけを変更したい場合は所有者にnilまたは-1を指定します。
# 所有者を100、グループを101に変更 File::chown(100, 101, "/home/httpd/html/index.html") # 所有者を101に変更 File::chown(101, nil, "/home/httpd/html/index.html") # グループを102に変更 File::chown(-1, 102, "/home/httpd/html/index.html")
ファイルの最終アクセス時刻と最終更新日時を変更する
File::utimeメソッドを使うとファイルの最終アクセス日と最終更新日時を変更することができます。日時はTimeクラスのインスタンスで指定します。
# index.htmlの最終アクセス日を2001-5-22 23:59:59(JST)、最終更新日を2001-5-1 00:00:00(JST)に変更する
File::utime(Time.local(2001, 5, 22, 23, 59, 59), Time.local(2001, 5, 1, 0, 0, 0), "index.html")
p File::atime("index.html") #=> "Tue May 22 23:59:59 JST 2001"
p File::mtime("index.html") #=> "Tue May 01 00:00:00 JST 2001"
相対パスから絶対パスを求める
File::expand_pathメソッドを使うと相対パスを絶対パスに変換することができます。第2引数を省略した場合は、カレントディレクトリを基準にします。
p File::expand_path('baz', '/foo/bar') #=> /foo/bar/baz
# 第2引数を省略した場合
Dir::chdir('/hoge/piyo')
p File::expand_path('.') #=> /hoge/piyo
p File::expand_path('..') #=> /hoge
p File::expand_path('fuga') #=> /hoge/piyo/fuga
ファイル名解析
ファイルパスからディレクトリパスを抜き出す
File::dirnameを使うと、ファイルパスからディレクトリパスを抜き出すことができます。
File::dirname('/hoge/piyo') #=> /hoge
#末尾に/が付いていても無視します。
File::dirname('/hoge/piyo/') #=> /hoge
#Windowsの\区切りにも対応しています。
File::dirname('C:\\hoge\\piyo') #=> C:\hoge
ファイルパスからファイル名を抜き出す
File::basenameを使うと、ファイルパスからファイル名を抜き出すことができます。
File::basename('/hoge/piyo') #=> piyo
#指定した拡張子を無視することもできます。
File::basename('/hoge/piyo.c', '.c') #=> piyo
File::basename('/hoge/piyo.cpp', '.c') #=> piyo.cpp
#拡張子にはワイルドカードも指定できます。
File::basename('/hoge/piyo.c', '.*') #=> piyo
File::basename('/hoge/piyo.cpp', '.*') #=> piyo
#ワイルドカードは末尾からの最短一致でチェックしているようです。
File::basename('/hoge/piyo.cpp', 'p*') #=> piyo.cp
File::basename('/hoge/piyo.cpp', 'c*') #=> piyo.
#ワイルドカードの前に2文字以上あると無視されるようです。
File::basename('/hoge/piyo.cpp', '.c*') #=> piyo.cpp
パス名とファイル名を一度に抜き出す
File::splitを使うと、ファイルパスからディレクトリパスとファイル名を一度に抜き出すことができます。
File::split('/hoge/piyo') #=> ["/hoge", "piyo"]
dir, file = File::split('/hoge/piyo')
p dir #=> "/hoge"
p file #=> "piyo"
拡張子を調べる
File::extnameを使うと、ファイルパスから拡張子を抜き出すことができます。
File::extname('/hoge/piyo.c') #=> .c
#ファイル名先頭の.は拡張子と見なされません。
File::extname('/hoge/.piyo') #=> ""
File::extname('/hoge/.piyo.c') #=> .c
Ruby逆引きハンドブック(るびきち)
Keyword(s):
References:[SideMenu] [逆引きRuby]