Ruby TIPS
ファイルに文字列を書き込む(出力する)には?
テキストファイルに文字列を書き込むための基本を解説。新規書き込みと追加の方法を確認した後、任意の位置から書き込む方法や読み書き両用モードでファイルを利用する方法を説明する。
ファイル入出力の基本 − 出力編
ファイルから文字列を読み込む方法については、「ファイルから文字列を読み込む(入力する)には?(基本編)」「ファイルから1文字ずつ読み込む(入力する)には?」と「ファイルから1行ごと/段落ごと読み込む(入力する)には?」の3回にわたって詳しく解説した。エンコーディングの指定などについてもそちらで説明したので、今回はファイルに文字列を書き込む(出力する)方法に絞って見ていく。
なお、ファイルへの書き込み時には、複数のプログラムやスレッドからのアクセスによる競合の問題に対処する必要がある。そのような排他制御の方法については、次回の記事で解説することとする。
ファイルに新規書き込みする ― writeモード
ファイルへの出力方法には、大きく分けて新規書き込みと追加書き込みの2つのモードがあり、ファイルのオープン時に、open
メソッドにいずれのモードを使うかを指定する。新規書き込みの場合は"w"を指定する。この場合、それまでファイルに保存されていた内容は空にされ、ファイルの先頭から文字列が書き込まれる。
File.open("foodstuff.txt", mode = "w"){|f|
f.write("strong flour\t420\n") # ファイルに書き込む
}
|
File
クラスのopen
メソッドの第1引数にファイルのパス名を指定し、第2引数にモードとして"w"を指定する。write
メソッドを使えばファイルに文字列を出力できる。
実行結果は以下の通りである。ファイルがすでに存在しても、それまでの内容はクリアされ、新規書き込みとなる。
$ more foodstuff.txt
weak flour 340
$ ruby sample001.rb
$ more foodstuff.txt
strong flour 420
|
プログラムを実行する前に、more
コマンドを使ってitem001.txt
ファイルの内容を確認しておく。プログラムを実行すると、別の文字列が書き込まれる。プログラムの実行後にファイルの内容を確認すると、すでに入力されていた文字列はクリアされ、新しく文字列が書き込まれたことが分かる。
ファイルが存在しない場合、ファイルは新規作成される。その場合、パーミッションをopen
メソッドの第3引数に8進数で指定できる。例えば、644モード(rw-r--r--)であれば、File.open("item001.txt", mode = "w", perm=0644)
とすればよい。なお、Ruby
では0で始まる数値は8進数のリテラルと見なされる。
- *1 HTMLで記述したタブ文字(
ファイルに追加書き込みする ― appendモード
ファイルに追加書き込みする場合は、openメソッドの第2引数に"a"を指定する。この場合、それまでファイルに保存されていた内容の後に文字列が書き込まれる。
File.open("foodstuff.txt", mode = "a"){|f|
f.write("strong flour\t420\n") # ファイルに書き込む
}
|
リスト1.1との違いは、Fileクラスのopenメソッドの第2引数にモードが"a"になっている点だけである。同じくwriteメソッドを使ってファイルに文字列を出力する。
実行例1.1と同じ(最初の状態の)ファイルがあるとすると、以下のような結果になる。
$ more foodstuff.txt
weak flour 340
$ ruby sample002.rb
$ more foodstuff.txt
weak flour 340
strong flour 420
|
プログラムを実行すると、すでに入力されていた文字列の後に文字列が追加書き込まれたことが分かる。
ファイルの任意の位置に文字列を書き込む ― readwriteモード、seekメソッド
ファイルの末尾ではなく、任意の位置に文字列を書き込みたい場合には、openメソッドの第2引数に"r+"を指定し、seek
メソッドを使って書き込む位置を指定する。"r+"モードでは、ファイルが読み書きモードで開かれ、読み書きの位置がファイルの先頭にセットされる。seek
メソッドの引数にはバイト単位でのオフセットと、基準となる位置を指定する。
File.open("foodstuff.txt", mode = "r+"){|f|
f.seek(12, IO::SEEK_SET)
f.write("62")
}
|
seek
メソッドの引数に、オフセットとして12を、基準となる位置にIO:SEEK_SET(先頭から)を指定し、読み書きの位置を進める。write
メソッドを使って2文字分書き込んでいるので、13バイト目から2文字が書き換えられることになる。
基準となる位置には、IO:SEEKSET(先頭から)、IO::SEEK_CUR(現在の位置から)、IO::SEEK_END(末尾から)などが指定できる。なお、マルチバイト文字が含まれるファイルの場合、オフセットに文字の途中のバイト位置を指定しないように注意する必要がある。
$ more foodstuff.txt
weak flour 340
strong flour 420
$ ruby sample003.rb
$ more foodstuff.txt
weak flour 362
strong flour 420
|
ファイルの1行目にはweak flourに続いてタブ文字、さらに340、最後に改行文字(LF)という15文字が入力されている。オフセットには先頭を0とした距離を指定するので、12を指定すれば13バイト目からが書き換えられることになる。1行目の最後の方の40が62に書き換えられていることが分かる。
ファイルの何行目かを書き換えたい場合には、seek
メソッドを使わなくても、readline
メソッドを何回か実行して、行を空読みすればよい。readline
メソッドを使えば、マルチバイト文字が含まれるファイルでも正確に行が空読みできる。ただし、write
メソッドは行や単語を書き換えるメソッドではないので、書き換えたい部分と書き込んだ文字列の長さが異なると期待した結果が得られないので注意が必要である。なお、ファイルの内容を行や項目単位で取り扱いたいときには、CSV
クラスのメソッドを利用すればよい(CSV
クラスの利用方法についてはも回を改めて紹介したい)。
File.open("foodstuff.txt", mode = "r+"){|f|
f.readline # 1行空読みする
f.write("enriched")
}
|
readline
メソッドを使って行を空読みすれば、何行かスキップできる。この例であれば、2行目の先頭に読み書きの位置を変更して、"enriched"を書き込む。
$ more foodstuff.txt
weak flour 362
strong flour 420
$ ruby sample004.rb
$ more foodstuff.txt
weak flour 362
enrichedlour 420
|
write
メソッドは文字列を出力するメソッドなので、単語や行を書き換えるわけではない。strong flourがenriched flourに書き換えられるのではなく、enrichedlourになってしまった。
ファイルに書き込んでから読み出す、追加と読み出しを行う ― "w+"モード、"a+"モード
ファイルを読み書き両用で開くには、他にも"w+"や"a+"が指定できる。
"w+"では、ファイルがすでに存在する場合、内容が空にされる。"w"との違いは、書き込んだ内容を読み出せるという点である("w"を指定するとreadline
などのメソッドは使えない)(リスト1.5、実行例1.5)。
"a+"では、ファイルの読み出しは先頭から行われるが、データの追加は常にファイルの末尾になる。この場合、seek
メソッドを使ってもやはりファイルの末尾に追加されることに注意(リスト1.6、実行例1.6)。
2つ続けて見ておこう。
File.open("foodstuff.txt", mode = "w+"){|f|
f.write("cornstarch\t210\n")
f.seek(0, IO::SEEK_SET)
puts f.readline
}
|
ファイルに新規書き込みし、書き込んだ文字列を読み出して表示するプログラム。write
メソッドによって文字列を書き込んだ後は、読み書きの位置がファイルの末尾に達しているので、seek
メソッドで先頭に戻してからreadline
メソッドで読み出す。
$ more foodstuff.txt
weak flour 340
strong flour 420
$ ruby sample005.rb
cornstarch 210
|
"w+"でファイルが開かれたので、それまでの内容はクリアされる。write
メソッドによって文字列を書き込んだ後、ファイルに書き込まれた行を読み出して表示した。
File.open("foodstuff.txt", mode = "a+"){|f|
f.write("cornstarch\t210\n") # 末尾に追加される
f.seek(0 IO::SEEK_SET)
puts f.readline
f.seek(0, IO::SEEK_SET)
f.write("fresh cream\t330\n") # 末尾に追加される
}
|
最初のwrite
メソッドでファイルの末尾に1行書き込む。次に、seek
メソッドで読み書きの位置を先頭に戻す。readline
メソッドで読み出した行(ファイルの先頭行)を表示した後、もう一度、seek
メソッドで読み書きの位置を先頭に戻す。最後のwrite
メソッドではseek
メソッドによる位置の変更は無視され、ファイルの最後に行が追加される。
$ more foodstuff.txt
weak flour 340
strong flour 420
$ ruby sample006.rb
weak flour 340
$ more foodstuff.txt
weak flour 340
strong flour 420
cornstarch 210
fresh cream 330
|
"a+"でファイルが開かれたので、write
メソッドによってファイルの末尾に文字列が書き込まれる。seek
メソッドで読み書きの位置を変えても、必ずファイルの末尾に追加されることに注意。読み出しはseek
メソッドによって決められた位置や直前のメソッドによって移動した位置から行われる。
まとめ
ファイルに文字列を書き込むにはFile
クラスのopen
メソッドの第2引数に"w","a","r+","w+","a+"のいずれかを指定してファイルを開く。"w"は新規書き込みモード、"a"は追加書き込みモードになる。また、"+"が付いたモードでは読み出しもできる。"r+"の場合ファイルの内容はクリアされず、読み書きの位置はファイルの先頭に位置づけられる。
API:IOクラス|Fileクラス カテゴリ:組み込みライブラリ
API:openメソッド|closeメソッド カテゴリ:Fileクラス
API:writeメソッド|seekメソッド|readlineメソッド カテゴリ:IOクラス
※以下では、本稿の前後を合わせて5回分(第20回~第24回)のみ表示しています。
連載の全タイトルを参照するには、[この記事の連載 INDEX]を参照してください。
20. ファイルから1文字ずつ読み込む(入力する)には?
Rubyでテキストファイルから文字列を読み込むための方法として、ファイルから1文字単位で文字を取得する方法と、ファイル内の全テキスト内容を先頭から1文字ずつループ処理する方法を説明する。
21. ファイルから1行/段落ごと読み込む(入力する)には?
Rubyでテキストファイルから文字列を読み込むための方法として、ファイル内の全テキスト内容を先頭から1行単位ずつもしくは1段落ずつループ処理する方法と、ファイルから読み込んだ全ての行を配列として返す方法を説明する。
22. 【現在、表示中】≫ ファイルに文字列を書き込む(出力する)には?
テキストファイルに文字列を書き込むための基本を解説。新規書き込みと追加の方法を確認した後、任意の位置から書き込む方法や読み書き両用モードでファイルを利用する方法を説明する。
23. ファイルの排他制御を行うには? その際のデッドロック問題とは?
1つのファイルに複数のプログラムから同時アクセスすると、上書きによりデータが消失する可能性がある。これを回避するために排他制御を行う方法と、その際に問題となるデッドロックを回避する方法について説明する。