Next: , Previous: File System Conventions, Up: Portable Shell


10.5 シェルの代入

persistent urban legendとは反対に,Bourneシェルは変数とバッククオートさ れている式が整然と分かれておらず,特に右側の割り当てとcaseの引数 がそうです.例えば,以下のコードを考えます.

     case "$given_srcdir" in
     .)  top_srcdir="`echo "$dots" | sed 's,/$,,'`"
     *)  top_srcdir="$dots$given_srcdir" ;;
     esac

以下のように書くと,より読みやすくなります.

     case $given_srcdir in
     .)  top_srcdir=`echo "$dots" | sed 's,/$,,'`
     *)  top_srcdir=$dots$given_srcdir ;;
     esac

そして,実際それはより移植性が高くなります.最初の試みの最初の caseで,全てのシェルが"`..."..."...`"を正しく解釈する わけではないので,top_srcdirの計算結果は移植性が高くありません. 更に悪いことには,同様に"`...\"...\"...`"を全てのシェ ルが解釈するわけではありません.二重引用符でバッククオートされている式の 内部で,二重引用符で囲まれた文字列を使用するための移植性を高める方法は全 くありません(pfew!).

$@
最も有名なシェルの移植性の問題の一つは,‘"$@"’との関連です.位置に 依存する引数が無いとき,POSIXでは‘"$@"’を何もないことと 等価になっていますが,オリジナルのUnixバージョン7のBourneシェルはその代 わりに‘""’として扱い,この動作はDigital Unix 5.0のようにそれ以降の 実装でも提供されています.

この移植性の問題を回避する伝統的な方法は,‘${1+"$@"}’を使用する ことです.残念ながら,この手法はMac OS Xでも使用されている,Zsh (3.x と 4.x)では動作しません.Bourneシェルをエミュレートしているとき,Zshは ‘${1+"$@"}’で単語の分離を実行します.

          zsh $ emulate sh
          zsh $ for i in "$@"; do echo $i; done
          Hello World
          !
          zsh $ for i in ${1+"$@"}; do echo $i; done
          Hello
          World
          !

Zshは,プレーンの‘"$@"’をおそらく処理しますが,上記の移植性の問題 のため,プレーンの‘"$@"’を使用することはできません.回避する方法の 一つは,‘${1+"$@"}’を‘"$@"’に変換するZshの“global aliases”に依存します.

          test "${ZSH_VERSION+set}" = set && alias -g '${1+"$@"}'='"$@"'

より保守的な回避方法は,位置に依存する引数を用いなくても良い限り, ‘"$@"’を避けることです.例えば,以下の代わりを考えます.

          cat conftest.c "$@"

この代わりに以下を使用することが可能です.

          case $# in
          0) cat conftest.c;;
          *) cat conftest.c "$@";;
          esac

${var:-value}
Ultrix shを含め,古いBSDシェルはシェルの代入に対してコ ロンを受け入れず,文句を言って終了します.
${var=literal}
引用符で囲まれていることを確かめてください.
          : ${var='Some words'}

それ以外のDigital Unix V 5.0のようなシェルでは,“bad substitution”のた めに終了します.

     
     
Solarisの/bin/shにはこの解釈に恐ろしいバグがあります.変数を ‘}’を含む文字列に設定する必要があることを想像してください.この ‘}’文字で,影響ある変数が既に設定されているとき,Solarisの /bin/shは混乱します.このバグは,以下のように実行することで作 動されるはずです.
          $ unset foo
          $ foo=${foo='}'}
          $ echo $foo
          }
          $ foo=${foo='}'   # no error; this hints to what the bug is
          $ echo $foo
          }
          $ foo=${foo='}'}
          $ echo $foo
          }}
           ^ ugh!

}’は,シングル引用符で囲まれている場合でも,‘${’に一致する ものとして解釈されているようです.二重引用符を使用すると問題は生じません.

${var=expanded-value}
Ultrixで,以下を実行したとします.
          default="yu,yaa"
          : ${var="$default"}

それはvarを‘M-yM-uM-,M-yM-aM-a’に設定し,すなわち,全ての文字 の八番目のビットがセットされるでしょう.‘$var’を展開するとき,シェ ルが八番目のビットを明示的にリセットするので,単純に‘echo $var’を使 用している現象が分かりません.このシェルにその違反で混乱させる二つの方法 は,以下のようになります.

          $ cat -v <<EOF
          $var
          EOF

それと以下です.

          $ set | grep '^var=' | cat -v

このバグの古典的で典型的なものの一つは以下のものです.

          default="a b c"
          : ${list="$default"}
          for c in $list; do
            echo $c
          done

単一行に‘a b c’を得るでしょう.なぜでしょうか?それは,‘$list’ にスペースが無いためです.‘M- ’,すなわち八ビット目を設定するスペー スがあるので,IFSによる分離が実行されないのです!!!

良いニュースの一つは,Ultrixが‘: ${list=$default}’で正確に動作す ることです.すなわち,引用符で囲まない場合です.悪いニュースとし ては,QNX 4.25は,listdefault最後の項目 に設定することです!

移植性の高い方法は,Ultrixで八番目のビットを二回切替えるために,二重(引 用符による)代入を使用することです.

          list=${list="$default"}

...しかし,Solarisの‘}’のバグ(上記を参照してください)には用心 してください.安全にするには,以下を使用してください.

          test "${var+set}" = set || var={value}

`commands`
一般的には意味がありませんが,Ash 0.2では最適化のためコマンドを実行する ためサブシェルをforkしないので,副作用のある単一の組み込み物を代入しない でください.

例えば,cdが何も出力しないことを調査したい場合,以下のことが生 じるかもしれないので,‘test -z "`cd /`"’を使用しないでください.

          $ pwd
          /tmp
          $ test -z "`cd /`" && pwd
          /

foo=`exit 1`’の結果は,読者への演習問題として残しておきます.

$(commands)
この構成は,‘`commands`’を置換するという意味があります.それ らを入れ子状にすることは可能ですが,バッククオートを用いて移植することは 不可能です.残念ながら,まだ全体的にサポートされていません.特に,現在の Solarisリリースでもサポートされていません.
          $ showrev -c /bin/sh | grep version
          Command version: SunOS 5.8 Generic 109324-02 February 2001
          $ echo $(echo blah)
          syntax error: `(' unexpected

また,irix 6.5のBourneシェルもサポートされていません.

          $ uname -a
          IRIX firebird-image 6.5 07151432 IP22
          $ echo $(echo blah)
          $(echo blah)

$(commands)’を使用する場合,異なる表記方法 ‘$((expression))’は現在のシェルではコマンドではなく数式だと勘 違いするので,コマンドがカッコで始まらないように確かめて下さい.この勘違 いを避けるため,二つの開カッコの間にはスペースを挿入して下さい.