Help us understand the problem. What is going on with this article?

[Ruby入門] 15. ファイルとディレクトリを操作する

More than 3 years have passed since last update.

>> 連載の目次は こちら!

今回は、ファイルとディレクトリを扱う方法について整理する。
ファイルはFileクラス、ディレクトリはDirクラスで扱う。
また、FileUtilsモジュールにて、UNIXコマンドライクにさまざまな操作が可能。

※ここでは例文をすっきりさせるため例外処理はしていない。実際にはメソッドによって例外を発生するので、適宜対応する。

■ Fileクラスでファイルを操作する

@see https://docs.ruby-lang.org/ja/latest/class/File.html

・Fileクラスは IOクラスを継承しているため、共通の入出力IFで操作できる

・Fileクラスは Enumerableモジュールを積んでるので、ループ系の処理ができる

● ファイルのオープンと基本的な読み書き

# 手動で開く、1行ずつ読む、手動で閉じる
file = File.open("doughnut.csv", "r:UTF-8")
file.each do |line|
    puts "商品名:%s(価格:%d円)" % line.split(",")
end
file.close
  # 商品名:プレーン(価格:129円)
  # 商品名:チョコレート(価格:108円)
  # 商品名:エンゼルフレンチ(価格:140円)

# ブロックで処理し、自動で閉じる
File.open("doughnut.csv", "r:UTF-8") do |file|
  file.each do |line|
    puts "商品名:%s(価格:%d円)" % line.split(",")
  end
end
  # 商品名:プレーン(価格:129円)
  # 商品名:チョコレート(価格:108円)
  # 商品名:エンゼルフレンチ(価格:140円)

# Kernelモジュールにもopenメソッドがあるので、関数のようにも書ける
open("doughnut.csv", "r:UTF-8") do |file|
  file.each do |line|
    puts "商品名:%s(価格:%d円)" % line.split(",")
  end
end
  # 商品名:プレーン(価格:129円)
  # 商品名:チョコレート(価格:108円)
  # 商品名:エンゼルフレンチ(価格:140円)

# ファイルを新規作成して書き込む
File.open("cafe.csv", "w", 0644) do |file|
   file.puts("カフェオレ,380")
end

# 追記モードで開いて書き込む
File.open("cafe.csv", "a") do |file|
    file.puts("ココア,400")
end

# ファイル全体を一度に読み込む
File.open("cafe.csv", "r:UTF-8") do |file|
    puts file.read
end
  # カフェオレ,380
  # ココア,400

・openメソッドにブロックを渡して処理した場合は、ブロック終了時に自動でcloseしてくれる

・openメソッドの第2引数はモード
r 読み込み専用でオープン。デフォルト。
w 書き込み専用でオープン。
a 追記モード
・上記は、それぞれ「+」を付けると読み書き両用でオープンする
・「w」「w+」は、ファイルが存在する場合に空にするので注意。新規作成用だな。
・「a」は、ファイルがなければ新規作成される。
・モードの後ろに「:UTF-8」のように文字エンコーディングを指定できる。今回はVSCode上のデバッグで「-Ku」が効いていなかったため明示した。

・openメソッドの第3引数には、ファイル新規生成の場合にパーミッションを指定できる。デフォルトは「0666」

● Fileクラスのその他のメソッド

・絶対パスの取得
p File.absolute_path("doughnut.csv") 
   # "/Users/miro/Documents/RubyProjects/MyRuby/doughnut.csv"

# 第2引数に基準パスを指定
p File.absolute_path("../..", "/Users/miro/Documents/RubyProjects") 
   # "/Users/miro"
・パスから、本体の名前(ファイル名/ディレクトリ名の部分)を取得
# ファイル名は、存在しなくても良いらしい。単に文字列として扱っている

p File.basename("/path/to/doughnut.csv"); # "doughnut.csv"

# サフィックスを除去して取得
p File.basename("./doughnut.csv", ".csv"); # "doughnut"
・パスから、本体の名前より前のパスの部分を取得
# ファイル名は、存在しなくても良いらしい。単に文字列として扱っている
p File.dirname("/path/to/mytest.txt")   # "/path/to"
p File.dirname("/Users/miro/Documents") # "/Users/miro"
p File.dirname("list.txt")              # "."
・パスから、本体の名前と、パスの部分を分割して配列で取得
# ファイル名は、存在しなくても良いらしい。単に文字列として扱っている
p File.split("/path/to/mytest.txt")   # ["/path/to", "mytest.txt"]
p File.split("list.txt")              # [".", "list.txt"]
・ファイルの情報を格納した「File::Stat」クラスのオブジェクトを取得
stat = File.stat("/Users/miro/Documents/RubyProjects/MyRuby/doughnut.csv")
p stat.file? # true
p stat.atime # 2017-05-03 11:19:50 +0900
# など。File::Stat の詳細は、
#   https://docs.ruby-lang.org/ja/latest/class/File=3a=3aStat.html
・パーミッションを変更
File.chmod(0666, "./doughnut.csv")
   # -rw-rw-rw-@ 1 miro  staff     69  5  2 21:39 doughnut.csv
