「PIONEチュートリアル」の版間の差分
(→基本2(特定のファイルを入力し、出力する(更新判定))) |
(→基本3(複数ファイルの入力と複数ファイルの出力)) |
||
| 行220: | 行220: | ||
== 基本3(複数ファイルの入力と複数ファイルの出力)== | == 基本3(複数ファイルの入力と複数ファイルの出力)== | ||
| + | |||
| + | さて、更新判定が理解できたところで、複数ファイルの入出力について考えてみましょう。test1.in, test2.inから、それぞれ3倍した数が格納された、test1.out, test2.outが出力できるPIONE定義書を作成してみましょう。ここで、名前をMultiplyingFiles.pioneとして作成してみましょう。 | ||
| + | ----- | ||
| + | |||
| + | 問題1:上記のMultiplyingFiles.pioneを作成して実行してみて下さい。 | ||
| + | |||
| + | ----- | ||
| + | |||
| + | 解答例1: | ||
| + | |||
| + | Rule Main | ||
| + | input 'test1.in' | ||
| + | input 'test2.in' | ||
| + | output 'test1.out' | ||
| + | output 'test2.out' | ||
| + | |||
| + | Action | ||
| + | awk '{ print $1*2 }' {$I[1]} > {$O[1]} | ||
| + | awk '{ print $1*2 }' {$I[2]} > {$O[2]} | ||
| + | End | ||
| + | |||
| + | ----- | ||
| + | |||
| + | どうでしょうか。思ったように動きましたか。複数のファイルがあれば、複数書き並べればよいのです。 | ||
| + | |||
| + | でもなんだか、同じ事をするのに何度も行を書くのは嫌だなと思いませんか。もし、2倍を5倍に変えたくなったら全ての行を書き換えなくてはなりませんね。10行ぐらいならそれでもよいけれど、100個、1000個とファイルが増えてきたらどうでしょうか。だんだん嫌になってきましたか。 | ||
| + | では、どうすれば良いでしょうか。例えば、シェルスクリプトが得意なひとは次の様なプログラムを設定することも出来ます。 | ||
| + | |||
| + | ----- | ||
| + | |||
| + | 解答例2: | ||
| + | |||
| + | Rule Main | ||
| + | input 'test1.in' | ||
| + | input 'test2.in' | ||
| + | output 'test1.out' | ||
| + | output 'test2.out' | ||
| + | |||
| + | Action | ||
| + | for i in `ls *.in`; do | ||
| + | awk '{ print $1*2 }' $i > `basename $i .in`.out | ||
| + | done | ||
| + | End | ||
| + | |||
| + | ----- | ||
| + | |||
| + | これでも、ファイルが増えるたびにinput/outputを書き換える必要があります。 | ||
| + | そこで、全てのファイルを入力し、全てのファイルを出力するように入出力を表現することも出来ます。 | ||
| + | |||
| + | ----- | ||
| + | |||
| + | 解答例3: | ||
| + | |||
| + | Rule Main | ||
| + | input '*.in'.all | ||
| + | output '*.out'.all | ||
| + | |||
| + | Action | ||
| + | for i in `ls *.in`; do | ||
| + | awk '{ print $1*2 }' $i > `basename $i .in`.out | ||
| + | done | ||
| + | End | ||
| + | |||
| + | ----- | ||
| + | |||
| + | どうでしょうか。うまく動作したでしょうか。ここで、 | ||
| + | |||
| + | input '*.in'.all | ||
| + | output '*.out'.all | ||
| + | |||
| + | の部分は、*は任意の文字列を表現します。つまり、*.inは、ファイルの最後が.inで終了しているファイル全て、*.outは、ファイルの最後が、.outで終了しているファイル全てを表します。.allがついていると、全てのファイルを取り扱うことを意味しています。 | ||
| + | |||
| + | これでずいぶんと記述しやすくなりました。 | ||
| + | |||
| + | でも、これだと、*.inのどれかのファイルが更新されていると全て変更になってしまいます。試してみて下さい。なんだか無駄ですね。また、同時にできるはずのことをfor文を使って順次実行しているので、時間も無駄ですね。このくらいのタスクであれば、たいして時間がかかるわけではないのですが、もっと時間のかかるタスクだったらどうでしょうか。必要なファイルの更新だけを、できれば複数のホストや一台でも最近のPCだったら、マルチコアCPUをもっているので、同時に動かすともっと早く終了できるようになるはずです。 | ||
| + | |||
| + | さて、それではどのようにすればよいのでしょうか。それが次の基本4になります。だんだんPIONEらしくなってきます。 | ||
| + | |||
| + | == 基本4(複数ファイルの入力と複数ファイルの出力の並列処理)== | ||
2014年3月29日 (土) 01:49時点における版
'PIONEチュートリアル ここでは、PIONEのチュートリアルを行います。
目次
基本1(特定のファイルを出力する)
まず、PIONEを動かすにはルール定義書をつくる必要があります。 どんな言語でも最初に作成するHelloプログラムを作ってみましょう。この場合、出力ファイルをひとつ指定することになります。
次の内容のファイル'HelloWorld.pione'を作成してみましょう。
Rule Main output 'message.txt' Action echo "Hello PIONE world !" > message.txt End
この後、pione-clientを実行します。
$ pione-client HelloWorld.pione -o helloOutput
例えば、次のような出力が流れます。
==> &Anonymous:Root([],{})
--> Rule Application: &Anonymous:Root([],{})
--> Distribution: &Anonymous:Root([],{})
>>> &Anonymous:Main([],{})
==> &Anonymous:Main([],{})
SH ------------------------------------------------------------
SH echo "Hello PIONE world !" > message.txt
SH ------------------------------------------------------------
<== &Anonymous:Main([],{})
<-- Distribution: &Anonymous:Root([],{})
<-- Rule Application: &Anonymous:Root([],{})
<== &Anonymous:Root([],{})
その結果、helloOutputというディレクトリができます。その中に指定したファイルmessage.txtが出力されています。
$ cat helloOutput/message.txt
Hello PIONE world !
もう一度、実行すると今度は、実行の必要がないために次のようなものが出力されます。
==> &Anonymous:Root([],{})
--> Rule Application: &Anonymous:Root([],{})
<-- Rule Application: &Anonymous:Root([],{})
<== &Anonymous:Root([],{})
さっきと比べると、Main Ruleが動いていないことが分かります。
さて、改めて設定したファイルを眺めてみます。
まず、最初に呼び出されるルール(Main)が定義されています。
Rule Main
Mainは、C言語などと同様に特別な意味をもつルールです。
次に、出力ファイルが定義されています。
output 'message.txt'
ここに書かれたファイルが最終的に-oで指定されたディレクトリに出力として戻ってきます。 Action以降が実際に起動するプログラムになります。
Action echo "Hello PIONE world !" > message.txt
実際にはシェルスクリプトが動きますので、どんなものも実行が可能です。
- !/bin/csh
で始めれば、cshを使って記述することもできますし、スクリプト系の言語であれば自由に記述し、実行形式を実行出来ます。
最後に、RuleをEndで終了します。
End
これが、一番シンプルなルールの書き方です。入力ファイルがないので、出力ファイルがなければ作成し、あれば、作成しないという動作をします。必要以上の動作をしないところが通常のシェルスクリプトを実行する場合と異なる展です。
基本2(特定のファイルを入力し、出力する(更新判定))
次に、入力ファイルから出力ファイルを作成する場合の定義書について作成してみましょう。
#Multiplying.pione
Rule Main
input 'test.in'
output 'test.out'
Action
awk '{ print $1*2 }' {$I[1]} > {$O[1]}
End
今回は、test.inという入力ファイルからその積算をして、test.outというファイルを作り出すルールです。入寮ファイルの更新判定により、ルールを実行するかどうかが変わってきます。
awkの使い方については、別途勉強してみて下さい。ここではファイルの中にある行頭の数字を2倍して、出力することができます。
まず、
$ mkdir MultiplyingInput $ pione-client Multiplying.pione -o MultiplyingOutput -i MultiplyingInput/
として、入力ファイルがあるディレクトリ(指定しなければ、現在のディレクトリ)を指定して実行してみます。
==> &Anonymous:Root([],{})
--> Rule Application: &Anonymous:Root([],{})
<-- Rule Application: &Anonymous:Root([],{})
<== &Anonymous:Root([],{})
入力ファイルがありませんので、何もする事がないとして終了してしまいます。先ほどとの違いに気がついたでしょうか。
次に、ファイルを作成して、実行してみます。
$ echo "3" > MultiplyingInput/test.in
$ echo "5" >> MultiplyingInput/test.in
$ cat MultiplyingInput/test.in
3
5
$ pione-client Multiplying.pione -o MultiplyingOutput -i MultiplyingInput/
==> &Anonymous:Root([test.in],{})
--> Rule Application: &Anonymous:Root([test.in],{})
--> Distribution: &Anonymous:Root([test.in],{})
>>> &Anonymous:Main([test.in],{})
==> &Anonymous:Main([test.in],{})
SH ------------------------------------------------------------
SH awk '{ print $1*2 }' {$I[1]} > {$O[1]}
SH ------------------------------------------------------------
<-- Distribution: &Anonymous:Root([test.in],{})
<-- Rule Application: &Anonymous:Root([test.in],{})
<== &Anonymous:Main([test.in],{})
<== &Anonymous:Root([test.in],{})
$ cat MultiplyingOutput/test.out
6
10
となり、確かに2倍の値のファイルができあがっていることが分かります。 もう一度、実行すると、
$ pione-client Multiplying.pione -o MultiplyingOutput -i MultiplyingInput/
==> &Anonymous:Root([test.in],{})
--> Rule Application: &Anonymous:Root([test.in],{})
<-- Rule Application: &Anonymous:Root([test.in],{})
<== &Anonymous:Root([test.in],{})
となり、ここでも何も実行しません。つまり、入力ファイルに比べて出力ファイルのほうが新しいので、更新判定の結果、実行しなくてよいと判断したことになります。賢いですね。
さて、ここで、ファイルを更新してみましょう。3行目に7を付け加えます。
$ echo "7" >> MultiplyingInput/test.in
そして、実行してみましょう。 $ pione-client Multiplying.pione -o MultiplyingOutput -i MultiplyingInput/
==> &Anonymous:Root([test.in],{})
--> Rule Application: &Anonymous:Root([test.in],{})
--> Distribution: &Anonymous:Root([test.in],{})
>>> &Anonymous:Main([test.in],{})
==> &Anonymous:Main([test.in],{})
SH ------------------------------------------------------------
SH awk '{ print $1*2 }' {$I[1]} > {$O[1]}
SH ------------------------------------------------------------
<-- Distribution: &Anonymous:Root([test.in],{})
<== &Anonymous:Main([test.in],{})
<-- Rule Application: &Anonymous:Root([test.in],{})
<== &Anonymous:Root([test.in],{})
$ cat MultiplyingOutput/test.out
6
10
14
今度は実行されました。これが更新判定によりルールが実行されるかどうかが判定されているということです。 もう一度実行しても、今度は実行されません。
$ pione-client Multiplying.pione -o MultiplyingOutput -i MultiplyingInput/
==> &Anonymous:Root([test.in],{})
--> Rule Application: &Anonymous:Root([test.in],{})
<-- Rule Application: &Anonymous:Root([test.in],{})
<== &Anonymous:Root([test.in],{})
さて、出力ファイルを削除してみるとどうなるでしょうか。
$ rm MultiplyingOutput/test.out
今度は、実行されますね。
では、内容は変えずに、touch コマンドを使って、入力ファイルの修正時刻を変えるとどうなるでしょうか。
$ touch MultiplyingInput/test.in
今回も、予想通り、動作しました。
もし、動作しないようだったら、バグです。すぐに、GITHUB/PIONEに報告しましょう。
さて、この基本2が理解できれば、まず、PIONEの動きの基本が理解できたことになります。
ところで、Actionで定義した動作の中に、見慣れない記号( {$I[1]}, {$O[1]} )が現れています。
Action
awk '{ print $1*2 }' {$I[1]} > {$O[1]}
End
{$I[1]}は入力ファイル、{$O[1]}は出力ファイルを表しています。それぞれの[]の中の数字1は、 input/outputで定義したそれぞれの1番目ということを意味しています。次の基本3での複数ファイルの入出力では1以外が使われることになります。 まだ、何が便利か少し分かりづらいと思いますが、例えば、Multiplying2.pioneとして、次のように記述するとどのように動作するでしょうか。
Rule Main
input 'test.in'
output '{$I[1]}.out'
Action
awk '{ print $1*2 }' {$I[1]} > {$O[1]}
End
少し考えてみて下さい。そして、動作させてみましょう。
$ pione-client Multiplying2.pione -o MultiplyingOutput -i MultiplyingInput/
MultiplyingOutputのディレクトリにはどんなファイルができあがったでしょうか。
そうですね。test.in.outができあがっています。何故かを考えてみましょう。
基本3(複数ファイルの入力と複数ファイルの出力)
さて、更新判定が理解できたところで、複数ファイルの入出力について考えてみましょう。test1.in, test2.inから、それぞれ3倍した数が格納された、test1.out, test2.outが出力できるPIONE定義書を作成してみましょう。ここで、名前をMultiplyingFiles.pioneとして作成してみましょう。
問題1:上記のMultiplyingFiles.pioneを作成して実行してみて下さい。
解答例1:
Rule Main
input 'test1.in'
input 'test2.in'
output 'test1.out'
output 'test2.out'
Action
awk '{ print $1*2 }' {$I[1]} > {$O[1]}
awk '{ print $1*2 }' {$I[2]} > {$O[2]}
End
どうでしょうか。思ったように動きましたか。複数のファイルがあれば、複数書き並べればよいのです。
でもなんだか、同じ事をするのに何度も行を書くのは嫌だなと思いませんか。もし、2倍を5倍に変えたくなったら全ての行を書き換えなくてはなりませんね。10行ぐらいならそれでもよいけれど、100個、1000個とファイルが増えてきたらどうでしょうか。だんだん嫌になってきましたか。 では、どうすれば良いでしょうか。例えば、シェルスクリプトが得意なひとは次の様なプログラムを設定することも出来ます。
解答例2:
Rule Main
input 'test1.in'
input 'test2.in'
output 'test1.out'
output 'test2.out'
Action
for i in `ls *.in`; do
awk '{ print $1*2 }' $i > `basename $i .in`.out
done
End
これでも、ファイルが増えるたびにinput/outputを書き換える必要があります。 そこで、全てのファイルを入力し、全てのファイルを出力するように入出力を表現することも出来ます。
解答例3:
Rule Main
input '*.in'.all
output '*.out'.all
Action
for i in `ls *.in`; do
awk '{ print $1*2 }' $i > `basename $i .in`.out
done
End
どうでしょうか。うまく動作したでしょうか。ここで、
input '*.in'.all output '*.out'.all
の部分は、*は任意の文字列を表現します。つまり、*.inは、ファイルの最後が.inで終了しているファイル全て、*.outは、ファイルの最後が、.outで終了しているファイル全てを表します。.allがついていると、全てのファイルを取り扱うことを意味しています。
これでずいぶんと記述しやすくなりました。
でも、これだと、*.inのどれかのファイルが更新されていると全て変更になってしまいます。試してみて下さい。なんだか無駄ですね。また、同時にできるはずのことをfor文を使って順次実行しているので、時間も無駄ですね。このくらいのタスクであれば、たいして時間がかかるわけではないのですが、もっと時間のかかるタスクだったらどうでしょうか。必要なファイルの更新だけを、できれば複数のホストや一台でも最近のPCだったら、マルチコアCPUをもっているので、同時に動かすともっと早く終了できるようになるはずです。
さて、それではどのようにすればよいのでしょうか。それが次の基本4になります。だんだんPIONEらしくなってきます。