-> English version



るびくる&RBのRubyプログラミング大作戦!
ファイルを扱う作業をRakeで便利にしよう!(パート1:概要編)

このエントリーをはてなブックマークに追加

るびくる:
Rubyの自称マスコットキャラクター。
好きな携帯電話メーカーはカシオ。無骨な見た目がステキだから。

RB(あーるびー):
解説役兼るびくるの指導役。
好きな携帯電話メーカーはソニー・エリクソン。見た目がおしゃれな携帯やコンパクトな携帯をいろいろ出してくれるから。


はじめまして。Rubyマスコットキャラクターのるびくるです!

堂々と名乗ってるけど、「自称」マスコットキャラクターだよね? 公式じゃないよね。

あれ、RBいま何かつぶやかなかった? ちょっとよく聞こえなかったなあ(スチャ)

絶対よく聞こえてたよね!? いま手に何構えたの!?

やだなあ、自己紹介からいきなり武器を持ち出したりしないよー。これAndroidケータイ。

(ウソだ、今の絶対ケータイを出す音じゃなかった……)
あ、はじめまして。解説役のRBです。
この連載記事では、るびくると2人で、Rubyについてのちょっと役立つ情報を発信していくよ。

それじゃわくわくさーん、今日のテーマはなんにする?

そうだね。最初はRuby有数の便利ツール、Rakeからにしようか。


1. Rakeってなに?

RakeはRuby-Makeの略で、その名の通りRubyで何かを作ったり、定型的な処理をしたいときに役立ってくれるツールだよ。

あれ、Ruby-Makeってことは、みたいに
何かのプログラムをコンパイルしたりするためのツールだと思ってたんだけど、そういうわけじゃないの?

うん。プログラムをコンパイルすることもできなくはないんだけど
どっちかというと、それ以外のものを作ることが多い。

よくある使い方だと、を作るためにrakeコマンドを使うことがあるし
何かのファイルをもとにして、HTMLファイルを作るためにrakeコマンドを使うこともある。
ちょっと変わった使い方だと、ファイルバックアップのために使ったりすることもできるね。

うーん、よくイメージが沸かないんだけど……「HTMLファイルを作るためにrakeコマンドを使う」ってどういうこと?

たとえば、るびくるは自分でWebサイトを作ってるよね?
そういうとき、いつもページを更新するたびにやらないといけない、定型的な処理ってない?

ある! あるよ!
ページが1つ増えたときに、HTMLファイル全部の上側のメニューを更新するのが大変だし
食べ物の写真を載せるときに、写真を縮小してサムネイル画像を1つ1つ作るのもめんどくさいし……

そうだよね。
せっかくRubyの使い方を知ってるんだから、そういう決まった処理はスクリプトにやらせたくないかな?

たとえば、なにかのコマンドを入力するだけで
自動的に、上側のメニューを差し込んだ状態のHTMLファイルを作ってくれるとか
あるディレクトリに置いた写真の画像ファイル全部を、自動的に縮小してくれるとか。

なるほど、ちょっとわかってきたよ。それを助けてくれるのがRakeっていうツールなんだね。

でも、そういうことってRakeを使わなくても、Rubyだけでやろうと思えばできるんだよね。
Rakeを使うと、どんなところが便利になるの?

うん。Rakeの便利なところはいろいろあるんだけど、大きな利点はこの3つかな。

  1. ファイルを扱う処理を書くのが、Rubyだけでやるよりも手軽
  2. いろいろなファイルが絡んだ複雑な処理を、「タスク」と「依存関係」の組み合わせで簡単に書ける
  3. Rubyのほとんどのツールやライブラリが、Rake用のスクリプトを同梱してる(ほぼ事実上の標準ツール)

    ただ、このあたりは今すぐに説明してもわかりにくいし
    実際にどう便利なのかを見てもらったほうが、わかってもらうのも早いと思うから
    これからRakeの使い方の説明を通して、少しずつ話していこうか。

はーい、お願いします!


2. 基本的なRakefileの書き方

まずRakeを使うためには、Rakefileっていう名前のRubyスクリプトが必要になる。
Rakefileの中で、Rakeを使って実行したいことの一連の流れをタスクとして定義するんだ。

一番簡単なRakefileは、だいたいこんな感じかな。

file: Rakefile

desc 'Create hello-rake.txt'
task :testfile do
  open('hello-rake.txt', 'w'){|f|
    f.write('Hello Rake world.')
  }
end

