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

[Ruby]FileUtils.cpの内部処理を調べる

目的

  • 以下を実行した際の内部処理を調べる。
sample.rb
require 'fileutils'
FileUtils.cp 'src_file', 'dest_file'

cpメソッド

  • まず、FileUtils.cpを実行すると、fileutils.rbのcpメソッドが呼ばれる。
fileutil_cp.rb
346: def cp(src, dest, preserve: nil, noop: nil, verbose: nil)
347:   fu_output_message "cp#{preserve ? ' -p' : ''} #{[src,dest].flatten.join ' '}" if verbose
348:   return if noop
349:   fu_each_src_dest(src, dest) do |s, d|
350:     copy_file s, d, preserve
351:   end
352: end
  • cpメソッドの引数は以下の通り。
引数 説明
src コピー元のファイル、配列でもOK
dest コピー後ファイル、ディレクトリでもOK
preserve ファイルの属性を保持するかどうか。何も指定しなければ保持しない。
noop ヌープ。ヌープにnil以外を指定すれば、何も実行されない。
verbose nil以外を指定すれば「cp -p src dest」みたいにコピーコマンド調に出力してくれる。
  • なお、preserve, noop, verboseはhashで渡す必要あり。

fu_output_messageメソッド

  • verboseにnil以外を渡した場合はfu_output_messageメソッドが実行される。
  • なので、fu_output_messageメソッドを見てみる。
fileutil_cp.rb
1481:   @fileutils_output = $stderr
1482:   @fileutils_label  = ''
1483:
1484:   def fu_output_message(msg)   #:nodoc:
1485:     @fileutils_output ||= $stderr
1486:     @fileutils_label  ||= ''
1487:     @fileutils_output.puts @fileutils_label + msg
1488:   end
1489:   private_module_function :fu_output_message
  • 1481行目で@fileutils_outputに標準エラーを代入する。
  • 1482行目で@fileutils_labelに空文字を代入する。
  • さらに、fu_output_messageメソッドの中で@fileutils_outputが偽または未定義なら標準エラーを代入する。(今回はエラーは出ていないので、何もなし)
  • @fileutils_labelも同様に偽または未定義なら空文字を代入する。
  • 最後に@fileutils_outputのputsメソッドを実行する。putsメソッドの引数としては@fileutils_labelとmsgとなる。
  • 標準エラーはIOクラスなので、putsメソッドを持っている。

cpメソッド

  • cpメソッドに戻ってヌープがnil以外ならその場でリターン
  • 次はfu_each_src_destメソッドを実行。
fileutil_cp.rb
346: def cp(src, dest, preserve: nil, noop: nil, verbose: nil)
347:   fu_output_message "cp#{preserve ? ' -p' : ''} #{[src,dest].flatten.join ' '}" if verbose
348:   return if noop
349:   fu_each_src_dest(src, dest) do |s, d|
350:     copy_file s, d, preserve
351:   end
352: end

fu_each_src_destメソッド

  • fu_each_src_destメソッドは引数がsrc,dest
  • その中でfu_each_src_dest0という似た名前のメソッドを呼ぶ。
  • その前にfu_same?メソッドでsrc, destが同じファイルがどうかを判断している。
  • src, destが同じファイルならArgumentErrorをraiseしている。
fileutil_cp.rb
1451: def fu_each_src_dest(src, dest)   #:nodoc:
1452:   fu_each_src_dest0(src, dest) do |s, d|
1453:     raise ArgumentError, "same file: #{s} and #{d}" if fu_same?(s, d)
1454:     yield s, d
1455:   end
1456: end

fu_same?メソッド

  • fu_same?メソッドではFileTest.#identical?メソッドを使用。
  • その後、fu_each_src_dest0メソッドを呼ぶ。
fileutil_cp.rb
1476: def fu_same?(a, b)   #:nodoc:
1477:   File.identical?(a, b)
1478: end

fu_each_src_dest0メソッド

  • 1460行目はコピー元ファイルを配列で指定したかの判定
  • 1460行目がTRUEの場合
    • Array.try_convertは引数をto_aするらしい。
    • 引数にto_aがなければnilを返すので、TRUEならsrcが配列、FALSEなら単一ファイルとなる。
    • srcが配列なら配列一つ一つに対して、yieldして、その一つ一つが1466行目に行く感じ。
  • 1460行目がFALSEの場合
    • コピー後ファイルがファイルではなく、ディレクトリだったらコピー元ファイル名を取って、またyieldするので、最終的には1470行目に行く感じ。
fileutil_cp.rb
1459: def fu_each_src_dest0(src, dest)   #:nodoc:
1460:   if tmp = Array.try_convert(src)
1461:     tmp.each do |s|
1462:       s = File.path(s)
1463:       yield s, File.join(dest, File.basename(s))
1464:     end
1465:   else
1466:     src = File.path(src)
1467:     if File.directory?(dest)
1468:       yield src, File.join(dest, File.basename(src))
1469:     else
1470:       yield src, File.path(dest)
1471:     end
1472:   end
1473: end

fu_each_src_dest0メソッド

  • fu_each_src_dest0メソッドに戻ってきました。
  • 今回はもちろんfu_same?(s, d)はFALSEなので、エラーは出ません。
  • 1454行目でやっと本丸のcopy_fileメソッドが呼ばれる。
fileutil_cp.rb
1451: def fu_each_src_dest(src, dest)   #:nodoc:
1452:   fu_each_src_dest0(src, dest) do |s, d|
1453:     raise ArgumentError, "same file: #{s} and #{d}" if fu_same?(s, d)
1454:     yield s, d
1455:   end
1456: end

copy_fileメソッド

  • copy_fileメソッドではコピー元、コピー先、属性保持するかとdereferenceが引数。
  • dereferenceっていうのは、何のことかわからず。。
  • Entry_クラスのインスタンスを生成している。
  • Entry_クラスはinternal use onlyのクラスらしい。。。
fileutil_cp.rb
421: def copy_file(src, dest, preserve = false, dereference = true)
422:   ent = Entry_.new(src, nil, dereference)
423:   ent.copy_file dest
424:   ent.copy_metadata dest if preserve
425: end
  • copy_fileメソッドではsrcファイルを開いて、IO.copy_streamを行う。
fileutil_cp.rb
1281: def copy_file(dest)
1282:   File.open(path()) do |s|
1283:     File.open(dest, 'wb', s.stat.mode) do |f|
1284:       IO.copy_stream(s, f)
1285:     end
1286:   end
1287: end

まとめ

  • コピー処理の本質はIOクラスのcopy_streamメソッド。
  • 自分が知らないメソッド(File.identical?、File.directory?)やyieldの使い方など、色々勉強になりますね。
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
ユーザーは見つかりませんでした