script



6. スクリプトプログラム

ここまで説明してきたことは、「データに名前を付ける」「関数をつくる」 というシンプルなことだけでした。まだ、プログラミングを楽しむことなんて できませんよね。この先は、すこしづつ、複雑な問題を解きながら、 楽しみ方を探りましょう。問題を解くこと,すなわち、プログラミングすること です。

さて、前章までは、Gauche のインタープリタ(gosh)の対話環境の中でプログラムを 動作させてきました。これでも非常に複雑なプログラムを書いて動作させる ことができます。実際、かの有名な MIT の教科書 SICP 『計算機プログラムの構造と解釈』では、ほぼすべてのプログラムは、 インタープリタの対話環境で動作させることを想定しています。 こうしたやりかたのメリットは、

  • 評価する式を自由に入力できる
  • 定義をその場で書いて、その場で実行できる

デメリットは、

  • インタープリタを手で起動しなければならない
  • シェルコマンドとして使えない

です。

6.1. シェルコマンド

シェルコマンドというのは、なんでしょう。 プログラミングをする目的はいろいろですが、そのなかで最も シンプルなもの、「一つの命令で、計算機になにか指示をする」というものです。 この「一つの命令で、計算機になにか指示をする」ということは、計算機を 使う日常のなかではだれでもやっていることですよね。

ディレクトリの一覧を見るには、

% ls 

一時ディレクトリを作るには、

% mkdir tmp

なんてことやりますよね。あるいは、覚えたての カックロをやるときは、

% kakro 28 4

とか(このプログラムはこの先で作ります)。

このように、shellからの命令のことをシェルコマンドといいいます。 そして、それを実現するプログラムのことをスクリプトプログラムと呼びます。

6.2. スクリプトプログラムの書き方

細い理屈は抜きにして、まず、よくある、Hello world をやってみましょう。 以下のコードを、hello というファイル名で保存しましょう。

;;-*- scheme -*-
(define (main args)
  (format (current-output-port) "Hello, world!~%")
  0)    

では、動かしてみましょう。動作させるには、インタープリタ gosh が 必要です。gosh の引数として、いま保存したスクリプトファイル名 hello を渡します。そうすると、インタープリタ gosh が hello の内容を解釈 実行します。

% gosh hello
Hello, world!

さて、通常のシェルスクリプトと同様に、スクリプトの最初の行に 呪文を加えることで、単独で実行可能なスクリプトにすることができます。

#!/usr/local/bin/gosh
;;-*- scheme -*-
(define (main args)
  (format (current-output-port) "Hello, world!~%")
  0)    

最初の行の#!がその呪文です。その直後にあるのは、 gosh インタープリタのフルパスです。コマンドラインから which gosh というコマンドで表示されるものです。

% which gosh
/usr/local/bin/gosh

さて、コードの内容を読みましょう。

一行目は呪文でしたね。 二行目はコメント行ですが、これは Emacs 用のおまじないです。 このおまじないは Emacs が Scheme モードで動作させるためのものです。

三行目以降がプログラムです。 ここでは、main という名前の関数だけを定義しています。 この関数の名前は、なんでもよいというわけではなく、 main である必要があります。また、引数も args にしておきましょう。

format は、Posix の printf にあたる関数だと思ってください。 詳しくは、Gauche のリファレンスマニュアルを見てください。

最後の 0 はプログラムの終了コードです。

6.3. main の評価

さて、上で定義した、プログラムは期待どおり、「Hello, world!」を 表示しました。しかし、どうして「Hello, world!」が表示されたのでしょう。

前の章まででは、評価したい式を、インタープリタに指示すると、その値を 求める手順にしたがって、プログラムが実行されるというようなことを書きました。

ところが、シェルのプロンプトから

% ./hello

とやったとき、その肝心の「評価したい式を、インタープリタに指示」というのは どうしたのでしょう。

インタープリタを呼出しているのは、スクリプトの最初の行の呪文だということは なんとなく解りますが、「評価したい式」をどのようにそのインタープリタに 指示しているのでしょう。

「評価<した>式」は実は、(main <args>) だったわけです。<args> は実際に スクリプトに与えられて引数のリスト(スクリプト名を含む)です。

Gauche のリファレンスマニュアルによると、