こんな感じのRakefileを書くと、「testfile」って名前のタスクが定義されたことになる。
見てもらえれば分かるとおり、"Hello Rake world."って書かれた
テキストファイルを1個作るためだけの、簡単なタスクだね。

Rakefileって、ファイル名の後ろに「.rb」ってつけなくてもいいの?

うん、付けなくても大丈夫だよ。
「Rakefile.rb」とか「rakefile.rb」なんていうファイル名で使うこともできるけど、一般的には拡張子無しの「Rakefile」が多い。
その辺は好みでいいんじゃないかな?

さて、それじゃさっき作ったRakefileを使ってみようか。

command:

> rake testfile

こんな風にrakeコマンドを実行すると、Rakefileと同じ場所にあるディレクトリに
「hello-rake.txt」っていうテキストファイルが作られる。

ちなみに、1.8系列のrubyを使っている場合には、から
上のコマンドを実行する前に、しておいてね。

へー、思ってたよりシンプルな仕組みなんだね。
1行目にある「desc」っていうのは、タスクの説明ってこと?

そう。この行は無くてもいいんだけど、この行があればrakeコマンドでタスクの説明を表示してくれるようになる。
rakeコマンドには、タスク一覧を表示できるオプションがあって
そのオプションをつけると、説明をつけたタスク名と説明の一覧を表示することができるんだよ。

たとえばさっき作ったRakefileのタスク一覧は、こんな感じで表示される。

command:

> rake -T
rake testfile  # Create hello-rake.txt

> rake -D
rake testfile
    Create hello-rake.txt

ちなみに、rake -Tの代わりにrake --tasksrake -Dの代わりにrake --describeを使うこともできるよ。

これを使えば、「あれどんなタスク定義してたっけ」ってなってRakefileの中を何度も読み直さなくてもいいんだね!
ところで、-Tと-Dってなにが違うの?

とくに大きな違いはないから、どっちを使っても大丈夫だよ。
違うのは-Tの場合、1タスクを1行で表示しようとするから、長い説明文が省略されるってことぐらいかな。


3. タスクの依存関係ってなに?

さて、今度は依存関係の解決やコマンドの実行について説明するために、もうちょっと複雑な例として
複数のファイルを扱うRakefileを書いてみようか。

FileTree

- Rakefile
- coolerator/
    - curry-block.stuff
    - carrot.stuff
    - potato.stuff
    - onion.stuff

file: Rakefile

# encoding: utf-8
desc 'カレーを料理するタスク'
task :cookcurry => 'curry.dish'

desc '冷蔵庫の材料を元に、カレーを作るファイルタスク'
file 'curry.dish' => ['coolerator/curry-block.stuff', 'coolerator/carrot.stuff', 'coolerator/potato.stuff', 'coolerator/onion.stuff'] do |task|
  # 冷蔵庫ディレクトリ(coolerator)に移動
  cd 'coolerator/' do
    # 野菜を切るために、「cut-food」コマンドを実行
    sh "cut-food carrot.stuff potato.stuff onion.stuff"

    # カレールーと全ての野菜を煮込むために、「boil-food」コマンドを実行
    sh "boil-food *.stuff"
  end
end

ただし、普通のファイルを扱うRakefileだと、るびくるには分かりにくいかもしれないから
一例として「材料を使ってカレーを料理する」タスクを、一連のrakeタスクとして定義してみたよ。

わたしがごはんにしか興味ないような言い方はやめてくれる!?
ああ、でもわかりやすいのがちょっと悔しい!

それじゃ、試しにrake -Tコマンドを実行してみようか。

command:

> rake -T
rake cookcurry   # カレーを料理するタスク
rake curry.dish  # 冷蔵庫の材料を元に、カレーを作るファイルタスク
>

最初のRakefileの例と同じで、descメソッドで説明をつけたタスクが一覧表示されてるんだね。

あれ、でもこれって……rake cookcurryを実行したら、どんな処理が実行されることになるの?
料理をしようとするのはなんとなく分かるんだけど。

うん。たぶんるびくるが疑問に思ってるのは、この部分のことだよね?

desc 'カレーを料理するタスク'
task :cookcurry => 'curry.dish'

そうそう! 最初の例では、

うん。今回の例では、cookcurryタスクの処理を直接書いてない。
その代わりに依存関係を使って、cookcurryタスクのためにcurry.dishファイルが必要だってことを表してるんだ。

依存関係?

すごく簡単に言うと、「Aを作るためにはBが必要です」っていう関係のこと。

たとえば、今回は「カレーを料理する」っていうタスクを定義したいわけだよね。
料理するからには、何かを作る必要があるんだけど
何を作ることができたら、料理をすることができたって言えると思う?

