おもこん

おもこんは「思いつくままにコンピュターの話し」の省略形です

はじめてのRake(3)

「はじめてのRake」の第3回です。 今回はファイルタスクの説明(後半)です。 今回はファイルタスクを記述する上で便利なファイルリストとパスマップを解説します。

はじめてこのブログを見る方は「はじめてのRake(1)」からご覧になってください。

文中に[R]という記号で始まる段落は、「Ruby上級者向けの解説」です。 上級とは、ほぼ「クラスを記述できるレベル」を指します。 上級以外の方はこの部分を飛ばしてください。

ファイルリスト

ファイルリストは、ファイル名の配列のようなオブジェクトです。 文字列の配列と同様の操作ができ、さらにいくつかの便利な機能を備えています。

まずファイルリストのインスタンスの作り方から説明しましょう。 クラス名「FileList」に[ ]をつけ、そのカッコのなかにファイル名をコンマで区切って書きます。 これで、そのファイルを持つファイルリストができます。

files = FileList["a.txt", "b.txt"]
p files

task :default

デフォルトタスクを定義しないと、コマンドラインからrakeを実行したときにエラーになります。 それを防ぐために何もしないデフォルトタスクを定義してあります。

ここでRakeの動作をもう一度確認しておきましょう。

  1. Rakeの実行環境を初期化する
  2. Rakefileはロードする。このときRakefileは(Rubyコードとして)実行される
  3. デフォルトタスクを呼び出す

2番めのRakefileロード時に、ファイルリストが作成され、表示され、デフォルトタスクの定義が行われます。 これらは「タスク呼び出し」前に行われていることに注意してください。

実行してみます。

$ rake
["a.txt", "b.txt"]
$

シェルで良く使われるGlobパターンも使えます。

files = FileList["*.txt"]
p files

task :default

実行してみます。

$ ls
 Rakefile   a.txt   b.txt   c.txt  '~a.txt'
$ rake
["a.txt", "b.txt", "c.txt", "~a.txt"]
$

GlobパターンについてはRubyのドキュメントを参考にしてください。

すべてのテキストファイルのバックアップ

すべてのテキストファイルをバックアップすることを考えてみます。 ここでは、「テキストファイル」を「拡張子が.txtのファイル」としておきます。 このとき、「すべて」というのは「現時点でのすべて」ではなく、「Rakeを実行する時点でのすべて」です。 将来テキストファイルが追加されたり、削除されたりする可能性がありますから、「現時点でのすべてのテキストファイル」と「Rakeを実行する時点でのすべてのテキストファイル」は同じとは限りません。 ですから、Rakefileの記述の中に、その時点でのテキストファイルを捕まえる仕組みを作らなければなりません。 それにはファイルリストを使います。

files = FileList["*.txt"]

さて、この中に「~a.txt」が含まれていますが、これはオリジナルが「a.txt」であるバックアップファイルですから、コピーの対象から外します。 そのときにはexcludeメソッドを使います。

files = FileList["*.txt"]
files.exclude("~*.txt")
p files

task :default

excludeメソッドは、与えられたパターンを自身の除外リストに加えます。 実行してみましょう。

$ rake
["a.txt", "b.txt", "c.txt"]
$

「~a.txt」が取り除かれました。

今ファイルリストにはオリジナルのファイル(コピー元のファイル)がセットされました。 一方、ファイルタスクの名前はバックアップファイル名です。 例えば「a.txt」を「a.bak」にコピーするファイルタスクでは、

  • タスク名は「a.bak」
  • 依存ファイル名が「a.txt」

です。 ファイルタスクを定義するためには、ファイルリストに含まれる「コピー元のファイル名」からタスク名である「コピー先のファイル名」を取得する必要があります。 それにはファイルタスクのextメソッドを使います。 extメソッドはファイルタスクに含まれる全てのファイルの拡張子を変更します。

names = sources.ext(".bak")

それではRakefileを書いてみましょう。

sources = FileList["*.txt"]
sources.exclude("~*.txt")
names = sources.ext(".bak")

task default: names

rule ".bak" => ".txt" do |t|
  cp t.source, t.name
end

実行してみます。

$ rake
cp a.txt a.bak
cp b.txt b.bak
cp c.txt c.bak
$

上手く動きました。 ここでテキストファイルを増やして、rakeを実行してみます。

$ echo Appended text file. >d.txt
$ rm *.bak
$ rake
cp a.txt a.bak
cp b.txt b.bak
cp c.txt c.bak
cp d.txt d.bak
$

新しいファイル「d.txt」もコピーされました。 ということは、Rakefileが「Rake実行時点でのすべてのテキストファイル」をバックアップしたのが確認できた、ということです。

この例の「*.txt」ファイルをソース、「*.bak」ファイルをターゲットということがあります。 一般に、「ソースは存在するが、ターゲットは存在するとは限らない」ということがいえます。 そのため、Rakefileではまずソースを取得して、そのソースからターゲットを生成する、という方法が良く用いられます。 上の例もその手法を用いています。

パスマップ

パスマップ・メソッドはファイルリストの強力なメソッドです。 元々はpathmapはStringオブジェクトのインスタンス・メソッドです。 これをFileListの各要素に対して実行するのがファイルリストのパスマップ・メソッドです。 パスマップは、その引数によって、様々な情報を返します。 よく使われる例をあげます。

  • %p => 完全なパスを表します
  • %f => 拡張子付きのファイル名を表します。ディレクトリ名は含まれません。
  • %n => 拡張子なしのファイル名を表します。
  • %d => パスに含まれるディレクトリのリストを表します。

パスマップの例示す前に、その準備としてカレントディレクトリに「src」ディレクトリを作り、その下に「a.txt」「b.txt」「c.txt」を作ります。

$ mkdir src
$ touch src/a.txt src/b.txt src/c.txt
$ tree
.
├── Rakefile
├── a.bak
├── a.txt
├── b.bak
├── b.txt
├── c.bak
├── c.txt
├── d.bak
├── d.txt
├── src
│   ├── a.txt
│   ├── b.txt
│   └── c.txt
└── ~a.txt

