はじめに
rakeタスクを初めて作っているときに、標準入力の文字を受け取りたいと思い、getsを実装したところエラーが発生しました。rubyではgetsでエラーが起きてなかったのになぜ?Rakefileでもrubyは扱えたんじゃないの?そんな疑問と格闘した結果です。
動作環境
- macOS version 11.4
- ruby 2.4.9
- rake 13.0.3
名前を読んでくれないrakeタスク君
このご時世、人と合う機会も減り、普段名前を呼ばれることがありません。せめてrakeタスク君からは名前を呼ばれたい、その一心で次のタスクを作成しました。
desc 'ユーザ名を入力すると、3回呼びかけてくれるタスクです。'
task :call_three do
print 'input your name:'
input = gets.chomp
puts ((input + '!') * 3)
end
しかし、実行すると
$ rake call_three
input your name:rake aborted!
Errno::ENOENT: No such file or directory @ rb_sysopen - call_three
/Users/akihiro/enviroment/src/pro_ruby/Rakefile:4:in `gets'
/Users/akihiro/enviroment/src/pro_ruby/Rakefile:4:in `gets'
/Users/akihiro/enviroment/src/pro_ruby/Rakefile:4:in `block in <top (required)>'
Tasks: TOP => call_three
(See full trace by running task with --trace)
エラーで名前を呼んでくれません。Rakeタスク君にまで見放されるのか。。
Ruby君は名前を読んでくれる
rubyで同じコードを実装しました。
print 'input your name:'
input = gets.chomp
puts ((input + '!') * 3)
すると、
$ ruby call_three.rb
input your name:aboru
aboru!aboru!aboru!
見事名前を読んでくれました!!
エラーを解読する
いつまでも、rake君のことでめげていては前に進めません。
かの有名なのび太くんの先生も
目が前向きについてるのは、なぜだと思う?
前へ前へと進むためだ!
という言葉を残しています。
早速エラーを解読します。先程のエラー文にはNo such file or directory @ rb_sysopen - call_three
と書かれています。 日本語にすると、そのようなファイルまたはディレクトリはありません。@rb_sysopen - call_three
となります。
どうやら、rakeタスク君はcall_threeを開こうとしているようです。
公式ドキュメントを読む
rakeタスク君がcall_threeファイルを開こうと暴挙に出た理由を探ります。まずは公式ドキュメントからgetsメソッドの正確な動作を確認します。
ARGFから一行読み込んで、それを返します。行の区切りは引数 rs で指定した文字列になります。
rs に nil を指定すると行区切りなしとみなしてファイルの内容をすべて読み込みます。ARGVに複数のファイル名が存在する場合は1度に1ファイルずつ読み>込みます。空文字列 "" を指定すると連続する改行を行の区切りとみなします (パラグラフモード)。
ふむふむ。ARGF, ARGVがわからないので調べる。
ARGFとは?
スクリプトに指定した引数 (Object::ARGV を参照) をファイル名とみなして、それらのファイルを連結した 1 つの仮想ファイルを表すオブジェクトです。 ARGV が空なら標準入力を対象とします。 ARGV を変更すればこのオブジェクトの動作に影響します。
ARGVとは?
Ruby スクリプトに与えられた引数を表す配列です。
ここまで来て全貌がつかめてきました。真実は、いつもひとつ!
rakeタスク君の挙動
まず、getsメソッドでARGFから一行読み込んで、それを返します。そしてARGFはスクリプトに指定した引数であるARGVをファイル名とみなします。そのため、rake君は
$ rake call_three
と実行したときrakeの引数であるcall_three
をファイル名とみなし、call_threeというファイルがないと騒いでいたようです。
次のようにRakefileを変更して
task :call_three do
puts ARGV
end
タスクを実行すると
$ rake call_three
call_three
ARGVにcall_threeが入っていることが分かります。
ruby君の挙動
一方でruby君の実行は
ruby call_three.rb
でした。ここでは引数を設定せずに実行しているため、ARGVが空となり、対象が標準入力に移ります。
Rakefileを修正する
さて、rakeタスクを実行するときは、タスク名が引数となり明示的に標準入力を指定しなければいけないことが分かりました。そこで、gets.chomp
の前にSTDINを指定します。
desc 'ユーザ名を入力すると、3回呼びかけてくれるタスクです。'
task :call_three do
print 'input your name:'
input = STDIN.gets.chomp
puts ((input + '!') * 3)
end
この状態でタスクを実行すると・・・
$ rake call_three
input your name:aboru
aboru!aboru!aboru!
やりました!ついにrakeタスク君を振り向かせることができました。
まとめ
- getsメソッドはコード実行時の引数をファイル名とみなす。引数がない場合は、標準入力を対象とする。
- rakeタスクではタスク名が引数となるため、getsメソッドを使うときは
STDIN.gets
や$stdin.gets
のように標準入力を表す定数を明示的に記述する必要がある。 - rubyファイル実行時は引数を指定しない場合、getsメソッドで標準入力を受け付けることができる。