うーん……それはもちろん、カレーだよね。

そう。それじゃカレーを作るためには、何が必要になる?

カレーの材料……つまり、カレールーとか、にんじん、ジャガイモ、タマネギ、ピクルスなんかが必要になるね。

最後のは必要ってわけじゃないと思うけど、だいたいそんな感じだね。
それじゃ、その関係を踏まえて、さっきのコードのタスク定義部分を見てみようか。

desc 'カレーを料理するタスク'
task :cookcurry => 'curry.dish'

desc '材料を元に、カレーを作るファイルタスク'
file 'curry.dish' => ['curry-block.stuff', 'carrot.stuff', 'potato.stuff', 'onion.stuff'] do |task|
  中略
end

この定義は、こういう関係を表してるんだ。

  1. 「カレーを料理する」タスクには、curry.dishファイルを作る必要がある
  2. curry.dishファイルを作るためには、「curry-block.stuff」や「carrot.stuff」などのファイルを作る必要がある

    rakeコマンドを実行したときには、これらの関係をチェックして、必要なファイルを作るためのタスクを勝手に実行してくれる。
    たとえばrake cookcurryを実行すれば、そのためにはcurry.dishファイルが必要になるから
    自動的にcurry.dishを作るためのタスクを実行してくれる、ってわけ。

なるほど、何となく分かってきたよ!
「file 'curry.dish'」の下にあるブロックの中の、コード部分が
そのcurry.dishを作るためのタスクだから……
rake cookcurryを実行するだけで、そのコード部分が呼び出されるんだね!

そう。Rakeはこんな風に、「Aを作るためにBが必要」「Bを作るためにCが必要」っていうような
ファイル同士の関連性(依存関係)を、次々と書いていくだけで
いろいろな種類のファイルを扱うややこしい処理も、それほど苦労せずに書くことができるし
自動的に必要なファイルだけ作ってくれるんだよ。

ここはRakeの魅力を知るためには重要な点だから、しっかりと理解しておいてね。


4. コマンドの実行とファイルタスク

それじゃ、依存関係のことはだいたい説明できたと思うから
改めてもう一回、さっきのRakefileを見ていこうか。

file: Rakefile

# encoding: utf-8
desc 'カレーを料理するタスク'
task :cookcurry => 'curry.dish'

desc '冷蔵庫の材料を元に、カレーを作るファイルタスク'
file 'curry.dish' => ['coolerator/curry-block.stuff', 'coolerator/carrot.stuff', 'coolerator/potato.stuff', 'coolerator/onion.stuff'] do |task|
  # 冷蔵庫ディレクトリ(coolerator)に移動
  cd 'coolerator/' do
    # 野菜を切るために、「cut-food」コマンドを実行
    sh "cut-food carrot.stuff potato.stuff onion.stuff"

    # カレールーと全ての野菜を煮込むために、「boil-food」コマンドを実行
    sh "boil-food *.stuff"
  end
end

まず、1行目のこのコメントは、エンコーディング宣言だね。
「このRubyスクリプトは、UTF-8エンコーディングで書かれています」ってことを宣言している箇所だよ。

# encoding: utf-8

あ、これって確かやつだよね!

ああ、そういえばるびくるのメイン環境はRuby 1.8だったね。
Ruby 1.9.1以降のエンコーディングの話については、ここでは詳しく説明はしないけど
とりあえずスクリプトの中に日本語を含んでいる場合には
常に1行目にエンコーディングの指定をしておいたほうがいい、ってことだけ覚えておいてね。

しかし自称とはいえ、マスコットキャラクターが最新じゃないRubyを使ってるってどうなの?

わたしは1.8ユーザーにも1.9.1以降のユーザーにも優しい
聖女のようなマスコットなので、それでよいのです。

……。

それじゃ、次に行こうか。
といっても、cookcurryタスクに関する部分は、依存関係のところで詳しく説明したから
次は5〜6行目の、ファイルタスクの宣言のところを見てみよう。

desc '冷蔵庫の材料を元に、カレーを作るファイルタスク'
file 'curry.dish' => ['coolerator/curry-block.stuff', 'coolerator/carrot.stuff', 'coolerator/potato.stuff', 'coolerator/onion.stuff'] do |task|

ここではtaskメソッドの代わりに、fileメソッドを使ってファイルタスクを定義してるね。

ファイルタスクっていうのは、ファイルを作るためのタスクだってことだよね。
それって、taskメソッドで定義する普通のタスクとは違うの?

