Ruby TIPS

Ruby TIPS

ファイルに文字列を書き込む(出力する)には?

2017年7月3日

テキストファイルに文字列を書き込むための基本を解説。新規書き込みと追加の方法を確認した後、任意の位置から書き込む方法や読み書き両用モードでファイルを利用する方法を説明する。

ローグ・インターナショナル 羽山 博
  • このエントリーをはてなブックマークに追加

ファイル入出力の基本 − 出力編

 ファイルから文字列を読み込む方法については、「ファイルから文字列を読み込む(入力する)には?(基本編)」「ファイルから1文字ずつ読み込む(入力する)には?」と「ファイルから1行ごと/段落ごと読み込む(入力する)には?」の3回にわたって詳しく解説した。エンコーディングの指定などについてもそちらで説明したので、今回はファイルに文字列を書き込む(出力する)方法に絞って見ていく。

 なお、ファイルへの書き込み時には、複数のプログラムやスレッドからのアクセスによる競合の問題に対処する必要がある。そのような排他制御の方法については、次回の記事で解説することとする。

ファイルに新規書き込みする ― writeモード

 ファイルへの出力方法には、大きく分けて新規書き込み追加書き込みの2つのモードがあり、ファイルのオープン時に、openメソッドにいずれのモードを使うかを指定する。新規書き込みの場合は"w"を指定する。この場合、それまでファイルに保存されていた内容は空にされ、ファイルの先頭から文字列が書き込まれる。

sample001.rb
File.open("foodstuff.txt", mode = "w"){|f|
  f.write("strong flour\t420\n")  # ファイルに書き込む
}
リスト1.1 ファイルを新規書き込みモードで開き、1行書き込むプログラム

Fileクラスのopenメソッドの第1引数にファイルのパス名を指定し、第2引数にモードとして"w"を指定する。writeメソッドを使えばファイルに文字列を出力できる。

 実行結果は以下の通りである。ファイルがすでに存在しても、それまでの内容はクリアされ、新規書き込みとなる。

ターミナル
$ more foodstuff.txt 
weak flour 340

$ ruby sample001.rb

$ more foodstuff.txt 
strong flour 420
実行例1.1 ファイルに1行の文字列を新規書き込みする*1

プログラムを実行する前に、moreコマンドを使ってitem001.txtファイルの内容を確認しておく。プログラムを実行すると、別の文字列が書き込まれる。プログラムの実行後にファイルの内容を確認すると、すでに入力されていた文字列はクリアされ、新しく文字列が書き込まれたことが分かる。

 ファイルが存在しない場合、ファイルは新規作成される。その場合、パーミッションをopenメソッドの第3引数に8進数で指定できる。例えば、644モード(rw-r--r--)であれば、File.open("item001.txt", mode = "w", perm=0644)とすればよい。なお、Rubyでは0で始まる数値は8進数のリテラルと見なされる。

  • *1 HTMLで記述したタブ文字( )は、Webページ上ではスペース文字( )と同じ空白1文字として表現されてしまう。本稿では見分けが付くように、タブ文字には黄色い背景色を指定している。以下同様。

ファイルに追加書き込みする ― appendモード

 ファイルに追加書き込みする場合は、openメソッドの第2引数に"a"を指定する。この場合、それまでファイルに保存されていた内容の後に文字列が書き込まれる。

sample002.rb
File.open("foodstuff.txt", mode = "a"){|f|
  f.write("strong flour\t420\n")  # ファイルに書き込む
}
リスト1.2 ファイルを追加書き込みモードで開き、1行書き込むプログラム

リスト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
実行例1.2 ファイルに1行の文字列を追加書き込みする

プログラムを実行すると、すでに入力されていた文字列の後に文字列が追加書き込まれたことが分かる。

ファイルの任意の位置に文字列を書き込む ― readwriteモード、seekメソッド

 ファイルの末尾ではなく、任意の位置に文字列を書き込みたい場合には、openメソッドの第2引数に"r+"を指定し、seekメソッドを使って書き込む位置を指定する。"r+"モードでは、ファイルが読み書きモードで開かれ、読み書きの位置がファイルの先頭にセットされる。seekメソッドの引数にはバイト単位でのオフセットと、基準となる位置を指定する。

sample003.rb
File.open("foodstuff.txt", mode = "r+"){|f|
  f.seek(12, IO::SEEK_SET)
  f.write("62")
}
リスト1.3 ファイルの任意の位置に読み書きの位置を変えるプログラム

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.3 ファイルに含まれる文字列を書き換える

ファイルの1行目にはweak flourに続いてタブ文字、さらに340、最後に改行文字(LF)という15文字が入力されている。オフセットには先頭を0とした距離を指定するので、12を指定すれば13バイト目からが書き換えられることになる。1行目の最後の方の4062に書き換えられていることが分かる。

 ファイルの何行目かを書き換えたい場合には、seekメソッドを使わなくても、readlineメソッドを何回か実行して、行を空読みすればよい。readlineメソッドを使えば、マルチバイト文字が含まれるファイルでも正確に行が空読みできる。ただし、writeメソッドは行や単語を書き換えるメソッドではないので、書き換えたい部分と書き込んだ文字列の長さが異なると期待した結果が得られないので注意が必要である。なお、ファイルの内容を行や項目単位で取り扱いたいときには、CSVクラスのメソッドを利用すればよい(CSVクラスの利用方法についてはも回を改めて紹介したい)。

