シェルスクリプトはどうやって自分自身のファイル名を拾うか

前回はシェルスクリプトから数値計算するのにbcを使ったけど、 数値計算用のスクリプト言語としてcalcというのがある。
プログラミング言語として面白い特徴がいくつかあったので書いてみる。

オプションとして-fフラグというフラグがある。
このフラグは #!/usr/bin/calc のようにシェバンでcalcを実行する場合に指定し、しかもオプションの中でも最後に指定しろ、となっている。

なぜこんなことになっているかというとシェバンが書かれているファイルのファイル名がほしいから。

通常、unixのシェルからコマンドを呼び出すとシステムがexecシステムコール呼び出し時に引数リストの先頭に呼び出されたコマンドのパスを付加する。

試しにCで以下のようなプログラムを書いて、実行ファイル名をshbangとしてみると

#include <stdio.h>

int main(int argc, char *argv[]) {
    printf("%d\n", argc);
    printf("%s\n", argv[argc - 1]);

    return 0;
}

$ ./shbang
1
./shbang

しかし、以下のようにシェルスクリプトのシェバンを通じて呼び出されると

$ cat shbang.sh
#!/path/to/shbang
$ ./shbang.sh
2
./shbang.sh

つまりこれは結果としてシェルから以下のコマンドを実行したのと等しい。

$ /path/to/shbang ./shbang.sh

通常、perlやrubyのようなスクリプトを実行するときはコマンドライン引数の最後で実行するファイル名を指定するけど、シェバンを使って起動されたときは起動された自分自身のファイル名が自動的に付加されているので、ファイル名を指定したのと同じ効果があるわけだ。

あ、この話って別にcalcの話じゃないですね。

Bashのプロセス置換で並列実行 & 待ち合わせ

シェルスクリプトで時間のかかる処理を並列で実行したい。
さらに各実行結果を取り出して順に並べるのにはBashのプロセス置換を使うと便利。

プロセス置換というのは本来ファイル名を書くところに一連のコマンドを記述できる機能。

<( command list... )
でcommand list...の結果をファイルのように読み込み、

>( command list... )
でcommand list...にファイルのように渡すことができる。

例えば、円周率を求めるのに高野の公式の4項を並列で計算して
最後に各項の和を求める処理は以下のような感じ(計算本体はbcで実行)。


#!/bin/bash
cat <(echo 'print sum(1/49,         -(49^2)),  "*48+"'  | bc -q pi10000.bc) \
    <(echo 'print sum(1/57,         -(57^2)), "*128-"'  | bc -q pi10000.bc) \
    <(echo 'print sum(1/239,       -(239^2)),  "*20+"'  | bc -q pi10000.bc) \
    <(echo 'print sum(1/110443, -(110443^2)),  "*48\n"' | bc -q pi10000.bc) | bc -q

pi10000.bc
scale = 10005;

define sum(t, c) {
  auto n, s

  for (n = 1; t != 0; n += 2) {
    s += t / n
    t /= c
  }

  return s
}
[参考:高野の公式]
http://ja.wikipedia.org/wiki/%E3%83%9E%E3%83%81%E3%83%B3%E3%81%AE%E5%85%AC%E5%BC%8F

このように一時ファイルを使わずにパイプのように使える。
プロセス置換はkshやzshでも使えるみたい。

Cでcompareが-1,0,1を返す理由

JavaのAPIリファレンスでBigDecimalのcompareToを見ていて気づいたのだけれど、
BigDecimalの値の大小を比較する際には以下のように行えとある。
BigDecimal bd = ...

...

// bd > 1
if (bd.compareTo(BigDecimal.ONE) > 0) {

// bd == 1
} else if (bc.compareTo(BigDecimal.ONE) == 0) {

// bd < 1
} else if (bc.compareTo(BigDecimal.ONE) < 0) {

...

特に驚くようなこともないけれど、一歩引いて考えてみると
記法としてはとても巧妙なものだと気づく。
つまり、右辺を0にすると比較演算子がそのままcompareToの比較関係を表しているのだ。
演算子オーバーロードに似ていると言ったら言いすぎだろうか。

Cでstrcmpやmemcmpが-1,0,1を返すのはまあそんなもんかなくらいに思っていたけど、
なんだか理由がわかった気がする。