ファイルが正常にロードされたら、goshは userモジュールに `main' という手続きが 定義されているかどうか調べ、定義されていればそれを呼びます。mainには、 スクリプトへの引数のリストが唯一の引数として渡されます。 リストの最初の要素はスクリプトファイル名です。

とあります。こういう取り決めがあるので、具体的に「評価したい式」は、 (main <args>) になるように、関数 main をプログラマが定義することになります。

前節で、「main という名前であることに意味がある」と言ったのはこういう意味 だったのです。

さて、ではちょっと、ここで、以下のようなスクリプトを作って動かしてみましょう。

#!/usr/local/bin/gosh
;;-*-scheme-*-
(define (main args)
  (for-each print args))

このスクリプトを、args というファイル名で、保存し実行パーミッションを オンにしておいて、たとえば、

./args -a hoge --foo=hage bar

とやってみましょう。このスクリプトは、リスト args の要素をひとつずつ、 一行ずつに分けて印字するというものです。

% ./args -a hoge --foo=hage bar
./args
-a
hoge
--foo=hoge
bar

これを見ると、gosh のコマンドラインインタープリタは、

./args -a hoge --foo=hage bar

というコマンドラインを、空白でくぎって、args のそれぞれの要素にして、 main に渡しているということがわかります。

6.4. 例: 華氏摂氏変換

温度の単位の摂氏(C)と華氏(F)との間の変換をする極簡単なユーティリティを 作成してみましょう。

6.4.1. args はリスト

摂氏Cと華氏Fとの関係式は、

9C=5(F−32)

です。まず、華氏Fから摂氏Cを求める関数 fahrenheit->celsius を 作りましょう。

上の関係式から、

C=5(F−32)/9

ですから、

(define (fahrenheit->celsius f)
  (/ (* 5 (- f 32)) 9))

ですね。さて、例えば、華氏50度は(50°F)は摂氏何度 になるかを知りたいとき、対話モードでは、単に

gosh> (define (fahrenheit->celsius f)
        (/ (* 5 (- f 32)) 9))
fahrenheit->celsius
gosh> (fahrenheit->celsius 50)
10

となるわけですが、これをシェルから

% f2c 50
10

のようにして、使いたいわけです。そこで、main も定義した、

#!/usr/local/bin/gosh
;;-*- scheme -*-
(define (fahrenheit->celsius f)
  (/ (* 5 (- f 32)) 9))

(define (main args) 
  (fahrenheit->celsius args))

を、f2cという名前で保存し、実行パーミッションをOnにします。

% chmod +x f2c

これには、main の定義

(define (main args)
  (fahrenheit->celsius args))

が含まれています。おっと、これではだめですね。 でも、折角ですから、実験してみましょう。

% ./f2c 50
*** ERROR: operation + is not defined between -32 and ("./f2c" "50")
Stack Trace:
_______________________________________
  0  (* 5 (- f 32))
        At line 5 of "././f2c"

なぜでしょう。前節で見ましたように、main の引数 args には、 入力したコマンドラインがリストとして束縛されています。 fahrenheit->celusius は、("./f2c" "50")というリストを渡されても 処理できませんよね。

6.4.2. リスト args の要素は文字列

そのリストの最初の要素は、コマンド名です。コマンドへの最初の 引数は args の先頭の次の要素です。リストから、先頭の要素を 取り出す関数は、car で、先頭の次の要素を取り出す関数は、cadr です。 そこで、main の定義を変更して、関数 fahrenheit->celsius に コマンドの引数だけが渡されるようにしましょう。

(define (main args)
  (fahrenheit->celsius (cadr args)))

こんどはどうでしょう。

./f2c 50                              ~/KahuaProject/khead/GaucheTutorial
*** ERROR: operation + is not defined between -32 and "50"
Stack Trace:
_______________________________________
  0  (* 5 (- f 32))
        At line 5 of "././f2c"

まだだめですね。これは、どういうことかというと、リスト args の要素は、 すべて、文字列であって、数値ではないということによります。つまり、 関数 fahrenheit->celsius に渡されるのは、50 という数ではなくて、 "50" という文字列なのです。これでは計算できません。そこで、 "50"という文字列を数値の 50 に変換するために、組み込み関数 string->number を使います。

(define (main args)
  (fahrenheit->celsius (string->number (cadr args))))

これでどうでしょう。

% ./f2c 50

あれっ。エラーメッセージは出なくなりましたが、何にも表示されませんね。 どういうことでしょう。

6.4.3. 印字 print

対話モードでは、式を評価すれば、その結果が印字されました。これは、 対話モードでは、ユーザの入力を「読み(Read)」、それを「評価し(Evaluate)」、 結果を「印字する(print)」というループ(Loop)になっているからです。 しかし、シェルコマンドととして使うためには、プログラム外部へ印字すること を明示的にプログラムに書いておく必要があります。そのための手続きが、 print 手続きです。

(define (main args)
  (print (fahrenheit->celsius (string->number (cadr args)))))

と main の定義を変更しましょう。

% ./f2c 50
10

今度はちゃんと所定の動作になりました。 まとめたものは、以下のとおり、

#!/usr/local/bin/gosh
;;-*- scheme -*-

(define (fahrenheit->celsius f)
  (/ (* 5 (- f 32)) 9))

(define (main args)
  (print (fahrenheit->celsius (string->number (cadr args)))))

6.4.4. c2f 摂氏から華氏への変換

これはもう簡単ですね。

F=9C/5+32

ですから、

(define (celsius->fahrenheit c)
  (+ (/ (* 9 c) 5) 32))

を使えばよいですね。 こちらは、以下のものを、c2f などの名前で保存して、実行パーミッションをOn にすると使えます。

#!/usr/local/bin/gosh
;;-*- scheme -*-

(define (celsius->fahrenheit c)
  (+ (/ (* 9 c) 5) 32))

(define (main args)
  (print (celsius->fahrenheit (string->number (cadr args)))))

Powered by Kahua