1 directory, 14 files
$

Rakefileを次のように書きます。

sources = FileList["src/*.txt"]
p sources.pathmap("%p")
p sources.pathmap("%f")
p sources.pathmap("%n")
p sources.pathmap("%d")

task :default

変数sourcesに代入されるファイルリスト・オブジェクトは「src/a.txt」「src/b.txt」「src/c.txt」を含みます。 では、実行してみます。

$ rake
["src/a.txt", "src/b.txt", "src/c.txt"]
["a.txt", "b.txt", "c.txt"]
["a", "b", "c"]
["src", "src", "src"]

パスマップでは、単純な文字列置換を行うための置換パターンを表すパラメータを指定することが出来ます。 パターンと置換文字列はコンマで区切り、全体を波括弧でくくります。 置換指定は、% と指示子の間に置きます。 例えば、「%{src,dst}p」とすると、「src」が「dst」に置換されたパス名が返されます。 これは、「依存ファイル名」から「タスク名」を取得するときに使うことができます。

パスマップの置換指定を使って、「srcディレクトリ以下のすべてのテキストファイルをdstディレクトリ以下にバックアップする」というRakefileを作ってみましょう。

sources = FileList["src/*.txt"]
names = sources.pathmap("%{src,dst}p")

task default: names

mkdir "dst" unless Dir.exist?("dst")
names.each do |name|
  source = name.pathmap("%{dst,src}p")
  file name => source do |t|
    cp t.source, t.name
  end
end

2行目でパスマップの置換指定を使っています。

  • sourcesは配列["src/a.txt", "src/b.txt", "src/c.txt"]なので
  • namesは配列["dst/a.txt", "dst/b.txt", "dst/c.txt"]になる

6行目では、バックアップ先のディレクトリ「dst」が存在しなければ作成します。 mkdirはFileUtilsモジュールのメソッドですが、このモジュールはRakeが自動的にrequireします。 8行目では文字列のpathmapメソッドを使って、タスク名から依存ファイル名を取得しています。

  • namedst/a.txtまたはdst/b.txtまたはdst/c.txtなので
  • sourceはsrc/a.txtまたはsrc/b.txtまたはsrc/c.txtになる

[R] 正規表現とProcオブジェクトを使ったルールを用いることもできます。

sources = FileList["src/*.txt"]
names = sources.pathmap("%{src,dst}p")

task default: names

mkdir "dst" unless Dir.exist?("dst")

rule /^dst\/.*\.txt$/ => proc {|tn| tn.pathmap("%{dst,src}p")} do |t|
  cp t.source, t.name
end

実行してみます。