うん。定義したブロック内の処理を実行する、ってところまでは同じなんだけど
ファイルタスクには、対応するファイルが作られていないときか、依存してるファイルが更新されたときの
どちらかのときにしか実行されないっていう特徴がある。

そっか。ファイルタスクを使えば、rake cookcurryコマンドを実行したときに

  1. curry.dishファイルがまだ作られていない(存在しない)とき
  2. 冷蔵庫の中にある、curry.dishファイルの材料(*.stuffファイル)が変更されたとき

のどっちかのときにしか、料理をしないようになるんだね。

そういうこと。だから、基本的には
何かのファイルを作るタスクは、できるだけ普通のタスクよりもファイルタスクにしておいたほうがいいよ

それじゃ、どんどん次に行くよ。

  # 冷蔵庫ディレクトリ(coolerator)に移動
  cd 'coolerator/' do

これはcdメソッドをブロックつきで実行して、カレントディレクトリを移動してるところだね。

あれ、cdメソッドって標準のメソッドにあったっけ?

お、いいところに気が付いたね。

そう。これは標準のメソッドじゃなくて、実はが持ってるメソッドなんだ。
Rakefileの中では、fileutilsライブラリが持ってるメソッドを、組み込み関数のように使うことができる
これも地味だけど、Rakeの便利なところの1つだね。

あー、それ確かにすごい便利!
fileutilsライブラリって、普段よく使うのに、組み込みのクラスになってないから
毎回require 'fileutils'って書いたり、FileUtils.cp_rって書いたりするのが面倒だったんだよね〜。

ああ、僕も手元のスクリプトの半分くらいにはrequire 'fileutils'が入ってるなあ。

  # 冷蔵庫ディレクトリ(coolerator)に移動
  cd 'coolerator/' do
    # 野菜を切るために、「cut-food」コマンドを実行
    sh "cut-food carrot.stuff potato.stuff onion.stuff"

    # カレールーと全ての野菜を煮込むために、「boil-food」コマンドを実行
    sh "boil-food *.stuff"
  end

最後に、cdブロックの内側を見てみようか。ここが実際に料理をしてるところだね。
ここでは、コマンドを実行するために、shメソッドを使ってるよ。

みたいなものってこと?

そうだね。shメソッドはRakeの拡張なんだけど、基本的にはRuby組み込みのsystemメソッドと同じ機能を持ってる。
ただ、実行したコマンドの内容を表示してくれるって特徴があるから
Rakefileの中では、できるだけshメソッドの方を使ったほうがいいよ。

たとえば、このRakefileを使ってrakeを実行したら、たぶんこんな感じの表示になる。

command:

> rake cookcurry
cut-food carrot.stuff potato.stuff onion.stuff
boil-food *.stuff
>

cut-foodコマンドとboil-foodコマンドの実行内容が表示されてるんだね。これは確かにわかりやすいね!


それじゃ、パート1:概要編はここで終わりにしようか。

今回は、あんまり実践的な内容の話はできなかったから
次回パート2では、実際にHTMLファイルを作る例を想定したRakefileの書き方や
ファイルリストの使いかたについての話をしていく予定だよ。

えー、この話次回に続くの!?

え、今さら!?
るびくるには連載だって本番前に言っておいたし、タイトルの横にちゃんと(パート1:概要編)って書いておいたじゃない!

いやー、連載だとは聞いてたんだけど、てっきりRakeの話自体は今回で終わるものなんだと思い込んでまして……

るびくるのその、人の話が左の耳から入って右の耳にそのまま抜けていく習性、どうにかならないの?

パート2:実践編1に続きます。

このエントリーをはてなブックマークに追加

参考Webページ:
rakeライブラリ - Rubyリファレンスマニュアル
Rake - idesaku web (後藤大輔さんによる解説記事)
注意事項
  • 本記事の内容は、Rake 0.9.2.2 時点での動作を元にしています。
  • 本記事内で使用しているアイコン画像は、桜去ほとりさんが制作されたものです。クリエイティブ・コモンズ 表示-非営利 3.0ライセンスのもとで、再配布や変更などを行っていただくことができます。
  • 本記事内の文章やソースコードは、CC0ライセンスのもとで、ご自由に利用していただくことができます。
  • ツンツク・モモコのお買い物大作戦と形式が類似していますが、本記事側が一方的に参考にさせていただいただけであり、とくに関連性はありません。

一言メッセージフォーム

るびくるへの質問や、やってほしい企画のリクエスト、Webサイト管理スタッフへの感想・意見・質問など、なんでもお気軽にどうぞ。