Shell Script Advent Calendar 2016 の 25日目の記事です。最終日ですね。クリスマスですね。私の誕生日ですね。 今年は子供達が一緒だからさみしくないもん。
Advent Calendar 最終日のネタとして相応しくないような気もしますが、 いろいろな *[!c]sh を使って経験した「えっ? ナニソレ? おかしくね?」 と感じた嫌いな仕様を紹介してみたいと思います。登場するシェルは bash, dash, ksh (AT&T ksh), mksh, zsh です。
移植性のよいシェルスクリプトを書くのにお役立てください。
bash の嫌いなところ
set -u
時に空の配列変数展開すると強制終了 (〜 bash 4.3)
シェルオプション set -u
は未定義の変数を展開しようとすると
エラーで強制終了してくれる便利なオプションです。
変数名の typo バグを発見してくれます。
少し古い bash の場合、配列要素が空っぽの配列変数を展開しようとした場合も未定義扱いとなります。
$ bash -c 'set -u; typeset -a names; echo "${names[@]}"'
bash: names[@]: 未割り当ての変数です
$ bash -c 'set -u; names=(); echo "${names[@]}"'
bash: names[@]: 未割り当ての変数です
非常に面倒くさいですが、条件付き変数展開を利用して対策しましょう。
$ bash -c 'set -u; typeset -a names; echo ${names[0]+"${names[@]}"}'
bash 4.4 では大丈夫でした、わーい…と思ったら別のがバグってます。
$ bash -c 'set -u; typeset -a names; echo "${#names[@]}"'
bash: names: 未割り当ての変数です
こう書けば回避できます。
$ bash -c 'set -u; typeset -a names=(); echo "${#names[@]}"'
0
LANG=en_US.UTF-8
時に [A-Z]
のパス名展開が意外な文字にマッチする
メッセージ等は英語にしたい、ただし文字列は UTF-8
として扱いたいとき、ロケールを en_US.UTF-8
にするとよいです。
しかし、bash は en_US-UTF-8
のときのパス名展開が特殊で、
例えば [A-Z]
をパス名展開に用いると、A
から Z
の英字だけでなく
なんと c
から z
の英字にもマッチします。
$ mkdir tmp
$ cd tmp
$ touch a b c x y z A B C X Y Z
$ LC_ALL=C /bin/bash --noprofile --norc -c 'echo [A-Z]'
A B C X Y Z
$ LC_ALL=ja_JP.UTF-8 /bin/bash --noprofile --norc -c 'echo [A-Z]'
A B C X Y Z
$ LC_ALL=en_US.UTF-8 /bin/bash --noprofile --norc -c 'echo [A-Z]'
A b B c C x X y Y z Z
これは仕様なんだそうです。
- #531721 - bash: glob pattern “[A-Z]” matches “b”, “c” .. and “z” on LC_ALL=en_US.UTF-8 - Debian Bug report logs
bash だけでなく en_US
は罠がありそう。ロケールわからん。
- How do I make ls sort underscore characters first? - Unix & Linux Stack Exchange
近年の Linux ディストリビューションであれば C.UTF-8
というロケールが利用できるので、
en_US.UTF-8
の代わりにこれを利用しましょう。
en_US.UTF-8
が使えないほかの環境では bash 以外を利用しましょう。
遅い
bash は遅い。
- bash, ksh, zsh の速度比較 - 拡張 POSIX シェルスクリプト Advent Calendar 2013 - ダメ出し Blog
bash, dash, mksh の嫌いなところ
チルダ展開の評価順位
bash, dash, mksh はチルダ展開が変数展開の前になります。
$ echo $LOGNAME
fumiyas
$ echo ~fumiyas
/home/fumiyas
$ echo ~$LOGNAME
~fumiyas
同様に、ユーザー名部分がダブルクォートで括られていると評価されません。
$ echo ~"$LOGNAME"
~fumiyas
$ echo ~"fumiyas"
~fumiyas
ksh, zsh はいずれもチルダ展開されます。これは便利。
$ echo ~$LOGNAME
/home/fumiyas
$ echo ~"$LOGNAME"
/home/fumiyas
$ echo ~"fumiyas"
/home/fumiyas
dash の嫌いなところ
位置パラメーターが空のときに shift
すると強制終了
https://twitter.com/koie/status/707128070864392192
From: 鯉江 @koie
くっそ、またdashに罠が。 shift || true でエラー回避しようとしてもエラーで終了してしまう。 bash ashはok.
dash は位置パラメーターが空だったり位置パラメーター数を越える数だけ
shift
すると、エラーになるだけでなく、終了してしまいます。
$ dash -c 'echo $#; shift; echo end'
0
dash: 1: shift: can't shift that many
回避策1。最初の位置パラメーターが未定義なら shift
を実行しないようにします。
$ dash -c 'echo $#; ${1+shift}; echo end'
0
end
回避策2。位置パラメーターの数が 0 なら shift
を実行しないようにします。
$ dash -c 'echo $#; [ $# -gt 0 ] && shift; echo end'
0
end
read
に変数未指定だとエラー
組込みコマンド read
は、入力中の不要の行を読み捨てたり、
ユーザーとの対話を前提としたシェルスクリプトで
Enter キーの入力待ちに利用することがあります。
そのような場合、入力を変数に保存しても無駄なので read
の引数に変数名は指定したくないところですが、
dash は変数を一つも指定しないとエラーになります。
$ dash -c 'echo -n "Hit Enter key to continue..."; read'
Hit Enter key to continue...dash: 1: read: arg count
これはオリジナルの sh 由来のようです。
https://twitter.com/n_soda/status/707395548353994752
From: SODA Noriyuki @n_soda
@satoh_fumiyasu オリジナルBourne shellも $ sh -c read sh: read: missing arguments ですよ。(Solaris 9で確認)
export と同時に変数に値を設定する際にチルダ展開されない
https://twitter.com/hirose31/status/713254558605029376
From: ひろせ31 @hirose31
dashでexportつきだどチルダが展開されないのって仕様なんすかね? /bin/dash -c ‘set -x; V=~hirose31; export E=~hirose31;’ + V=/home/hirose31 + export E=~hirose31
これはバグなような気がします。
$ dash -c 'V=~fumiyas; export V; echo "$V"'
/home/fumiyas
$ dash -c 'export V=~fumiyas; echo "$V"'
~fumiyas
ksh (AT&T ksh) の嫌いなところ
組込みコマンド echo
に移植性がない
ksh の組込みコマンド echo
の仕様は OS の /bin/echo
の仕様になっています。つまり、#!/bin/ksh
でスクリプトを書いたとしても、
echo
を使ってしまうと OS のポータビリティなくなります。
参考:
- echo コマンドの違いと移植性の問題 - 拡張 POSIX シェルスクリプト Advent Calendar 2013 - ダメ出し Blog
シグナルで終了したコマンドの $?
の値が 128 + シグナル番号でない
ksh だけ 256 + シグナル番号になります。
$ dash -c 'sh -c "kill -9 \$\$"; echo $?'
Killed
137
$ bash -c 'sh -c "kill -9 \$\$"; echo $?'
bash: 1 行: 23370 強制終了 sh -c "kill -9 \$\$"
137
$ ksh -c 'sh -c "kill -9 \$\$"; echo $?'
ksh: 23354: Killed
265
$ mksh -c 'sh -c "kill -9 \$\$"; echo $?'
Killed
137
$ zsh -c 'sh -c "kill -9 \$\$"; echo $?'
137
ksh (AT&T ksh), mksh の嫌いなところ
function
の関数内での $0
の値が関数名になってしまう
ksh, mksh では function
で関数を定義すると、その関数内の $0
はスクリプト名 (起動時のコマンド名) ではなく、関数名が展開されます。
$ ksh -c 'funcname() { echo "$0"; }; funcname' progname argv1 argv2 argv3
progname
$ ksh -c 'function funcname { echo "$0"; }; funcname' progname argv1 argv2 argv3
funcname
zsh でもデフォルトは同様ですが、zsh オプション FUNCTION_ARGZERO
で変更できます。
$ zsh -c 'set -o no_FUNCTION_ARGZERO; function funcname { echo "$0"; }; funcname' progname argv1 argv2 argv3
progname
set -x
が function
関数には無効
シェルオプション set -x
を設定すると、実行するコマンドラインの内容が
$PS4
の値を先頭に付けて標準エラー出力に出力されます。
シェルスクリプトの実行トレースに便利です。
しかし ksh, mksh では、set -x
は function
の関数内は対象外となります。
$ ksh -c 'func() { echo $1 in-function; }; set -x; func 1; func 2'
+ func 1
+ echo 1 in-function
1 in-function
+ func 2
+ echo 2 in-function
2 in-function
$ ksh -c 'function func { echo $1 in-function; }; set -x; func 1; func 2'
+ func 1
2 in-function
+ func 2
2 in-function
ksh, mksh で function
関数に set -x
相当を有効化するには、
別途 typeset -ft 関数名
を実行してやる必要があります。
typeset +f
で関数名をすべて得られるので、次のようにすれば一括して適用できます。
ただし、これ以降に新たに定義した関数には適用されないので注意。
typeset -ft $(typeset +f)
zsh の嫌いなところ
$status
が予約されている
スクリプトで $status
という変数を利用したいことがありますが、
zsh では予約されていて使えません。
$ zsh -c 'status=0'
zsh:1: read-only variable: status
echo
がデフォルトでエスケープシーケンスを解釈する
zsh の echo
はデフォルトでエスケープシーケンスを解釈します。
ほかの実装とは異なるので注意。
参考(再掲):
- echo コマンドの違いと移植性の問題 - 拡張 POSIX シェルスクリプト Advent Calendar 2013 - ダメ出し Blog
対話シェル時に [!abc]
でヒストリー展開しようとする
パス名展開の文字クラスの否定の記述は [!...]
という形式になりますが、
zsh は対話シェルのとき (コマンドヒストリーが有効のとき) に
!...]
の部分をヒストリー展開しようとします。
$ echo [!abc]
zsh: event not found: abc]
コマンドヒストリーが無効なシェルスクリプトでは大丈夫なので、
あまり大きな問題ではありせん。
対話シェル時は POSIX sh とは非互換ですが [^...]
形式を使いましょう。
ところで、12月25日はクリスマスな上に、
OSS 界隈で地味に活躍されているふみやすさんの誕生日ですね!
っ http://www.amazon.co.jp/registry/wishlist/27M7TV8CEEF6G?sort=priority
逆に、あなたの書いた OSS や Blog や Advent Calendar が気に入ったら何か送りたく なってしまうかもしれないので、プロフィールや Web サイトに あなたの Amazon 欲しいものリストの URL を貼っておいてくださいね!
私が勤める OSSTech っていう某弊社で社員募集しているようです。 人材紹介会社を介さなければ、入社後に 20万円のボーナス! 「ふみやすっていう人に紹介された」と言ってもらえると私にもボーナス!! → https://www.osstech.co.jp/recruit/
よろしければ、これまで参加した/参加予定のほかの Advent Calendar もどうぞ。
- Ansible Advent Calendar 2023
- シェル芸 Advent Calendar 2023
- 闇の魔術に対する防衛術 Advent Calendar 2023
- Ansible Advent Calendar 2023
- Ansible Advent Calendar 2020
- DNS温泉 Advent Calendar 2019
- OSSTech Advent Calendar 2019
- Ansible Advent Calendar 2018
- OSSTech Advent Calendar 2018
- Debian/Ubuntu Advent Calendar 2017
- Linux Advent Calendar 2017
- Shell Script Advent Calendar 2017
- Shell Script Advent Calendar 2016
- OpenLDAP と仲間たち Advent Calendar 2015
- Postfix Advent Calendar 2014
- 拡張 POSIX シェルスクリプト Advent Calendar 2013