・ファイルの削除
File.delete("./doughnut.csv")
File.unlink("./cafe.csv")
・ファイルか?ディレクトリか?存在するか? など
p File.file?("./list.txt")       # true
p File.directory?("./list.txt")  # false
p File.exist?("./list.txt")      # true  「exists?」は非推奨なので注意
p File.symlink?("./list.txt")    # false
・ファイル名のパターンマッチング
p File.fnmatch("list*", "list.txt")     # true
p File.fnmatch("*list*", "./list.txt")  # false
p File.fnmatch("*list*", "./list.txt",         File::FNM_DOTMATCH) # true
p File.fnmatch("{my,your}file*", "myfile.txt", File::FNM_EXTGLOB)  # true
  # 第2引数のファイル名は、存在しなくても良いらしい。単に文字列としてマッチングしている
  # ワイルドカードとして `*', `?', `[]', `{}' が使用できる
  # 第3引数でオプションを指定できる
  #   FNM_NOESCAPE  エスケープ文字 \ を普通の文字とみなす
  #   FNM_PATHNAME  ワイルドカードが / にマッチしなくなる
  #   FNM_CASEFOLD  大小文字を区別しない
  #   FNM_DOTMATCH  ワイルドカードが 先頭の「.」にマッチするようになる
  #   FNM_EXTGLOB   {} 内のカンマで区切られたいずれかの文字列でマッチングできる
・拡張子を取得したい
p File.extname("foo/foo.tar.gz")  # ".gz"
p File.extname("path/to/dir")     # ""
  # ファイル名は、存在しなくても良いらしい。単に文字列としてマッチングしている
・ファイル名の変更
File.rename("list.txt", "renamed.txt")
・ファイルのサイズを取得
p File.size("/Users/miro/.rvm/rubies/ruby-2.4.0/bin/ruby") # 8960

■ Dirクラスでディレクトリを操作する

@see https://docs.ruby-lang.org/ja/latest/class/Dir.html

# ディレクトリの作成
Dir::mkdir("tmp", 0700) # パーミッションは省略したら0777
Dir::mkdir("tmp")       # 既に存在したら Uncaught exception: File exists 〜

# 空のディレクトリの削除
Dir::rmdir("tmp")
Dir::delete("tmp")    # 存在しなかったら、
Dir::unlink("tmp")    #    Uncaught exception: No such file or directory 〜

# ディレクトリの中身の一覧を取得
p Dir::entries("./")
  # [".", "..", ".vscode", "cafe.csv", "doughnut.csv", "mytest.rb"]

# 存在するか
p Dir.exist?("/path/to/none") # false

# 現在のユーザのホームディレクトリを取得
p Dir.home  # "/Users/miro"

# カレントディレクトリを取得
p Dir.pwd # "/Users/miro/Documents/RubyProjects/MyRuby"

# 指定のディレクトリへ移動
Dir.chdir("/Users/miro/Temp/"); p Dir.pwd # "/Users/miro/Temp"

# パターンマッチングでディレクトリ内のエントリ一覧を取得
items = Dir.glob("*.csv"); p items
  # ["baibai.csv", "cafe.csv", "doughnut.csv"]
  # 第2引数でパターンマッチングのオプションを指定できる。
  # オプションの種類は、前述の「ファイル名のパターンマッチング」の fnmatch メソッドと同じ

# glob にブロックを渡して処理する
Dir.glob("*.csv") {|fname|
  puts fname
}

■ FileUtilsモジュールの便利メソッド

@see https://docs.ruby-lang.org/ja/latest/class/FileUtils.html

FileUtilsのメソッドは、UNIXコマンドに対応しているのでわかりやすい

require 'fileutils'

# 空ファイルの作成
FileUtils.touch("list.txt")

# ファイルのコピー
FileUtils.cp("cafe.csv", "cafe2.csv")

# ファイルの削除
FileUtils.rm("cafe2.csv")

# ファイルの強制削除
FileUtils.rm_f("cafe.csv")

# ディレクトリの作成
FileUtils.mkdir("test");

# 複階層のディレクトリの作成
FileUtils.mkdir_p("./test/path/to/mydir")

# ディレクトリのコピー
FileUtils.cp_r("./test/path/to/mydir", "./test/path/to/mydir2")

# ディレクトリの削除
FileUtils.rm_r("./test/path/to")

# ディレクトリの強制削除
FileUtils.rm_rf("./test")

# カレントディレクトリを取得
p FileUtils.pwd # "/Users/miro/Temp"

# 指定のディレクトリへ移動
FileUtils.cd("/Users/miro/Documents/RubyProjects/MyRuby")

# パーミッションを変更
FileUtils.chmod(0666, "./cafe.csv")
  # -rw-rw-rw-  1 miro  staff     34  5  3 11:35 cafe.csv
  #「chmod_R」なら、ディレクトリ以下を再帰的にchmodできる

# オーナーを変更する
FileUtils.chown("nobody", "nogroup", "./cafe.csv")
  # 第1引数は、ユーザ名かuid、第2引数はグループ名かgid
  # それぞれ、nil または -1 を渡すと変更されない
  # 第3引数の対象ファイルを複数指定したい場合は、配列で指定可能
  #「chown_R」なら、ディレクトリ以下を再帰的にchownできる

# ファイルの内容が同じか?
FileUtils.cp("doughnut.csv", "copy.txt")
p FileUtils.compare_file("doughnut.csv", "copy.txt") # true
  # cmp, compare_file, identical? は同機能のメソッド

# シンボリックリンクの作成
FileUtils.ln_s("doughnut.txt", "sym_to_doughnut")
  # lrwxr-xr-x  1 miro  staff     12  5  3 11:55 sym_to_doughnut -> doughnut.txt
  # symlink メソッドも同機能
prgseek
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした