Next: Limitations of Usual Tools, Previous: Special Shell Variables, Up: Portable Shell
だめだよ全く,我々は本気なのに.制限のあるシェルもあるんです! :)
全ての組み込みコマンドやコマンドは,オプションをサポートし,そのため,ダッ
シュで始まる引数を用いると,全く異なる動作をすることを覚えておくべきです.
例えば罪の無い‘echo "$word"’でも,word
がダッシュで始まるとき
は予期しない結果となるはずです.この問題は,パイプでは‘x’を後で評価
するように,‘echo "x$word"’を使用することで避けることが可能です.
移植性の高いスクリプトは,どちらのオプションもサポートしていると仮定すべ
きではなく,どちらの動作もデフォルトと仮定すべきではありません.これは
ちょっとトリッキーで,例えば,POSIXのデフォルトの動作では,現
在の論理的なディレクトリがシンボリックリンクの場合,‘ls ..’と
‘cd ..’では異なるディレクトリを参照している可能性があります.
dirに..の要素が無い場合,cd dirを使用しても
安全です.また,Autoconfが生成するスクリプトは,ac_top_srcdir
の
ような変数を計算するとき,この問題を調査するので(see Configuration Actions),これらの変数でcdしても安全です.
pwdコマンドの議論も参照してください.
最後の‘;;’は不要ですが,使用した方が良いでしょう.
fnmatch
のバグのため,bashはバックススラッシュを文字クラ
スとして正しく処理することに失敗します.
bash-2.02$ case /tmp in [/\\]*) echo OK;; esac bash-2.02$
このコードをunixやms-dosの絶対パスとして使用したいとき,非常に 残念なことになります.このバグを回避するために,常にバックスラッシュを最 初に書いてください.
bash-2.02$ case '\TMP' in [\\/]*) echo OK;; esac OK bash-2.02$ case /tmp in [\\/]*) echo OK;; esac OK
Ash 0.3.8のように,シェルによっては空のcase
/esac
で混乱する
ものもあります.
ash-0.3.8 $ case foo in esac; error-->Syntax error: ";" unexpected (expecting ")")
多くのシェルでは,カッコで囲まれているケース文をサポートしておらず,それ は,対になっているカッコに依存しているツールを使用している我々のような人 間にとっては残念なことです.例えば,Solaris 8のBourneシェルがそうです.
$ case foo in (foo) echo foo;; esac error-->syntax error: `(' unexpected
echo
ですが,移植性の問題の根源として最も驚くべきものかもし
れません.移植性の高い‘echo’を使用することは,オプションとエスケー
プシーケンスを削除しない限り不可能です.移植性を目標とする新しいアプリケー
ションでは,‘echo’の代わりに‘printf’を使用すべきです.
オプションを期待しないでください.ECHO_N
などの,-cをシミュ
レーションする方法は,See Preset Output Variables.
引数へのバックスラッシュは,処理について同意がとれていないので使用しない でください.‘echo '\n' | wc -l’を用いれば,Digital Unix 4.0と MIPS RISC/OS 4.52のshでは答えは2になりますが, Solarisのsh,Bash,そしてZsh(のshエミュレーションモー ド)では答えは1になります.問題が本当にechoにあることに注意して ください.全てのシェルは,‘'\n'’をバックスラッシュと‘n’の組み 合わせであると解釈します.
これらの問題のため,不定の文字を含む文字列をechoに渡さないでく ださい.例えば,fooの値がバックスラッシュを含んでおらず,‘-’ で始まらないことを知っている場合,‘echo "$foo"’は安全ですが,それ以 外では以下のようなヒアドキュメントを使用すべきではありません.
cat <<EOF $foo EOF
$?
を想定しています.残念ながらBash
2.04を移植したDJGPPのように,シェルによっては‘exit 0’を実行します.
bash-2.04$ foo=`exit 1` || echo fail fail bash-2.04$ foo=`(exit 1)` || echo fail fail bash-2.04$ foo=`(exit 1); exit` || echo fail bash-2.04$
‘exit $?’を使用すると期待される動作に復帰します.
autoconfが生成するようなシェルスクリプトなどには,以前の終了状 態をクリーンアップする仕掛けを使用しているものもあります.シェルの最後の コマンドがゼロではないステータスで終了した場合も,呼び出し側がエラーの発 生を報告できるように,ゼロでないステータスで終了する仕掛けがあります.
残念ながら,Solaris 8 shのように,シェルによってはexit
コマンドの引数を無視する仕掛けが存在するものもあります.これらのシェルで
は,その仕掛けで呼び出しがプレーンのexit
によるものなのか,
exit 1
によるものなのか決定できません.exit
を直接呼び出す代
わりに,この問題を回避するためにAC_MSG_ERROR
を呼び出してください.
ああ,Solaris 2.5,irix 6.3,irix 5.2,AIX 4.1.5,そ してDigital unix 4.0のような多くのシェルは,受けとった環境変数を exportすることを忘れています.結果として,二つの変数は共存して います.環境変数とシェル変数の二つです.以下のコードは,この失敗を説明す るものです.
#! /bin/sh echo $FOO FOO=bar echo $FOO exec /bin/sh $0
環境変数で‘FOO=foo’として実行した場合,これらのシェルはそれぞれ ‘foo’と‘bar’を交互に出力しますが,‘foo’を出力した後に,続 けて‘bar’を出力します.
このため,それぞれ更新した環境変数を再びexportすべきです.
for arg do echo "$arg" done
シェルによっては,間違って解釈するので,for
と同じ行にdo
を
書いてはいけません.
for arg; do echo "$arg" done
明示的に位置に依存する引数を参照したい場合,‘$@’のバグがあるので, 以下のように使用してください.
for arg in ${1+"$@"}; do echo "$arg" done
しかし,ZshはBourneシェルエミュレーションモードでも,‘${1+"$@"}’
で単語の分離を試みるのことを覚えておいてください.‘$@’の詳細は,
Shell Substitutionsを参照してください.
if ! cmp -s file file.new; then mv file.new file fi
その代わりに以下を使用してください.
if cmp -s file file.new; then :; else mv file.new file fi
ifの終了ステータスをリセットしないシェルもあります.
$ if (exit 42); then true; fi; echo $? 42
そこでは,適切なシェルなら‘0’を出力すべきです.これは,異常終了とな るので,Makefileでは特に問題です.これが,Automakeが生成するよう な適切に書かれているMakefileがごちゃごちゃした構成になっている理 由です.
if test -f "$file"; then install "$file" "$dest" else : fi
printf %s -foo
POSIX 1003.1-2001では,pwdは,-L (“論理的”) と-P (“物理的”)オプションをサポートし,-Lがデフォル トになっている必要があります.しかし,伝統的なシェルはこれらのオプション をサポートしておらず,pwdコマンドは-Pのように動作しま す.
移植性の高いスクリプトでは,どちらのオプションもサポートしていると仮定す べきではなく,どちらの動作もデフォルトと仮定すべきではありません.また, 多くのホストは‘/bin/pwd’が‘pwd -P’と同じですが, POSIX はこの動作を要求しておらず,移植性の高いシェルではそれに 依存すべきではありません.
通常,そのままpwdを使用するのが最善でしょう.最近のホストでは, これで論理的なディレクトリ名を出力し,以下の利点があります.
cdコマンドでの議論も参照してください.
set x $my_list; shift
すべてのオプションを認識しないこととは"反対"の問題(例えば,‘set -e -x’で‘-x’をコマンドラインに割り当てるといった問題)があるシェルもあ ります.以下のように省略した方が良いでしょう.
set -ex
test
プログラムは,多くのファイルと文字列のテストを実行する方法で
す.それは別名の‘[’で呼び出されることも多いのですが,M4の引用符文字
という問題から,Autoconfのコードではその名前を使用することが要求されてい
ます.
test
を使用して複数の調査を行う必要がある場合,test
の演算子
の‘-a’と‘-o’の代わりに,シェル演算子の‘&&’と‘||’ で
組み合わせてください.System Vでは,‘-a’と‘-o’の優先順位は,単
項演算子とは間違った関係になっています.従って,POSIXはそれら
を指定しないので,それを使用すると移植性が無くなります.同じ文で
‘&&’と‘||’を組み合わせる場合,同じ優先順位があることを覚えてお
いてください.
testで‘!’を使用してもかまいませんが,ifではでき
ません.‘test ! -r foo || exit 1’.
/bin/sh
のような古いシェルでは,-hだけをサポートしていま
す.
test
は引数をオプションとして解釈するので(例えば,
‘string = "-n"’),特にstringがダッシュで始まる場合,
‘test "string"’を避けてください.
一般に信じられていることとは反対に,‘test -n string’と ‘test -z string’は,移植性があります.それにもかかわ らず,(Solaris 2.5,AIX 3.2,unicos 10.0.0.6,Digital unix 4等の)多くのシェルには信じられない優先順位があり,string がオペレータのように見える場合は混乱するかもしれません.
$ test -n = test: argument expected
危険はありますが,代わりに‘test "xstring" = x’や‘test "xstring" != x’を使用してください.
以下のような慣用句はのバリエーションは普通に見つかります.
test -n "`echo $ac_feature | sed 's/[-a-zA-Z0-9_]//g'`" && action
与えられているパターンに一致するとき動作します.そのような構文は,常に使 用を避けるべきです.
echo "$ac_feature" | grep '[^-a-zA-Z0-9_]' >/dev/null 2>&1 && action
シェルの組み込みコマンドなのでより速くなっているため,可能な場所では
case
を使用してください.
case $ac_feature in *[!-a-zA-Z0-9_]*) action;; esac
ああ,POSIXの構文‘[!...]’をサポートしていないシェルは 知りませんが,文字クラスの否定は移植性が無いかもしれません(対話的モード では,zshは‘[!...]’の構文で混乱し,‘!’のため,ヒ ストリ内のイベントを探します).多くのシェルは,構文‘[^...]’ の 代替物をサポートしていません(Solaris,Digital Unix等).
以下は解決方法の一つです.
expr "$ac_feature" : '.*[^-a-zA-Z0-9_]' >/dev/null && action
以下の方が良いかもしれません.
expr "x$ac_feature" : '.*[^-a-zA-Z0-9_]' >/dev/null && action
‘foo’がバックスラッシュを含んでいるとき問題を回避するので,
‘expr "Xfoo" : "Xbar"’は,‘echo "Xfoo" |
grep "^Xbar"’より堅牢です.
POSIXでは,この点は絶対的に明確ではありませんが,‘$?’をト ラップするとき,トラップの前の最後に実行されたコマンドの終了ステータスが 設定されるべきだということは,広く認められています.曖昧な部分は以下のよ うに要約可能です.“トラップがexitで開始されるとき,実行された 最後のコマンドは何でしょう?exitの直前ですか?それとも exit自身ですか?”
Bashはexitを最後のコマンドと考えますが,ZshとSolaris 8 shは,トラップが実行されたときexitの処理中 な ので,トラップを受信する前の終了ステータスだと考えられます.
$ cat trap.sh trap 'echo $?' 0 (exit 42); exit 0 $ zsh trap.sh 42 $ bash trap.sh 0
移植性の解決方法は簡単です.‘exit 42’にしたいとき,‘(exit 42); exit 42’を実行し,最初のexitはZshに対する42の終了ステータスを 設定するために使用され,二番目は,トラップを誘発し,Bashに対して終了ステー タスとしての42を渡すためです.
FreeBSD 4.0のシェルには,以下のバグがあります.コードが内部 trapの場合,空行で‘$?’が0にリセットされます.
$ trap 'false echo $?' 0 $ exit 0
幸運にもこのバグはtrapのみに影響します.
それらが存在しない場合,シェルは,falseに対しては正しく, trueに対しては正しくない,異常終了のステータスを生成するので, ある意味ではそのとおりです.
PS1
のような邪魔な変数を利用不可能にすることは非常に役立つので,存
在をテストし,提供されていればそれを使用し,unsetがサポー
トされていないときは,無効にする値を与えてください.
if (unset FOO) >/dev/null 2>&1; then unset=unset else unset=false fi $unset PS1 || PS1='$ '
無効にする値については,See Special Shell Variables. また,環境変数の caseについてはexportのドキュメントLimitations of Builtinsも参照してください.