$ rm dst/*
$ rake
cp src/a.txt dst/a.txt
cp src/b.txt dst/b.txt
cp src/c.txt dst/c.txt
$

ルールを使う方がよりシンプルなRakefileになります。

ディレクトリタスク

ディレクトリタスクを作るdirectoryメソッドを最後に紹介します。 ディレクトリタスクはタスク名の「ディレクトリが存在しなければ作成する」というタスクです。

directory "a/b/c"

このディレクトリタスクは、「a/b/c」というディレクトリを作成するタスクです。 もし、cの親であるb、aも存在しなければ、それも作成します。

これを用いてdstディレクトリを作ることもできます。

sources = FileList["src/*.txt"]
names = sources.pathmap("%{src,dst}p")

task default: names
directory "dst"

names.each do |name|
  source = name.pathmap("%{dst,src}p")
  file name => [source, "dst"] do |t|
    cp t.source, t.name
  end
end

注意しなければいけないのは、ディレクトリタスクは「タスク」なので、Rakefileのロード実行中はそのタスクが定義されるだけだということです。 ディレクトリの作成にはタスク呼び出しが必要です。 そこで、「dst」を「dst/a.txt」「dst/b.txt」「dst/c.txt」の事前タスクに追加します。 このことにより、コピーの前にディレクトリの作成が行われます。

{R}ルールを使って書き直してみます。

sources = FileList["src/*.txt"]
names = sources.pathmap("%{src,dst}p")

task default: names
directory "dst"

rule /^dst\/.*\.txt$/ => [proc {|tn| tn.pathmap("%{dst,src}p")}, "dst"] do |t|
  cp t.source, t.name
end

ルールの事前タスクにディレクトリタスクが追加されています。

以上、ファイルタスクを補助するファイルリスト、パスマップ・メソッド、ディレクトリタスクについて説明しました。 次回はこれまでの知識を使って、実践的なRakefileを書いてみます。

はじめてのRake(2)

「はじめてのRake」の第2回です。 今回はファイルタスクの説明(前半)です。 ファイルタスクはRakeにおいて最も重要なタスクです。 ファイルタスクのためにRakeがあると言っても過言ではありません。

はじめてこのブログを見る方は「はじめてのRake(1)」からご覧になってください。

文中に[R]という記号で始まる段落は、「Ruby上級者向けの解説」です。 上級とは、ほぼ「クラスを記述できるレベル」を指します。 上級以外の方はこの部分を飛ばしてください。

ファイルタスクとは?

ファイルタスクはタスクの一種です。 ファイルタスクにも一般のタスクと同じように「名前」「事前タスク」「アクション」があります。 一般のタスクとの違いは次の3点です。

  • ファイルタスクの「名前」は(ファイルの)パスを表す
  • ファイルタスクはそのアクションを実行するかどうかについての条件がある
  • ファイルタスクはfileメソッドで定義する(一般のタスクはtaskメソッド)

これ以外は一般のタスクと同じで、「タスクの呼び出しの前に事前タスクを呼び出す」「タスクの実行は一度だけ」です。

それでは、ファイルタスクのアクションを実行する上での条件とは何でしょうか。 条件は2つあります。

  • タスクの名前が示すファイルが存在しない
  • タスクの名前が示すファイルのmtime(ファイル内容変更時間)が、その事前タスク(複数ある場合はそのどれか)のmtimeよりも古い。 ただし、事前タスクがファイルタスクではない場合(一般のタスクである場合)はmtimeの代わりに現在時刻を用いる。 したがって、事前タスクが一般のタスクを含む場合は、そのファイルタスクは常に実行される。

[R]ここでいうmtime(ファイル内容変更時間)はRubyのFile.mtimeメソッドの値です。 Linuxのファイルにはatime, mtime, ctimeの3つのタイムスタンプがあります。

  • atime 最後にアクセスされた時刻
  • mtime 最後に変更された時刻
  • ctime 最後にinodeが変更された時刻

RubyのFile.mtimeメソッドはこのmtimeを返します。(C言語で書かれたオリジナルのRubyはCのシステムコールでその値を取得しています)

ファイルのバックアップ

それでは具体例を見ていきましょう。 ここではテキストファイル「a.txt」のバックアップファイル「a.bak」を作ることを考えます。 単純にファイルをコピーすれば良いので、

$ cp a.txt a.bak

で出来ますが、練習のためにRakefileにしてみます。

file "a.bak" => "a.txt" do
  cp "a.txt", "a.bak"
end

このRakefileの内容を説明します。

  • fileメソッドでファイルタスク「a.bak」を定義しています
  • a.bakの事前タスクは「a.txt」です。
  • タスク「a.bak」のアクションはcp "a.txt", "a.bak"です。

cpメソッドは第1引数ファイルを第2引数ファイルにコピーするメソッドです。 このメソッドはFileUtilsモジュールで定義されています。 FileUtilsはRubyの標準添付ライブラリですが、ビルトインではないため、通常はrequire 'fileutils'をプログラムに書かなければなりません。 しかし、Rakeが自動的にrequireするのでRakefileにそれを書く必要はありません。

タスク「a.bak」が呼び出されると、その実行の前に事前タスク「a.txt」が呼び出されます。 ところが、Rakefileにはタスク「a.txt」の定義が書かれていません。 Rakeは事前タスクの定義が無いときにどのように振る舞うのでしょうか? Rakeはファイル「a.txt」存在するならば、ファイルタスク「a.txt」を名前だけのタスク(事前タスクとアクションは無い)として自ら定義します。 そしてそのタスクを呼び出しますが、アクションが無いので何もせずに「a.bak」の呼び出しに戻ります。 もし「a.txt」が存在しなければエラーになります。

それでは、コマンドラインから実行してみましょう。

$ ls
Rakefile  a.txt
$ rake a.bak
cp a.txt a.bak
$ ls
Rakefile  a.bak  a.txt
$ diff a.bak a.txt
$ rake a.bak
$
  • 最初はカレントディレクトリには「Rakefile」と「a.txt」の2つのファイルだけがあります。
  • rakeを実行すると、「a.txt」が「a.bak」にコピーされます。
  • ディレクトリをリスティングすると、「a.bak」が新たに加わっています。
  • diffを使って「a.bak」と「a.txt」を比較すると、同じ内容のファイルなので、何もメッセージが出ません
  • 再びrakeを実行しますが、「a.bak」が「a.txt」より後に作成されているため、アクションは実行されません

ここでは、最も基本的なファイルタスクの使い方を学びました。

複数ファイルのバックアップ

次に3つのファイルをバックアップするRakefileについて考えてみましょう。 新たに「b.txt」と「c.txt」というファイルを作っておきます。 Rakefileのもっとも初歩的な書き方は、次のようなものでしょう。

file "a.bak" => "a.txt" do
  cp "a.txt", "a.bak"
end

file "b.bak" => "b.txt" do
  cp "b.txt", "b.bak"
end

file "c.bak" => "c.txt" do
  cp "c.txt", "c.bak"
end

ここには、3つのファイルタスクが定義されています。 それを実行してみましょう。 あらかじめ、「a.bak」は削除しておきます。

$ ls
Rakefile  a.txt  b.txt  c.txt
$ rake a.bak
cp a.txt a.bak
$ rake b.bak
cp b.txt b.bak
$ rake c.bak
cp c.txt c.bak
$ ls
Rakefile  a.bak  a.txt  b.bak  b.txt  c.bak  c.txt

皆さん既に気がついたことと思います。 「自分だったらこんなことしない。rakeを3回使うならcpを3回使うのと変わらないじゃないか」。 その通りです。

一度のRake実行で3個のファイルをコピーしたいですね。 これは、一般のタスクと3つのファイルタスクを関連付けることで実現できます。 最初に「copy」タスクを作り、3つのファイルタスクをその事前タスクにしてみましょう。

task copy: %w[a.bak b.bak c.bak]

file "a.bak" => "a.txt" do
  cp "a.txt", "a.bak"
end

file "b.bak" => "b.txt" do
  cp "b.txt", "b.bak"
end

file "c.bak" => "c.txt" do
  cp "c.txt", "c.bak"
end

実行してみます。

$ rm *.bak
$ rake copy
cp a.txt a.bak
cp b.txt b.bak
cp c.txt c.bak

最初に、バックアップファイルを削除するのを忘れないでください。 一度のrake実行で3つのコピーができました。

リファクタリングしましょう。 2つのことを改善します。

  • トップレベルのタスクを「copy」から「default」に変えます。 「default」はrakeの引数が省略されたときに実行されるデフォルトのタスクです。
  • 3つのファイルタスクをRubyイテレーションを使って1つにまとめます。
backup_files = %w[a.bak b.bak c.bak]

task default: backup_files

backup_files.each do |backup|
  source = backup.ext(".txt")
  file backup => source do
    cp source, backup
  end
end
  • はじめに、バックアップ・ファイルの配列を作り、「backup_files」という変数に代入しておきます。
  • トップレベルのタスクを「default」にします。
  • バックアップファイルの配列の一つひとつの要素を取り出すeachメソッドを用います。 取り出した要素がブロックのbackup変数に代入されます。
  • 変数sourceに、backupの拡張子を「.txt」に変えたものを代入します。 「ext」メソッドはRakeがStringクラスに追加したメソッドで、拡張子を変更するものです。 元々のStringクラスには「ext」メソッドはありません。
  • fileコマンドでファイルタスクを定義します。 「each」メソッドで、ブロックが3回繰り返されるので、fileコマンドも3回実行され、「a.bak」「b.bak」「c.bak」の3つのファイルタスクが定義されます。

実行してみます。

$ rm *.bak
$ rake
cp a.txt a.bak
cp b.txt b.bak
cp c.txt c.bak
$ touch a.txt
$rake
cp a.txt a.bak
$
  • バックアップファイルをすべて削除します
  • rakeを実行すると、3つのファイル全てがコピーされます。
  • touchを使って「a.txt」のmtimeを更新します(現在時刻に設定する)
  • rakeを実行すると、「a.bak」のmtimeよりも「a.txt」のmtimeの方が新しいので、ファイルタスク「a.bak」のアクションを実行します。 他のファイルタスクはバックアップのmtimeの方が新しいので、アクションは実行されません。

例の最後で「touch」を使ってmtimeを変更しましたが、通常はエディタでファイルを上書きするときにmtimeの更新が起こります。 つまり、元ファイルが新しくなるとファイルタスクのアクションを実行する条件が整うことになります。

少々リファクタリングを追加し、ブロックの中でタスクのインスタンスを使う方法を紹介します。

ファイルタスクの定義の部分を次のように変更します。

file backup => source do |t|
  cp t.source, t.name
end

ブロックに新たにパラメータ「t」が加わりました。 「t」にはファイルタスク「backup」が代入されます。 (Ruby的にはそのインスタンスが代入されます)

taskメソッドのブロックでも同じパラメータが使えます。

タスクやファイルタスクには便利なメソッドがあります。

  • name タスクの名前を返す
  • prerequisites 事前タスクの配列を返す
  • sources 自身が依存するファイルのリストを返す
  • source 自身が依存するファイルのリストの最初の要素を返す

この他にもメソッドはありますが、よく使われるのは上の4つのメソッドです。

新しいファイルタスクの定義では、そのアクションが「t.source」から「t.name」にコピーするように変わっています。 これは、それぞれ「source」と「backup」になりますから、以前のファイルタスクと内容的には同じです。

ルール

これまでのバックアップは「.txt拡張子のファイルを.bak拡張子のファイルにコピーする」というものでした。 これを「a.bak」というファイル名にあてはめれば、「a.txtをa.bakにコピーする」というアクションを持つファイルタスクが得られます。 このように、ファイルタスクを作るための規則をルールと呼びます。 ルールは「rule」メソッドで定義できます。 具体的に「rule」を使ったRakefileの例を見てみましょう。

backup_files = %w[a.bak b.bak c.bak]

task default: backup_files

rule '.bak' => '.txt' do |t|
  cp t.source, t.name
end

はじめの3行は今までと変わりません。 3行目の定義によると、defaultの事前タスクは「a.bak」「b.bak」「c.bak」ですが、それらのタスクの定義は書かれていません。 Rakeは、事前タスクの定義がないときは、その呼び出しの直前に事前タスクの定義を試みます。

  • ルールが定義されていて、タスク名がルールに合致するときは、そのルールを用いてタスクを定義する
  • 合致するルールが無く、タスク名が、その時点で存在するファイル名に一致すれば、タスク名のみ(事前タスクとアクションが無い)のファイルタスクを定義する
  • 以上のどれでもなければ、エラーになる

この例におけるルールは次のようになります。

  • タスク名の拡張子が「.bak」である
  • 依存するファイル名の拡張子が「.txt」である
  • アクションは、そのタスクの「t.source」(タスクが依存するファイルの配列の最初の要素)を「t.name」(タスク名=ファイル名)にコピーする、ということである

3つのタスク「a.bak」「b.bak」「c.bak」はすべてルールに合致するので、ルールに従ってタスクが定義されます。 それでは、実行してみましょう。

$ rm *.bak
$ rake
cp a.txt a.bak
cp b.txt b.bak
cp c.txt c.bak
$

今までと同じように動作しました。

ruleメソッドの'.bak'の部分は、Rakeが正規表現/\.bak$/に変換します。 この正規表現とタスク名の「a.bak」「b.bak」「c.bak」が比較されるのです。 そこで、最初から正規表現にしておいてもルールを定義できます。

rule /\.bak$/ => '.txt' do |t|
  cp t.source, t.name
end

[R] このことは、「拡張子の一致」だけでなく「任意のパターンに対する一致」を可能にします。 例えば、バックアップファイルを「~a.txt」のように先頭にチルダ~」を付けるように変更することが可能です。

backup_files = %w[~a.txt ~b.txt ~c.txt]

task default: backup_files

rule /^~.*\.txt$/ => '.txt' do |t|
  cp t.source, t.name
end

ところが、これではエラーになってしまいます。

$ rake
rake aborted!
Rake::RuleRecursionOverflowError: Rule Recursion Too Deep: [~a.txt => ~a.txt => ~a.txt => ~a.txt => ~a.txt => ~a.txt => ~a.txt => ~a.txt => ~a.txt => ~a.txt => ~a.txt => ~a.txt => ~a.txt => ~a.txt => ~a.txt => ~a.txt => ~a.txt]

Tasks: TOP => default
(See full trace by running task with --trace)

これは、=> '.txt'の部分が良くないのです。 これだと「~a.txt」の依存ファイルが、タスク名の「~a.txt」の拡張子を「.txt」に変えたものである「~a.txt」になってしまいます。 つまりタスク名と依存タスク名が同じなので、ルールを適用するときに無限ループに陥ってしまうのです。 Rakeでは、16回のループが起きたときにエラーとして処理します。

これを避けるには、依存ファイルをProcオブジェクトで定義します。

backup_files = %w[~a.txt ~b.txt ~c.txt]

task default: backup_files

rule /^~.*\.txt$/ => proc {|tn| tn.sub(/^~/,"")} do |t|
  cp t.source, t.name
end

procメソッドのブロックの引数には、タスク名(例えば「~a.txt」)がRakeによって渡されます。 Procインスタンスの生成には、lambdaメソッドや「->( ){ }」(Rubyのドキュメント参照)も使えます。

実行してみます。

$ rake
cp a.txt ~a.txt
cp b.txt ~b.txt
cp c.txt ~c.txt
$

今回はファイルタスクの基本とルールを学びました。 次回は、ファイルタスクの後半として、ファイルリストの使い方を説明します。

はじめてのRake(1)

Rakeの初歩について書こうと思います。 1回のブログでは難しいので、4回シリーズで書きます。 その内容はそれぞれ、タスク、ファイルタスク(前半)、ファイルタスク(後半)、応用例です。

文中に[R]という記号で始まる段落は、「Ruby上級者向けの解説」です。 上級とは、ほぼ「クラスを記述できるレベル」を指します。 上級以外の方はこの部分を飛ばしてください。

(2022/7/30追記) Githubに「はじめてのRake」をアップロードしました。 このブログの内容を更に発展させたチュートリアルです。

github.com

Github Pagesもあります。

toshiocp.github.io

Rakeのインストール

RakeはRubyのアプリケーションです。 RubyのインストールについてはRubyの公式ホームページを参考にしてください。 Rakeは「Rubyの標準添付ライブラリ」なので、インストールされたRubyの中に含まれることが多いですが、もしそうでないときは、

  • Linuxディストリビューションrubyパッケージをインストールした場合=>パッケージのrakeをインストール
  • 別の方法でインストールした場合=>コマンドから「gem install rake」でインストール

などの方法でインストールしてください。

Rakeとは?

Rakeは、Makeと同様の機能を、Rubyプログラムとして実装したアプリケーションです。

Makeは、Cでコンパイルするときに、コンパイル過程全体をコントロールするプログラムです。 しかし、MakeはC専用ではなく、いろいろなコンパイラトランスレータ(ある形式から別の形式に変換するプログラム)を制御することができます。 便利なMakeですが、その文法はマニアックです。 初歩的な使い方をしているうちは、分かりやすいのですが、使えば使うほど分かりにくくなっていきます。 例えばこんな感じです。

application: $(OBJS)
    $(CC) -o $(@F) $(OBJS) $(LIBS)

$(OBJS): %.o: %.c $(HEADER)
    $(CC) -fPIC -c -o $(@F) $(CFLAGS) $<

それに対してRakeは

  • Rubyの文法をそのまま使うことができる
  • したがって、分かりやすく柔軟な書き方ができる

という利点があります。

Rakeの基本

まず、コマンドのrakeとカレントディレクトリに置くRakefileというファイルがポイントになります。 rakeコマンドは引数にタスク名(タスクは後で説明します)をとり、

$ rake hello

の形で使います。 この例では、引数helloはタスク名です。

このときrakeは次のことを順に実行します。

  • Rakefileをロード、実行する(Rakefileにはタスクの定義が書かれている)。
  • コマンドライン引数で指定されたタスクを呼び出す(実行する)

Rakefileにタスクの定義を書く」ことが、rakeを使う際のポイントになります。 当然、コマンドラインから呼び出されるタスクはRakefileの中で定義されていなければなりません。

[R] 「タスクの定義を書く」「タスクを定義する」などは、Rubyのドキュメントで使われている言い回しです。 Rubyに熟練している人は、これが具体的に何を意味するか気になるかもしれません。 Ruby的には「Taskクラスのインスタンスを生成する」ことを意味します。

Rakefileでのタスク定義

タスクはオブジェクトで、名前、事前タスク(前提条件)、アクションを持ってますが、事前タスクとアクションは無くても構いません。

それでは、まず名前だけを持っているタスクを作成してみましょう。 Rakefileに次のように書き込みます。

task :simple_task

taskはタスクを定義するためのコマンド(命令)だと考えてください。 一般に「コマンド」はプログラム言語において、コンピュータに何かをさせるためのものです。 例えば、Shellでは、「cd」はカレント・ディレクトリを移動する「コマンド」です。 「cd /var」によって、カレントディレクトリが「/var」に移動しますが、それは「/var引数を与えてcdコマンドを実行した」ことの結果なのです。

同様に「task」コマンドには引数「:simple_task」が与えられています。 そして「taskコマンドを実行することにより、simple_taskを名前とするタスクが作成される」のです。 なお、引数の:simple_taskはシンボルですが、文字列を使っても構いません。

task "simple_task"

両者に対してtaskコマンドが行う動作は全く同じです。

実は、taskコマンドは、Rubyの文法から見ると、taskメソッドの呼び出しで、:simple_taskはtaskメソッドへの引数です。 ですので、今後はtaskを「コマンド」あるいは「メソッド」ということがありますが、

  • 「コマンド」は、taskの「タスク作成」機能に注目している場合
  • 「メソッド」は、Rubyの文法上の機能に注目している場合

で使い分けをしています。 細かいことになるので、あまり気にしなくても構いません。

[R] Rubyの文法から見た場合、taskコマンドは「メソッド呼び出し」で、:simple_taskはtaskメソッドの引数です。 Rubyではメソッド呼び出しの引数にカッコを付けても付けなくても良いのでこのように書けるのです。 もしカッコを付けるのならば、

task("simple_task")

となります。 (taskとカッコの間にはスペースを入れない)。 どちらでも定義できますが、カッコ無しを用いるのが良いです。

「タスクを定義する」とは「Taskクラスのインスタンスを生成する」ことです。 インスタンスの生成には通常newメソッドが使われますが、Task.newよりもtaskメソッドの方が便利です。 なお、taskメソッドでは、その実行の中で「Task.new」が呼び出され、タスクのインスタンスが生成される仕組みになっています。

タスク「simple_task」には事前タスクとアクションは定義されていません。

コマンドラインからタスクを実行してみましょう。

$ rake simple_task

タスクは呼び出されているのですが、アクションが無いため、見た目には何も起こりません。 タスクが定義できているかどうかは、次のようにするとわかります。

$ rake -AT
rake simple_task  #

オプションATは登録されているすべてのタスクを表示します。 これで、simple_taskが定義されていることがわかりました。

アクション

アクションは、taskメソッド呼び出しのブロックで表します。

task :hello do
  print "Hello world!\n"
end

このタスクはhelloという名前です。 helloには事前タスクはありません。 アクションは「Hello world!」と画面表示する、というものです。

では、このタスクを実行してみましょう。

$ rake hello
Hello world!

タスクhelloが呼び出され、そのアクションが実行されて「Hello world!」の文字列が表示されました。

[R] Rubyにはブロックを(1)波カッコ({})で表す(2)doendで表す、の2つの方法があります。 Rakefileではどちらも動作しますが、読みやすさの点からdoendを使うのが良いでしょう。 また、波カッコを使う場合、次のように書くと動作しません。

task :hello {print "Hello world!\n"}

これは、do-endより波カッコの方が強く結合するために起こるエラーです。 Rubyのドキュメントが参考になるので見てください。 これを解消するには、引数にカッコをつけます。

task(:hello) {print "Hello world!\n"}

Rakeでは、「taskがあたかもコマンドであるかのように表現したい」ということがあります。 波カッコを使うとそれができませんから、動作はするけれども推奨はできないのです。

RakeはRuby文法の自由さ(引数のカッコを省略できるなど)を使って、taskなどのコマンドを提供しています。 このように、特定の分野のために作られたコマンドをもつ言語を「DSL(Domain-Specific Language)」といいます。 do-end推奨の背景にはDSLの考え方があります。

事前タスク

あるタスクが事前タスクを持っている場合、そのタスクが呼び出され(実行され)る前に事前タスクを呼び出します。

タスクの定義は

task タスク名 => 事前タスク(の配列)do
  アクション
end

のようになります。

「タスク名=>事前タスク(の配列)」のところは、Rubyのハッシュです。 メソッド呼出の末尾にハッシュを渡す場合は カッコ({}) を省略することができます。 省略しなければ「{タスク名 => 事前タスク(の配列)}」となりますが、これでも動作します。

また、タスク名がシンボルの場合、例えば「:abc => "def"」と書くのを「abc: "def"」と書くことができます。 同様に、「:abc => :def」と「abc: :def」は同じです。

次の例では、firstとsecondという2つのタスクがあり、firstがsecondの事前タスクになっています。

task second: :first do
  print "Second.\n"
end

task :first do
  print "First.\n"
end

タスクsecondを呼び出すと、事前タスクであるfirstがその前に呼び出されます。

firstを実行 => secondを実行

という順になります。

$ rake second
First.
Second.

Rakefileの例

歌川さんの味玉のレシピをMakefileで記述するが面白かったので、そのRake版を作ってみました。

# 味玉をつくる

task :お湯を湧かす do
  print "お湯を湧かします\n"
end

task 卵を茹でる: :お湯を湧かす do
  print "卵を茹でます\n"
end

task :'8分待つ' => :卵を茹でる do
  print "8分待ちます\n"
end

task ボウルに氷を入れる: :'8分待つ' do
  print "ボウルに氷を入れます\n"
end

task ボウルに水を入れる: :ボウルに氷を入れる do
  print "ボウルに水を入れます\n"
end

task ボウルに卵を入れる: :ボウルに水を入れる do
  print "ボウルに卵を入れます\n"
end

task 卵の殻を剥く: :ボウルに卵を入れる do
  print "卵の殻を剥きます\n"
end

task :ジップロックに日付を書く do
  print "ジップロックに日付を書きます\n"
end

task ジップロックにめんつゆを入れる: [:ジップロックに日付を書く, :卵の殻を剥く] do
  print "ジップロックにめんつゆを入れます\n"
end

task ジップロックに卵を入れる: :ジップロックにめんつゆを入れる do
  print "ジップロックに卵を入れます\n"
end

task 一晩寝かせる: :ジップロックに卵を入れる do
  print "一晩寝かせます\n"
end

task 味玉: :一晩寝かせる do
  print "味玉ができました\n"
end

実行してみます。

$ rake 味玉
ジップロックに日付を書きます
お湯を湧かします
卵を茹でます
8分待ちます
ボウルに氷を入れます
ボウルに水を入れます
ボウルに卵を入れます
卵の殻を剥きます
ジップロックにめんつゆを入れます
ジップロックに卵を入れます
一晩寝かせます
味玉ができました

タスクの呼び出しは一度だけ

すでに呼び出されたタスクは実行されません。 つまり「タスクの実行は1度だけ」です。

例えば、味玉のRakefile

task ジップロックに卵を入れる: :ジップロックにめんつゆを入れる do

のところを

task ジップロックに卵を入れる: [:ジップロックにめんつゆを入れる, :卵の殻を剥く] do

とすると、「卵の殻を剥く」が2箇所で事前タスクになります。 呼び出しが2回ありますが、実行は1回だけなので、実行結果は同じになります。

[R] タスクのインスタンス・メソッドに「invoke」(呼び出し)と「execute」(実行)があります。 invokeはアクションを一度だけ実行しますが、executeはそのメソッドが呼ばれた回数だけ何度でも実行します。 それで、Rubyのドキュメントでは「呼び出し」と「実行」の2つの言葉を区別して使っているようです。 このブログでは使い分けが曖昧な箇所がありますが、大きな混乱はないと思っています。 なお、invokeは自身のタスクを呼び出す前に事前タスクを呼び出しますが、executeは事前タスクを呼び出しません。

タスク名には文字列も使える

今までタスク名にシンボルを使ってきましたが、文字列を使うこともできます。

task "simple_task"
task "second" => "first"

このような書き方も可能です。 シンボルでハッシュを記述するには「{abc: :def}」のような書き方ができますが、シンボルの最初に数字がくるときにはこれが使えません。 「{0abc: :def}」や「{abc: :2def}」はシンタックス・エラーになります。 「{:'0abc' => :def}」「{abc: :'2def'}」のように書かなければなりません。 文字列ではこのような心配がなく、シングルあるいはダブルクォートで囲めばエラーにはなりません。

慣例としては

task abc: %w[def ghi]

の書き方が多く用いられるようです。 %wは空白で区切られた文字列の配列を返します。 %w[def ghi]["def", "ghi"]は同じです。 Rubyのドキュメントの%記法を参考にしてください。

味玉の例で%記法を使うと

task ジップロックにめんつゆを入れる: %w[ジップロックに日付を書く 卵の殻を剥く] do

となります。

「和製英語」のおもしろYoutube

複雑な心境

最近、英語のことを書いたらスターをたくさんいただきました。 とても嬉しかったです。

もともとこのウェブサイトはコンピューターや数学について思いついたことを書くものです。 パソコン、数学記事にはスターが少ないので、嬉しかったけれども、他方で複雑な気持ちです。

和製英語のおもしろいYoutube

ところで先日の「和製英語に注意」に興味を持たれた方が多いので、今日はそれに関連するYouTube を紹介したいと思います。 投稿者はハナさんというイギリス人です。 最近はYouTubeの投稿をしていませんが、この人の動画は面白い(面白かった?)です。 リンクを貼っておきますので、ぜひ皆さんご覧になってください!!

www.youtube.com

最近の逆バージョン

和製英語は、本来の英語ではないことが問題ですが、最近はその逆バージョンがあります。 本来の英語の意味通りの英語を、日本語で表すことも可能なのに、わざわざ英語を使うこと(人)があります。 たとえば、

どうしても違和感を感じる自分がいます。 もし、これに違和感を感じなければ、あなたの英語は完璧なのかも。

7/20追記

上の例の簡単な解説をつけます。

  • ダイバーシティ=>diversity 多様性。ディヴァーシティと発音する人もいる。 イノベーション=>innovation 革新、刷新。 「これからは、多様な人材を活かし、その能力を最大限発揮する経営により、その企業活動を刷新していかなければならない」
  • インセンティブ=>incentive 報奨金(物)。 「コロナ対策が進んだ組織(例えば飲食店、ワクチン接種に協力する企業)などに、報奨としての補助金等交付するなどの施策を導入すべき」というような意味。 インセンティブは、厚労省経産省などの行政の中でも以前から使われているようです。
  • ショートノーティス=>short notice 突然の通知、間際になってからの通知。 「締切間近での依頼で失礼します」あるいは「急なお知らせで失礼します」など
  • リマインド=>remind 気づかせる、思い起こさせる。 「(何かすでに通知しておいたことについての)確認のメールを出しておいてね、よろしく」
  • アジェンダ=>協議事項、議事(日程表)、(業務の)予定表。 「それでは、本日の議題を確認しましょう」

Google Adsense 導入についてのお知らせ

この度当ブログでは Google AdSense による広告掲載を始めることにしました。 現在はまだ広告が貼られていませんが、順次掲載されるようになることと思います。 つきましては2点ほどお知らせがあります。

Google Adsenseの導入ってどんな感じ?

一つ目は、最近「はてな」がアナウンスしている「ブログの収益化」についての私自身の経験です。 具体的には、Google AdSense の導入についてです。 私の場合は、比較的順調に進みました。

事前準備が2つ必要です。

  • プライバシーポリシーの設定。 「はてな」に例があるので、それに準じて設定するとやりやすいと思います。
  • 連絡先の明示(メールアドレスなど)

次はGoogle Adsenseへの申請手続きです。 一番面倒だったのは Google AdSense の規約を読む事でした。 メインの規約以外に複数の規約があり、一通り読むのに1時間ほどかかったと思います。 大筋としては分かりやすい規約だと思いました。 それ以外の手続きは画面に沿って進めれば大丈夫です(一部「はてな」の説明と実際の手続きが違う点もありました)。

一般には、Google Adsenseの申請から承認まで、数日から4週間ぐらいかかると言われていますが、私の場合は日曜日に申請して本日未明には承認の連絡が来ました。 営業日で考えると1日で通ったことになります。 あまりに早いのに驚きました。

申請が通ったらすぐに広告が貼られるのかと思っていたら、そうではありませんでした。

(7/20追記訂正)理由は良く分かっていません。申請後にGoogle Adsentの広告設定からコードをコピーしたら広告が現れましたが、そのコピーをはずしても広告が表示されています。キャッシュのせいだったのかも?

また広告の掲載場所を私の側で「厳密に」決めることができませんでした。 いろいろ調べてみましたが、「自動設定」ではおおまかな設定しかできないようです。

(7/22追記訂正)はてなの「はてなブログ収益化攻略ガイド」に書かれていましたが、 「自身で広告の設置箇所や大きさを設定できる『広告ユニット』とGoogleアドセンスが推奨する箇所に広告を設置できる『自動広告』が」あります。 それで、自動広告をやめて、広告ユニットにし、掲載場所を定めることができました。 現在、タイトル下(これは自動的に入るみたいです)、サイドバー下、フッタに広告が入るようになっています。

当ブログのプライバシーポリシーについて

二つ目はプライバシーポリシーについてです。 Google Adsense 導入にともない、プライバシーポリシーを設定しなければならなくなりました。 サイドバーからプライバシーポリシーという項目をクリックしていただき、該当ページを開くことが出来ます。 これは「当ブログの」プライバシーポリシーであって、「はてな」のそれではありません。

簡単に言いますと、訪問者から「個人を特定できない形での情報」を収集し、それがGoogleによって利用されるということです。 また、訪問者はCookie の設定を変えて拒否をすることも可能です。

今後は?

ブログの収益化について興味をお持ちの方も多いと思いますので、ある程度収益が発生した時点でご報告したいと思います。 その日が早く来ることを願っていますが、はたしてどうでしょうか???

一番大事なのは、ブログの内容を良くすることだと思うので、頑張ります。

和製英語に注意

今日は英語ネタです。

和製英語で悩む

「日本語を介さずに英語を話しなさい」と良く言われますね。 まず日本語で言いたいことを考えそれを英語に翻訳する、これでは会話では間に合わない。 だから最初から英語で考えなさい、ということです。

とはいっても、どうしても日本語が頭に浮かぶのが日本人です。 特に日本語の中で使われる英語、「トレーナー」「ブーツ」「スケジュール」などはすぐに浮かんできます。 このとき困るのが「和製英語」です。

和製英語」は日本語に取り入れられた英語で、

  • 日本で作られた英語
  • 本来の意味とは異なる使い方をする英語

のことを指します。

例えば、前者では「ドクターヘリ」です。 「ドクターヘリ」は救急患者を搬送するヘリコプターで医師が同乗しているものをいいます。 これは完全な「和製英語」です。 もし、英会話中に「Doctor Heli」というと、「Dr. Heli 」(ヘリ博士)と思われるかもしれません。

後者の例では、「スマート」です。 日本語の「スマート」はスタイルが良いという意味で使われますが、英語のsmartは「利口な」「抜け目のない」ような意味で使われることが多いです。 「スタイルが良い」「ほっそりした」という場合はslimが使われます。

どうしても日本語会話で使っている英語が頭の中に浮かんでしまうのは困ったものです。

アメリカ英語かイギリス英語かで悩む

アメリカ英語とイギリス英語の違いが気になることもあります。 ただ、これはどちらを話しても通じることが多いように思います。 アメリカ人はイギリス英語を知っているし、イギリス人もアメリカ英語を知っているからです。

むしろリスニングのときに問題かもしれません。 学校教育ではアメリカ英語なので、どちらかというと日本人はイギリス英語に慣れていません。

例えば、履歴書は、

  • アメリカ英語では「resume」(レジュメ)もともとフランス語
  • イギリス英語では「CV」(curriculum vitae、カリキュラム・ヴァイタイ)

レジュメしか知らないと、CVと言われたときに「???」となります。

「スケジュール」はイギリス英語では「シェジュール」と発音する人もかなりいます。 知っていないと、聞き取れないでしょうね。

ひとつひとつ覚えるしかない

和製英語や米英の違いは、結局「ひとつひとつ覚えるしかない」のでしょう。 勉強の中で出てきたとき、地道に覚えていくことが大事だと思います。 がんばりましょう。

【森を見よ】2のゼロ乗はなぜ1なのか?

今日は数学ネタで、高校生向きの内容です。

※ 「森を見よ」は「木を見て森を見ず」のことわざに由来するものです。 「木を見て森を見ず」とは「細かいことに気を取られて全体を見ることができない」ことです。 「森を見よ」は「全体を見よう」「周りを見よう」「そのことによって理解を深めよう」という呼びかけの言葉です。 そして、数学学習の躓き(つまずき)を解決しようということです。 「おもこん」の新しいシリーズとして立ち上げました。

2の0乗はなぜ1なのか?

そもそも累乗はその指数の分だけ掛け算をするということです。 例えば

2^3=8

は「2を3回かけると8になる」という式です。 2の右上に小さくかかれた3を「指数」といい、式の左辺は「2の3乗」と読みます。

2\times 2\times 2=8

ということですね。

  • 2の4乗ならば、2を4回かけることで、16
  • 2の5乗ならば、2を5回かけることで、32

このへんの理解は難しくないですね。

指数を小さくしていきましょう。

  • 2の3乗ならば、2を3回かけることで、8
  • 2の2乗ならば、2を2回かけることで、4
  • 2の1乗ならば、2を1回かけることで???

2を1回かけることって何? ここで最初の躓きが起こます。 「1回じゃかけられないじゃん」。 数は2個あって初めて掛け算ができるので、1個じゃ掛け算ができない。

もっともです。

「2の0乗は2を0回かけること」はもっとかけられないじゃん???

これを「そういう決まりになっているんだよ」と説明するので、本人納得できないわけですね。

これを説明するには「森を見よ」、つまり2の0乗や2の1乗だけではなく、その周辺(2の2乗、3乗、4乗・・・)を見ることが役に立ちます。 次の表を見てください。

2^{-2} 2^{-1} 2^{0} 2^{1} 2^{2} 2^{3} 2^{4} 2^{5}
4 8 16 32

この表で2の2乗と2の3乗を比べてみます。

  • 2の2乗が4、2の3乗が8で、後者は前者の2倍になっています。
  • 2の3乗と2の4乗を比べると、それぞれ8と16で、やはり2倍になっています。
  • 2の4乗と2の5乗も16と32でやはり2倍。

「ひとつ右の列に行くと2倍される」という法則があることが分かります。 これは非常に重要なことで、(ちょっと難しくなりますが)公式で書くと、

2^{n+1}=2^n\times 2

となります。

これは重要な公式です。 内容的には簡単ですが、案外高校生にはわからないものです。

ちょっと話が横に逸れてしまいました。 次は逆に右から左に行くことを考えています。

  • 2の5乗が32、2の4乗が16で、前者を2で割れば後者になる
  • 2の4乗が16、2の3乗が8で、前者を2で割れば後者になる
  • 2の3乗が8、2の2乗が4で、前者を2で割れば後者になる

ですから、「左に行くと2で割った数になる」とうことです。 さきほどとちょうど反対の計算をすることになります。

2の累乗を並べてると、「右に行くときは2倍」「左に行くときは2で割る」いう法則があることがわかりました。 この法則を用いると2の1乗はどうなるでしょうか?

  • 2の1乗は2の2乗の4を2で割ったものですから2になる
  • 2の0乗は2の2乗の2を2で割るから1になる
  • 2のマイナス1乗は2の0乗の1を2で割るから1/2になる
  • 2のマイナス2乗は2のマイナス1乗の1/2を2で割るから1/4になる

これを改めて表にすると次のようになります。

2^{-2} 2^{-1} 2^{0} 2^{1} 2^{2} 2^{3} 2^{4} 2^{5}
\frac{1}{\;4\;} \frac{1}{\;2\;} 1 2 4 8 16 32

これらは法則を当てはめて出てきた結果ですが、こんなことをして役に立つのというふうに思われるかもしれませんね。

実はこれ役に立つんですね。 この考えを推し進めると、指数関数や対数関数に発展していきます。 そして、それは統計、物理、科学、経済といった広範な分野に応用されます。 ですが、それをここで説明するのは難しいので、「役に立つ」ということだけ理解してください。

※ はじめての「森を見よ」シリーズ、いかがだったでしょうか? 数学では、その対象になっていることだけを見ていてはわからないが、周りを見ると理解できるということがたくさんあります。 今後も「森を見よ」シリーズをよろしくお願いします。