sample004.rb
File.open("foodstuff.txt", mode = "r+"){|f|
  f.readline  # 1行空読みする
  f.write("enriched")
}
リスト1.4 1行空読みして、2行目から文字列を出力するプログラム

readlineメソッドを使って行を空読みすれば、何行かスキップできる。この例であれば、2行目の先頭に読み書きの位置を変更して、"enriched"を書き込む。

ターミナル
$ more foodstuff.txt 
weak flour 362
strong flour 420

$ ruby sample004.rb

$ more foodstuff.txt 
weak flour 362
enrichedlour 420
実行例1.4 2行目の先頭から何文字かを書き換えた

writeメソッドは文字列を出力するメソッドなので、単語や行を書き換えるわけではない。strong flourenriched flourに書き換えられるのではなく、enrichedlourになってしまった。

ファイルに書き込んでから読み出す、追加と読み出しを行う ― "w+"モード、"a+"モード

 ファイルを読み書き両用で開くには、他にも"w+""a+"が指定できる。

 "w+"では、ファイルがすでに存在する場合、内容が空にされる。"w"との違いは、書き込んだ内容を読み出せるという点である("w"を指定するとreadlineなどのメソッドは使えない)(リスト1.5、実行例1.5)。

 "a+"では、ファイルの読み出しは先頭から行われるが、データの追加は常にファイルの末尾になる。この場合、seekメソッドを使ってもやはりファイルの末尾に追加されることに注意(リスト1.6、実行例1.6)。

 2つ続けて見ておこう。

sample005.rb
File.open("foodstuff.txt", mode = "w+"){|f|
  f.write("cornstarch\t210\n")
  f.seek(0, IO::SEEK_SET)
  puts f.readline
}
リスト1.5 ファイルを新規読み書き両方モードで開き、1行書き込んだ後、読み出すプログラム

ファイルに新規書き込みし、書き込んだ文字列を読み出して表示するプログラム。writeメソッドによって文字列を書き込んだ後は、読み書きの位置がファイルの末尾に達しているので、seekメソッドで先頭に戻してからreadlineメソッドで読み出す。

ターミナル
$ more foodstuff.txt 
weak flour 340
strong flour 420

$ ruby sample005.rb
cornstarch 210
実行例1.5 ファイルに書き込まれた文字列を確認する

 "w+"でファイルが開かれたので、それまでの内容はクリアされる。writeメソッドによって文字列を書き込んだ後、ファイルに書き込まれた行を読み出して表示した。

sample006.rb
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")  # 末尾に追加される
}
リスト1.6 ファイルを追加読み書き両方モードで開き、数行書き込むプログラム

最初の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
実行例1.6 追加出力の場合は常にファイルの末尾にデータが追加される

"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回分(第19回~第23回)のみ表示しています。
 連載の全タイトルを参照するには、[この記事の連載 INDEX]を参照してください。

19. ファイルから文字列を読み込む(入力する)には?(基本編)

テキストファイルから文字列を読み込むための基礎を解説。ファイル操作をブロックで記述する方法や、ファイルを開く際に「テキスト読み出し専用モード」でアクセスしたり文字コードを指定したりする方法、BOM付きファイルを処理する方法を説明する。

20. ファイルから1文字ずつ読み込む(入力する)には?

Rubyでテキストファイルから文字列を読み込むための方法として、ファイルから1文字単位で文字を取得する方法と、ファイル内の全テキスト内容を先頭から1文字ずつループ処理する方法を説明する。

21. ファイルから1行/段落ごと読み込む(入力する)には?

Rubyでテキストファイルから文字列を読み込むための方法として、ファイル内の全テキスト内容を先頭から1行単位ずつもしくは1段落ずつループ処理する方法と、ファイルから読み込んだ全ての行を配列として返す方法を説明する。

22. 【現在、表示中】≫ ファイルに文字列を書き込む(出力する)には?

テキストファイルに文字列を書き込むための基本を解説。新規書き込みと追加の方法を確認した後、任意の位置から書き込む方法や読み書き両用モードでファイルを利用する方法を説明する。

23. ファイルの排他制御を行うには? その際のデッドロック問題とは?

1つのファイルに複数のプログラムから同時アクセスすると、上書きによりデータが消失する可能性がある。これを回避するために排他制御を行う方法と、その際に問題となるデッドロックを回避する方法について説明する。

サイトからのお知らせ

Twitterでつぶやこう!


Build Insider賛同企業・団体

Build Insiderは、以下の企業・団体の支援を受けて活動しています(募集概要)。

ゴールドレベル

  • グレープシティ株式会社
  • 日本マイクロソフト株式会社