シェルスクリプトのキモいところ

シェルスクリプトインタプリタを作ろうかと、シェルスクリプトの仕様を調べています。気持ちの悪い仕様をいくつか見つけました。仕様書*1を見ながら、dash で試しました。

丸括弧と波括弧のふるまいが違う。

丸括弧はサブシェル、波括弧はコマンドのグルーピングをするための似たような文法ですが、ふるまいが異なるので戸惑います。

$ (echo hello)
hello

丸括弧はコマンドの前後にスペースも要らず、丸括弧内のコマンドが実行されます。

$ {echo hello}
{echo: not found
$ { echo hello }
Syntax error: end of file unexpected (expecting "}")
$ { echo hello; }
hello

しかし、波括弧はコマンドの前にスペースか改行をを入れて、コマンドの後ろにはセミコロンか改行を入れる必要があります。波括弧は優先順位が低くてコマンド名や引数として見なされてしまいます。きっと、波括弧は「あとづけ」なのでしょう。

リダイレクトだけでコマンドになる。
$ >abc
$ ls abc
abc

リダイレクトだけでコマンドとして成立します。

引数とリダイレクトは順不同である。
$ echo a 1> A b 2> B 3> C c
$ cat A
a b c
$ ls
A  B  C

引数とリダイレクトを混ぜてもよい。

$ 1> A echo 1 2 3
$ cat A
1 2 3

リダイレクトはコマンドの前に持ってくることもできます。

条件式の末尾に & が使える。

シェルスクリプトの if 文の条件式はただのコマンドですが、その中でバックグラウンドジョブを作る & が使えてしまいます。

$ if false& then echo hello; fi
hello
$ 
[1] + Done(1)                    false

この場合、条件式は真になります。

関数定義に if 文、 for 文、 case 文などが使える。

シェルスクリプトの関数の中身は通常、波括弧でくくったグループで書きます。

func() {
    echo hello
}

実は、丸括弧で書くこともできます。

func() (
    echo hello
)

ここまではそんなに驚かないのですが、if 文、for 文、while 文、case 文などを関数の定義の後ろにいきなり書いても正しいです。

func() if true; then
    echo hello
fi

func() for i in 1; do
    echo hello
done

 func() case A in 
    A) echo hello
esac

dash だと、単コマンドでも関数を定義できました。bash は無理でした。POSIX の仕様的には bash の方が正しそうです。

func() echo hello
関数定義にリダイレクトが付けられる。

どこで使うのだろう?

func() {
    echo hello
} > /dev/null