最近、bashをよくいじっています。今更ながらではありますが、bashの奇妙な構文や挙動にはウンザリします。だからといって、bashが嫌いになるわけでもないですけどね。
アレはトークンじゃなかろうか
bashの代入文、アレ、変じゃないですか。bashに限らず、POSIXシェルは同じ構文ですけど。x = 3 だと代入文にならないのです。x ってコマンドを実行しようとします。実験してみましょう。
$ x = 3 -bash: x: command not found $ # xという名前のコマンドを作る $ echo '#/bin/bash' > x # bashで実行 $ echo 'echo "$@"' >> x # 引数を表示 $ chmod +x x # xを実行可能にする $ PATH=$PATH:. # カレントディレクトリをパスに加える $ x = 3 = 3 $ x =3 =3 $
次に、x= 3 です。x= 3 が思惑と違うのは、x = 3 とは事情が違います。x= は代入文になってはいます。これは、x='' と同じで、変数xに空文字列が代入されます。その後で、コマンド3を実行しようとするのです。
$ x= 3 -bash: 3: command not found $
x=3 とすれば、xに3が代入されるのですが、この形の代入の指令って、文や式ではなくて、トークンじゃないかと思います。つまり、bashの代入は代入文でも代入式でもなくて代入トークン。
歴史を調べたわけじゃないので僕の憶測ですが; x=3 を一塊のモノ(それをトークンと呼ぶ)として読み込んで、「イコール記号があったら特別扱いする」という構文解析していたんでしょう。
x=y=3 とすると、変数xに文字列'y=3'が代入されます。左から見て最初のイコール記号が特別扱いなんですね*1。x=3 というコマンドを実行したいときはどうすればいいのでしょう。やってみます。
$ cp x x=3 $ 'x=3' hello world hello world $
クォートすればいいわけです。
アレはコマンドの飾りだったのじゃなかろうか
先ほどの x= 3 をもう一度話題にします。
$ x=3 $ echo $x 3 $ x= 3 -bash: 3: command not found $ echo $x 3 $
ちょっと変だと思いませんか。x= 3 で x='' と同じ代入が起きたはずなのに、xの値が変更されてません。しかし、
$ x= $ echo "--$x--" ---- $
代入の後にコマンドが続くときは、変数の変更はその行だけに限定されます。代入だけなら、変数の変更は残ります。これも奇妙な仕様ですね。
またしても憶測ですが、一時的な変数の変更のほうが主たる用途だったような気もします。しかも、内部使用のシェル変数より環境変数(エクスポートされるシェル変数)の変更をしたかったのでしょう、憶測だけど。
$ date Thu Oct 15 13:26:56 JST 2015 $ echo $LANG en_US.UTF-8 $ LANG=ja_JP.UTF-8 date 2015年 10月 15日 木曜日 13:27:02 JST $ echo $LANG en_US.UTF-8 $
環境変数によりコマンドの挙動を変えたい、けど、環境に影響を残したくない、そんなときに便利です。
ところで、次の現象を不思議に思いませんか。
$ echo $LANG en_US.UTF-8 $ LANG=ja_JP.UTF-8 echo $LANG en_US.UTF-8 $
僕は「はぁー?」と思いました。これは、「LANG=ja_JP.UTF-8 echo $LANG」というコマンドライン全体が、シェルにより変数展開されて、それから実行されるせいです。変数展開は、LANG=en_US.UTF-8 の状態で実行されるので、「LANG=ja_JP.UTF-8 echo LANG=en_US.UTF-8」が実行されて上記の結果になります*2。
行頭の LANG=ja_JP.UTF-8 が、後続するコマンドにちゃんと影響していることはdateの例で分かっていますが、printenvで環境変数を表示できます。
$ echo $LANG en_US.UTF-8 $ LANG=ja_JP.UTF-8 printenv LANG ja_JP.UTF-8 $
変数展開との関係
コマンドの引数に変数参照を使えるのはもちろんですが、コマンド名も変数で与えることができます。
$ arg='+%Y-%m-%d' $ date $arg 2015-10-15 $ cmd=date $ $cmd $arg 2015-10-15 $
それでは、代入トークンを変数に入れてから展開したらどうでしょう。
$ lang_ja=LANG=ja_JP.UTF-8 $ echo $lang_ja LANG=ja_JP.UTF-8 $ $lang_ja date -bash: LANG=ja_JP.UTF-8: command not found $
変数から展開された文字列は、あたかもクォートされているように代入の解釈からブロックされるようです。リダイレクトなどの解析も同様です。
$ date > date.txt $ cat date.txt Thu Oct 15 13:45:22 JST 2015 $ redirect='>date.txt' $ echo $redirect >date.txt $ rm date.txt $ date $redirect date: invalid date `>date.txt' $
イコール記号やリダイレクト記号(不等号)を見つけて解釈するのは変数展開の前であって、変数展開が終わったら、もうその解釈はしないのです。
特殊な意味を持つ記号がイッパイあって、その解釈のタイミングが何段階にも分かれており、いつどこで解釈されるのか(解釈されないのか)把握し切れないのが、bashを難しくしている要因のひとつです。
もともとはお手軽で簡単に使えるスクリプト言語だったのが、建て増しを繰り返した結果、一貫性に欠けた難しい言語になってしまいましたね >bash 。