このブログの更新は Twitterアカウント @m_hiyama で通知されます。
Follow @m_hiyama

メールでのご連絡は hiyama{at}chimaira{dot}org まで。

はじめてのメールはスパムと判定されることがあります。最初は、信頼されているドメインから差し障りのない文面を送っていただけると、スパムと判定されにくいと思います。

参照用 記事

変だよBash: 代入文

最近、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

*1:最初に出現したイコールの左側が名前になってないときは、全体をコマンド名と解釈します。例えば、x+y=3 なら、x+y=3 という名前のコマンドを探します。なんか行き当たりばったりな印象。

*2:この事実は、set -x してからコマンドを実行すると確認できます。うるさい表示を元に戻すには set +x します。