ShellScript

シェルスクリプトとしてのRuby

Rubyをシェルスクリプトとして使う方法を詳述します.数値計算ではすでにあるプログラムの入力パラメータを変えて,繰り返し計算で精度や時間を検証するという作業が頻発します.このようなときにいちいちコマンドを打ち込むのでなく,スクリプトを書いて自動的に実行させるということをプロのプログラマはやってます.このような場面でRubyはもっとも強力な言語です.

課題1

Piをモンテカルロ法で解くプログラムpi0.rbの試行回数を変えて実行するスクリプトは 以下の通り.

number=[10,100,1000]

number.each do |num1|
  res=`echo #{num1} | ruby pi0.rb`
  puts "#{num1} #{res}"
end

実際に実行し,結果を示せ.

Rubyの解説をすこし

each
配列にある要素全てを順番に|num1|にいれて実行するループ.
コマンド出力
`コマンド`(バッククオートで囲まれています)で実行したいコマンドを実行することができます.上のスクリプトではその出力をresに入れています.#{num1}はnum1の中身が展開されて,つまり数値に変換されて入っています.
|
縦棒はlinuxのパイプラインと呼ばれる機能です.echo出力をruby pi0.rbに渡します.

課題2

以下のスクリプトを参考にして試行回数の増加に伴う誤差の変化を示せ.

pi=Math::PI
(pi-res).abs

課題3

さらに,数値積分のプログラムを同時に実行するようにスクリプトを書き,下記のような出力を得よ.ここで第1列は試行あるいは分割数,2列がモンテカルロ法,3列が単純な矩形近似,4列が台形近似の結果である.

[BobsNewPBG4-6:~/NumRecipeEx07/Pi] bob% cat all.res
        10    0.058407346410207    0.122661139820753    0.001666664682633
       100    0.061592653589793    0.010217666616163    0.000016666666663
      1000    0.058407346410207    0.001002167666673    0.000000166666673
     10000    0.003592653589793    0.000100021667573    0.000000001666573
    100000    0.004647346410207    0.000010000215923    0.000000000015923
   1000000    0.000848653589793    0.000001000000903    0.000000000001097

課題4

上記のスクリプトの出力をloglog(両対数)で表示するためのgnuplotのコマンドです.

gnuplot> set logscale x
gnuplot> set logscale y
gnuplot> plot "res.all" using 1:2 with linespoints

3列,4列目も加えて以下のようなグラフを描け.

Figure1.gif

ちょっと高度なコントロール

より複雑なコントロールを示します.時間があれば解読してください.狙いは,

  • あらかじめ定まった入力方法やファイルをどうやって書き換えるか.
  • あらかじめ定まった出力形式(これは,人に優しくするため不用な文字があります)から,必要な情報をどうやってとり出して,成型するか

です.下準備であらかじめ定まった入出力を与えるプログラムを作ります.その後,Rubyによるファイル成型のアイデアを示します.

下準備

コイン投げを改良して,平均(Mean)と標準偏差(Root mean square:RMS)を計算するようにする.

number = gets.chomp.to_i
total = Array.new(number+1,0)

iter = gets.chomp.to_i
puts "Number:"+number.to_s
puts "Iter  :"+iter.to_s
iter.times do
  up=0
  number.times do
    up+=rand(2)
  end
  total[up]+=1
end

mean =0
sum2 =0
(number+1).times {|i|
  mean+=i*total[i]
  sum2+=i*i*total[i]
}
mean=mean.to_f/iter
puts "Mean:"+mean.to_s
disp=sum2.to_f/iter- mean*mean
puts  "RMS :"+Math::sqrt(disp).to_s

これを実行すると

[BobsNewPBG4-6:~/NumRecipeEx07/Week5] bob% ruby coin2.rb
100
10000
Number:100
Iter  :10000
Mean:50.0513
RMS :4.97870146825454

となる.入力をファイル(INPUT)から読み込み,ファイル(OUTPUT)へ出力するようにする.

[BobsNewPBG4-6:~/NumRecipeEx07/Week5] bob% cat INPUT
100
10000

[BobsNewPBG4-6:~/NumRecipeEx07/Week5] bob% ruby coin2.rb < INPUT > OUTPUT
[BobsNewPBG4-6:~/NumRecipeEx07/Week5] bob% cat OUTPUT
Number:100
Iter  :10000
Mean:50.0102
RMS :4.94569468932326

INPUTファイルの編集

INPUT.origという以下のようなファイルを用意する.

[BobsNewPBG4-6:~/NumRecipeEx07/Week5] bob% cat INPUT.orig
#NUMBER
10000

この#NUMBERをrubyで書き換えてINPUTファイルを生成する.

number=[10,100]

inData = File.read("INPUT.orig")

number.each do |num1|
  infile = open("INPUT","w")
  infile.puts  inData.sub(/#NUMBER/,"#{num1}")
  infile.close
  puts `ruby coin2.rb < INPUT`
  puts ' '
end

実行すると以下のようになる.

[BobsNewPBG4-6:~/NumRecipeEx07/Week5] bob% ruby CoinControl.rb
Number:10
Iter  :10000
Mean:5.0128
RMS :1.58468803238997
 
Number:100
Iter  :10000
Mean:50.054
RMS :4.96766383725789

OUTPUTファイルからの値の抽出

OUTPUTファイルから正規表現を使ってNumber, RMSを含んだ行を取りだし, splitで分けて数値を抽出する.

number=[10,100,1e3]

inData = File.read("INPUT.orig")

number.each do |num1|
  infile = open("INPUT","w")
  infile.puts  inData.sub(/#NUMBER/,"#{num1}")
  infile.close
  `ruby coin2.rb < INPUT >OUTPUT`
  outData = File.read("OUTPUT")
  m=outData.scan(/Number.*$/)[0]
  num=m.split(/:/)[1].chomp
  m=outData.scan(/RMS.*$/)[0]
  rms=m.split(/:/)[1].chomp]
  puts "#{num} #{rms}"
end

outDataにいれたOUTPUTの文字列から,Numberを含む行をscanし,その配列の中身を[0]で取りだし,splitで分けた2番目の要素を取り出しています.結果は以下の通り.

[BobsNewPBG4-6:~/NumRecipeEx07/Week5] bob% ruby CoinControl.rb
10 1.58974770010842
100 5.00486186722476
1000 15.7354073703219

抽出の仕方には他にもいくつかやり方がある.

grep

m=outData.grep(/Number/)[0]
num=m.split(/:/)[1].chomp

scanするかわりにgrepで一行まるごと取り出しています.UNIXコマンドのgrepに近い使い方ができます.

grep

str1=`grep Number OUTPUT`
num=str1.split(/:/)[1].chomp

同じことをUNIXコマンド`grep`で一行取り出しています.

ファイルを1行ごとに操作

outfile = open("OUTPUT","r")
while line = outfile.gets
  if line =~ /Number/ then
    num=line.split(/:/)[1].chomp
  end
end

grep操作をrubyで置き換えて実行しています.

match

m=outData.match(/Number:/)
num=m.post_match.to_a[0].chomp

ちょっと毛色を変えてmatchを使っています.matchでNumber:を取りだし,その後ろにある文字列(post_match)を行毎に区切って(to_a),その1番目を取り出しています.

Last modified:2007/08/03 18:12:16
Keyword(s):
References:[LinuxEx]