[ << ] [ >> ]           [Top] [Contents] [Index] [ ? ]

10. 移植性のあるシェルプログラミング

独自の調査を書いているとき,コードを移植性の高いものにするため,使用を避 けるべきシェルスクリプトプログラムのテクニックもあります.Bourneシェルと, BashとKornシェルのような上位互換性があるシェルは,何年もかけて進展しまし たが,問題を避けるために,UNIXバージョン7の以降の1977年頃に加えられ た機能を利用しないでください(see section システム).

シェル関数,エイリアス,無効な文字クラスや,Bourneシェル互換のものでは見 つからないすべての機能を使用するべきではありません.最小公倍数に制限され てます.unsetさえ,全てのシェルではサポートしていません!また,以 下のように,インタプリタ仕様として,感嘆符の後にスペースを含めてください.

 
#! /usr/bin/perl

パスの前のスペースを省略する場合,(DYNIXのような)4.2BSDを基本 とするシステムは,`#! /'は4バイトのマジックナンバーとして解釈される ので,その行を無視します.古いシステムでは,`#!'行の長さにも小さな 制限があり,例えばSunOS 4では,(改行を含めず)32バイトになります.

configureスクリプトで実行すべき外部プログラムの設定は,かなり 小さくなっています.リストは,See (standards)Utilities in Makefiles section `Utilities in Makefiles' in GNU Coding Standards. この制限で, ユーザは,かなり小さいプログラム設定から残りをビルドすることが可能になっ ていて,パッケージ間の独立部分を多くし過ぎることを避けることができます.

これらの外部ユーティリティには,移植性の高い機能のサブセットがあります. 通常のツールの制限を参照してください.

シェルに関するドキュメントのソースは他にもあります.例えば, the Shell FAQsを参照し てください.


10.1 シェル

いくつかのシェルのファミリーがあり,最も重要なものは,Bourneファミリーと Cシェルファミリーで,それらは全く互換性がありません.移植性の高いシェル スクリプトを書きたい場合,Cシェルファミリーのメンバーは避けてください. the Shell difference FAQには,Unixシェルの小さな歴史と,それらの間の比較が 書かれています.

以下で,Bourneシェルファミリーのメンバーを,いくつか説明していきます.

Ash

ashは,動作の軽いBourne互換シェルとしてGNU/Linuxと BSDシステムでよく使用されています.Ash 0.2には0.3.xシリーズで 修正されているバグがいくつかありますが,バージョン0.2は多くの GNU/Linux 配布物で配布されているので,移植性の高いシェルスクリ プトではそれを回避すべきです.

Ash 0.2での互換性のため以下のようにしてください.

Bash

bashを実行しているかどうかを検出するために, BASH_VERSIONが設定されているかどうかをテストしてください.その拡 張を利用不可能にし,POSIX互換性を要求するため,`set -o posix' を実行してください.詳細は,See (bash)Bash POSIX Mode section `Bash POSIX Mode' in The GNU Bash Reference Manual.

Bash 2.05とそれ以降

バージョン2.05とそれ以降のbashは,set組み込みコマン ドの出力に対して,その出力をより容易に評価できるように設計されているので, 異なる書式を使用しています.しかし,この出力はそれ以前のバージョンの bash(や,おそらくそれ以外の多くのシェル)と互換性がありません. そのため,bash 2.05やそれ以上のものをconfigureの実行 に使用している場合,それ以外のすべてのビルドの作業に対しても,同じように bash 2.05を使用する必要があるでしょう.

Ksh

KornシェルはBourneファミリーと互換性があり,POSIXにほとんど準 拠しています.一般的に`ksh88'と`ksh93'という二つの有名な変種が あり,最初のリリースの年で後から命名されました.それらは通常 kshと呼ばれていますが,Solarisシステムには三つの変種があります. /usr/bin/kshは`ksh88'です. /usr/xpg4/bin/shはPOSIX準拠の`ksh88'の類似物で す. /usr/dt/bin/dtkshは`ksh93'です./usr/bin/kshが Soralisの標準です.それ以外の類似物はオプションパッケージの一部です.こ れらのパッケージには追加の変更はありませんが.それらは最小限のOSのインス トールの一部ではないので,インストール状況によっては無いかもしれません. パブリックドメインの`pdksh'と呼ばれているKornシェルのクローンも広く 利用可能になっています.それは,いくつか独自のものがありますが `ksh88'の機能のほとんどがあります.

Zsh

zshが実行されているかどうかを検出するために, ZSH_VERSIONが設定されているかどうかをテストしてください.デフォル トで,zshはBourneと互換性はありません.`emulate sh'を実行し,NULLCMDを`:'に設定する必要があります.詳細は, See (zsh)Compatibility section `Compatibility' in The Z Shell Manual.

Zsh 3.0.8は,Mac OS X 10.0.3でのネイティブな/bin/shです.

Russ AllberyとRobert Lipeの間でなされた,以下の議論は読む価値があります.

Russ Allbery:

/bin/shが唯一のシェルであるというGNU仮定では,永久に 行き詰まってしまいます.ベンダーは,ユーザの既存のシェルスクリプトを壊し たくはありませんし,BourneシェルにはPOSIXシェルと完全に互換で はない部分もあります.このため,この方法を採用するベンダーは,決し て (OK…"決して,決してとは言わないよ")Bourneシェルを (/bin/shとして)POSIXシェルで置き換えないでしょう.

Robert Lipe:

これは本当に問題です.ほとんどのもの(少なくともほとんどのSystem V)はシェ ル関数を受け入れるBourneシェルがあるのですが,ほとんどのベンダーの /bin/shはPOSIXシェルではありません.

そのため,ほとんど現在のシステムはPOSIX標準に適合しているシェ ルがどこかにあるのですが,問題はそれを見つけることです.


10.2 ヒアドキュメント

`\'は,次のシンボルと一緒になって特別の意味を持たないので,維持され る`\'に依存しないでください.OpenBSD 2.7のネイティブな /bin/shでは,`\"'は`"'に展開され,ヒアドキュメントで は引用符で囲まれていない分離子として用いられます.一般的な規則として, `\\'が`\'に展開される場合,`\'を得るために`\\' を使 用してください.

OpenBSD 2.7の/bin/shでは,以下のようになります.

 
$ cat <<EOF
> \" \\
> EOF
" \

そして,Bashでは以下のようになります.

 
bash-2.04$ cat <<EOF
> \" \\
> EOF
\" \

多くの古い(Bourneシェルを含む)シェルでは,ヒアドキュメントは非効率に実装 されています.大きなヒアドキュメントを間違って扱うシェルもあります.例え ば,Solaris 8 dtkshksh M-12/28/93dで提供されていて, ヒアドキュメントを1024バイトのバッファの境界で間違った変数の展開を生じま す.ユーザは一般的に,より速くより信頼性の高いシェルを使用して,これらの 問題を修正することが可能で,例えば,そのまま`./configure'するのでは なく,コマンド`bash ./configure'を使用します.

シェルによっては,単一の文の中にヒアドキュメントが多過ぎるとき,非常に非 効率になるものもあります.例えば,`configure.ac'に以下のようなもの 含めたとします.

 
if <cross_compiling>; then
  assume this and that
else
  check this
  check that
  check something else
  …
  on and on forever
  …
fi

シェルは,その中のそれぞれのヒアドキュメントに対して一時ファイルを作成し ながら,if/fiの文脈全体をパースします.forkごとにそ のようなヒアドキュメントに対してリンクを作成するシェルもあり,インストー ルされた後のクリーンアップコードで正しく削除されます.それは,シェルが永 久に受け入れられるリンクを作成しているのです.

if/fiの外部のテストを移動したり,複数のif/fi の文脈を作成したりすることで,かなり動作が改善されるでしょう.とにかく, こういった構成は,典型的なAutoconfの使用では正しくありません.実際,M4マ クロは,シェルの条件文を見ることができないので,それは推奨されておらず, 条件分岐の前にそれが展開され,実行時に条件文が失敗だと分かるとき,マクロ 展開に失敗するかもしれず,マクロの実行を完全に終了できないでしょう.


10.3 ファイルディスクリプタ

システムによっては,明らかに不可解なのですが,特殊な目的で使用しているた め,ファイルディスクリプタには使用すべきではないものもあります.

 
3 --- それを`/dev/tty'として開くシステムもあります.
4 --- Kubota Titanで使用されています.

Ultrixでは異常終了だと告げられるので,同じファイルディスクリプタに複数回 同じファイルをリダイレクトしないでください.

 
ULTRIX V4.4 (Rev. 69) System #31: Thu Aug 10 19:42:23 GMT 1995
UWS V4.4 (Rev. 11)
$ eval 'echo matter >fullness' >void
illegal io
$ eval '(echo matter >fullness)' >void
illegal io
$ (eval '(echo matter >fullness)') >void
Ambiguous output redirect.

それぞれの場合で,期待される結果はもちろん,`matter'を含んでいる `fullness'と,空の`void'です.

コマンドの代入のリダイレクトを標準エラー出力にしないでください.それは, コマンドの代入の内部で行なう必要があります.エラーメッセージを削 除することを期待して`: `cd /zorglub` 2>/dev/null'を実行しているとき, `: `cd /zorglub 2>/dev/null`'は正しく動作します.

(AshでもBashでもない)Zshが割当を可能にすることに注意する価値はあります. `foo=`cd /zorglub` 2>/dev/null'.

ほとんどのシェルでは,(Bash,Zsh,Ashを含め)全てではありませんが,標準エ ラー出力を,サブシェルに対しても追跡しています.内部コマンドの標準エラー 出力を得る目的がある場合,これでは結果が望まない内容になるかもしれません.

 
$ ash -x -c '(eval "echo foo >&2") 2>stderr'
$ cat stderr
+ eval echo foo >&2
+ echo foo
foo
$ bash -x -c '(eval "echo foo >&2") 2>stderr'
$ cat stderr
+ eval 'echo foo >&2'
++ echo foo
foo
$ zsh -x -c '(eval "echo foo >&2") 2>stderr'
# Traces on startup files deleted here.
$ cat stderr
+zsh:1> eval echo foo >&2
+zsh:1> echo foo
foo

様々なレベルの詳細が分かるでしょう....

一つの回避方法は,興味がない行をgrepで削除することで,良い行は削除しない ことを期待しつつ....

`exec >foo; mv foo bar'のように,開いているファイルの移動/削除の試 みはしないようにしてください.mvの詳細は,シェル組み込みの制限を参照してください.


10.4 ファイルシステムの条件

autoconfとその仲間達は,通常様々なUnixで実行されますが,それは その他のシステムでも使用され,最も顕著なものとしてはDOSの仲間 があげられます.このことは,ファイルとパス名に関する仮定に衝突します.

例えば,以下のようなコードを考えます.

 
case $foo_dir in
  /*) # Absolute
     ;;
  *)
     foo_dir=$dots$foo_dir ;;
esac

それらのシステムではドライブスペックを使用していて,通常はディレクトリの 分離子としてバックスラッシュを使用しているため,絶対パスを正しく検出する ことに失敗するでしょう.絶対パスに対する調査の標準的な方法は以下のとおり です.

 
case $foo_dir in
  [\\/]* | ?:[\\/]* ) # Absolute
     ;;
  *)
     foo_dir=$dots$foo_dir ;;
esac

適切な場合は角カッコの引用符で囲み,最初の文字としてのバックスラッシュを 保持していることを確認してください(see section シェル組み込みの制限).

また,コロンがデバイス指定の一部として使用されているので,これらのシステ ムではそれをパスの分離子として使用していません.パスを作成しているときや パスにアクセスしているときは,代わりにPATH_SEPARATOR出力変数を使 用してください.configureは,開始時にこれを適切な値(`:' または`;')に設定します.

ファイル名にも余計な注意が必要になります.(DJGPPのような) autoconfを十分に実行できるUnixのようなDOSベースの環 境では,通常長いファイル名を適切に扱うことが可能ですが,パッケージを壊し てしまう深刻な制限も残っています.これらの問題のいくつかは, doschk パッ ケージで容易に検出することが可能です.

以下は簡単な全体像です.問題には,適用を示すためSFN/LFNで印が ついています.SFNは,Windows下のDOS窓ではなく,プレーンな DOSにのみ関連する問題を意味し,一方LFNは,Windowsでも存在 する問題を意味しています.

複数のドットの禁止 (SFN)

DOSはファイル名に複数のドットがあるものを扱うことが不可能です. これは,autoconfは`.in'をテンプレートファイルの接尾子に使 用するので,移植性の高いconfigureスクリプトを構築しているときに覚えてお く必要がある,特に重要なことです.

以下はUnix上では完全にOKです.

 
AC_CONFIG_HEADERS([config.h])
AC_CONFIG_FILES([source.c foo.bar])
AC_OUTPUT

しかし,それは`config.h.in',`source.c.in',そして `foo.bar.in'が必要になるので,DOSでは問題があります.パッ ケージをDOSベースの環境でより移植性を高くするため,その代わり に以下を使用すべきです.

 
AC_CONFIG_HEADERS([config.h:config.hin])
AC_CONFIG_FILES([source.c:source.cin foo.bar:foobar.in])
AC_OUTPUT
ドットの前置の禁止 (SFN)

DOSはドットで始まるファイル名を扱うことが不可能です.これは通 常,autoconfに対してはあまり重要ではない問題です.

大文字小文字を区別しない (LFN)

DOSは大文字小文字を区別しないので,例えば,`INSTALL'とい う名のファイルと`install'という名のディレクトリの両方を持つことがで きません.これは,makeにも影響します.ディレクトリに `INSTALL'という名のファイルがある場合,`make install'は (`install'がPHONY として印がついていないとき)何もしません.

8+3の制限 (SFN)

DOSファイルシステムでは,ファイル名の最初の8文字と最初の3文字 の拡張子のみ保存され,それらはユニークである必要があります.それは, `foobar-part1.c',`foobar-part2.c',そして `foobar-prettybird.c'の全ては同じ名前(`FOOBAR-P.C')になります. `foo.bar'と`foo.bartender'も同じものになります.

注意:これは通常,ファイル名をユニークにするために短いバージョンでは数字 の後置を使用するので,Windowsでは問題になりません.しかし,レジストリの 設定でこの動作を停止可能です.これで長いファイル名を含むファイルのツリー を,SFNLFNの環境で共有することが可能になりますが,上記の問題 は同様に存在します.

無効な文字

DOSファイル名で無効な文字もあり,そのため避けた方が良いでしょ う.LFNの環境では,`/',`\',`?',`*',`:', `<',`>',`|',そして`"'です.SFN環境では,それ 以外にも無効になります.これには,`+',`,',`[',そして `]'が含まれます.


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))'は現在のシェルではコマンドではなく数式だと勘 違いするので,コマンドがカッコで始まらないように確かめて下さい.この勘違 いを避けるため,二つの開カッコの間にはスペースを挿入して下さい.


10.6 代入

列にいくつかの変数を設定するとき,評価の順序が定義されていないことを覚え ておいてください.例えば,`foo=1 foo=2; echo $foo'は,Solarisの shでは`1'になりますが,Bashでは`2'になります.順序を強 制するために`;'を使用する必要があります.`foo=1; foo=2; echo $foo'のようにします.

`subdir/program'を見つけるために,以下に依存しないようにしてくださ い.

 
PATH=subdir$PATH_SEPARATOR$PATH program

これはZsh 3.0.6では動作しません.代わりに以下のようなものを使用してくだ さい.

 
(PATH=subdir$PATH_SEPARATOR$PATH; export PATH; exec program)

代入の終了ステータスに依存しないようにしてください.Ash 0.2はステータス を変更せず,最後の文に伝搬します.

 
$ false || foo=bar; echo $?
1
$ false || foo=`:`; echo $?
0

そして,更に悪いことに,QNX 4.25はあらゆる場合で終了ステータス を0に設定します.

 
$ foo=`exit 1`; echo $?
0

デフォルト値を代入するために,以下のアルゴリズムを使用してください.

  1. デフォルト値がリテラルで閉じカッコを含まない場合は以下を使用してください.

     
    : ${var='my literal'}
    
  2. デフォルト値が閉じカッコを含まず,展開されず,初期化されている変数がIFS で分けられていない(すなわち,リストでない)場合,以下を使用してください.

     
    : ${var="$default"}
    
  3. デフォルト値が閉じカッコを含まず,展開されず,初期化されている変数がIFS で分けられる(すなわち,リストの)場合,以下を使用してください.

     
    var=${var="$default"}
    
  4. デフォルト値が閉じカッコを含む場合,以下を使用してください.

     
    test "${var+set}" = set || var='${indirection}'
    

ほとんどの場合,`var=${var="$default"}'で良いのですが,駄目なとき は後者を使用してください.正当性のための, `${var:-value}'と`${var=value}' の 項目は,See section シェルの代入.


10.7 シェルスクリプト内のカッコ

一列にある二つの開カッコは,シェルの実装によっては間違って処理されること を覚えておいて下さい.例えば,`pdksh' 5.2.14では以下のコードのパー スを失敗します.

 
if ((true) || false); then
  echo ok
fi

この問題を回避するため,二つの開カッコの間にスペースを挿入して下さい. `$(('に関する同様な問題と回避方法があります.シェルの代入を参照して下さい.

POSIXでは,以下のような開カッコを用いたcaseパターンのサ ポートを要求しています.

 
case $filename in
(*.c) echo "C source code";;
esac

しかし,この例の(には古いBourneシェルの実装で移植性がないものもた くさんあります.安全のため削除すべきでしょう.


10.8 特殊なシェル変数

シェルの動作に深く影響するため,使用すべきではないシェル変数もあります. シェルからまともな動作に戻るため,unsetすべき変数もありますが, unsetは移植性が無く(see section シェル組み込みの制限),代替値が 必要になります.これらの変数を以下にリストアップします.

CDPATH

この変数が設定されているとき,それはcdが相対的なファイル名で呼び 出されるときに検索するディレクトリのリストを設定します.POSIX 1003.1-2001では,CDPATHで空ではないディレクトリ名が正しく使用され ている場合,cdは絶対的なファイル名を結果として出力することになっ ています.残念ながらこの出力では,absがパスを二回受けとるので, `abs=`cd src && pwd`'のような慣用句が駄目になります.また,多くのシェ ルは,この部分のPOSIXに準拠していません.例えば,zsh は,`.'以外のディレクトリ名がCDPATHで選択されている場合以外, 結果を出力しません.

実際,この問題があるシェルはunsetもサポートしているので,以下 のようにしてその問題を回避することが可能です.

 
(unset CDPATH) >/dev/null 2>&1 && unset CDPATH

Autoconfが生成したスクリプトは自動的にCDPATHをunsetするので,これ らのスクリプトのこの問題を心配する必要はありません.

IFS

IFSの最初の文字をバックスラッシュに設定しないでください.実際, `"$@"'で要素を加えるときは,Bourneシェルは最初の文字(バックスラッ シュ)を使用し,そして,バックスラッシュエスケープをもう一度解釈する(!) シェルもあり,そのため,バックスペースとその他の奇妙な文字で終ることが可 能になっています.

(splitを実行していないとき,標準的なコードの)IFSの適切な値は, `SPCTABRET'です.`@*'の引数を連結するために 使用するので,最初の文字は特に重要です.

LANG
LC_ALL
LC_COLLATE
LC_CTYPE
LC_MESSAGES
LC_MONETARY
LC_NUMERIC
LC_TIME

あまりに多くのコンフィグレーションコードがCロケールを仮定していて, POSIXではCロケールが要求される場合はロケールの環境変数を `C'に設定する必要があるので,Autoconfが生成したスクリプトは通常,こ れらのすべての変数を`C'に設定します.しかし,非標準の古いシステム (特にSCO)では,ロケールの環境変数が`C'に設定されている場 合は壊れてしまうので,これらのシステムでAutoconfが生成したスクリプトを実 行するとき,代わりに変数を未設定(unset)にしてください.

LANGUAGE

LANGUAGEはPOSIXで指定されていませんが,それは場合によっ てはLC_ALLに優先させるGNUの拡張なので,Autoconfが生成し たスクリプトはそれも設定します.

LC_ADDRESS
LC_IDENTIFICATION
LC_MEASUREMENT
LC_NAME
LC_PAPER
LC_TELEPHONE

これらのロケール環境変数はGNUの拡張です.それらは,上記の POSIXの仲間(LC_COLLATEなど)のように扱われます.

LINENO

ほとんどの近代的なシェルは,現在の行番号をLINENOで提供しています. その値は,現在のコマンドの最初の行番号です.Autoconfは近代的なシェルで configureの実行を試みます.利用可能なそのようなシェルが無い場 合,それぞれの文字列$LINENO(英数文字が続かない)をインスタンスを行 番号で置換するために,Sedに前もって渡す手法を用いて,LINENOの実装 を試みます.

実行時の動作が異なるので,evalLINENOに依存すべきでは ありません.また,Sedに前もって渡す手法を用いる可能性は,引用符で囲んで いるとき,ヒアドキュメントのとき,または行を跨るほど長いコマンドのとき, $LINENOに依存すべきではないことを意味しています.ただし,サブシェ ルは問題ありません.以下の例では,一行目,六行目,そして九行目は移植性が ありますが,それ以外のLINENOのインスタンスは移植性がありません.

 
$ cat lineno
echo 1. $LINENO
cat <<EOF
3. $LINENO
4. $LINENO
EOF
( echo 6. $LINENO )
eval 'echo 7. $LINENO'
echo 8. '$LINENO'
echo 9. $LINENO '
10.' $LINENO
$ bash-2.05 lineno
1. 1
3. 2
4. 2
6. 6
7. 1
8. $LINENO
9. 9
10. 9
$ zsh-3.0.6 lineno
1. 1
3. 2
4. 2
6. 6
7. 7
8. $LINENO
9. 9
10. 9
$ pdksh-5.2.14 lineno
1. 1
3. 2
4. 2
6. 6
7. 0
8. $LINENO
9. 9
10. 9
$ sed '=' <lineno |
>   sed '
>     N
>     s,$,-,
>     : loop
>     s,^\([0-9]*\)\(.*\)[$]LINENO\([^a-zA-Z0-9_]\),\1\2\1\3,
>     t loop
>     s,-$,,
>     s,^[0-9]*\n,,
>   ' |
>   sh
1. 1
3. 3
4. 4
6. 6
7. 7
8. 8
9. 9
10. 10
NULLCMD

コマンド`>foo'を実行しているとき,zshは`$NULLCMD >foo'を実行します.BourneシェルはNULLCMDが`:'だと考えますが, zshはBourneシェル互換モードでも,NULLCMDを`cat'に 設定します.NULLCMDの設定を忘れた場合,スクリプトは標準入力からの データ待ちのためサスペンド状態になるかもしれません.

ENV
MAIL
MAILPATH
PS1
PS2
PS4

これらの変数は,対話的なシェルに対してのみ影響すると考えられるので,シェ ルスクリプトに対して問題はありません.しかし,少なくとも一つのシェル (pre-3.0 UWIN ksh)はそれが対話的かどうかを混同し,つまり (例えば)PS1の副作用として,`$?'を予期せず変更するはずです.こ のバグを回避するために,Autoconfが生成したスクリプトは以下のようなことを します.

 
(unset ENV) >/dev/null 2>&1 && unset ENV MAIL MAILPATH
PS1='$ '
PS2='> '
PS4='+ '
PWD

POSIX 1003.1-2001は,cdpwdが現在のディレ クトリの論理的なパスを示すPWD環境変数を必ず更新することを要求して いますが,伝統的なシェルはこれをサポートしていません.一つのシェルの実体 がPWDを管理していて,サブディレクトリと別のシェルはPWDを知ら ずにcdを実行する場合,これで混乱するはずです.この状況では, PWDは間違ったディレクトリを示します.`$PWD'の代わりに ``pwd`'を使用してください.

status

この変数は,zsh(少なくとも3.1.6)での`$?'へのエイリアスで,そ のため読み出し専用になっています.使用しないでください.

PATH_SEPARATOR

設定されていない場合,configureはビルドシステムに対する適切な パスの分離子を検出し,PATH_SEPARATOR出力変数をそれに応じて設定し ます.

DJGPPシステムでは,パス分離子を制御するために,PATH_SEPARATOR環境 変数をbashが(PATHのような)特定の環境変数を設定するため に使用している`:'または`;'のいずれかに設定することが可能です. これはbash内部でのみ動作するので,パス分離子として`;'がサ ポートされていないファイル内で代入する方が安全だろうという理由から, configureで標準的なDOSのパス分離子(`;')を検出し たいことでしょう.そのため,この変数をunsetするか,`;'に設定してく ださい.

RANDOM

RANDOMを提供するシェルも多くあり,その変数は使用するたびに異なる 整数を返します.その値が使用されていないとき,変更さることはほとんどあり ませんが,IRIXQ 6.5では毎回値が変更されます.これは,set を使用して監視すべきです.


10.9 シェル組み込みの制限

だめだよ全く,我々は本気なのに.制限のあるシェルもあるんです! :)

全ての組み込みコマンドやコマンドは,オプションをサポートし,そのため,ダッ シュで始まる引数を用いると,全く異なる動作をすることを覚えておくべきです. 例えば罪の無い`echo "$word"'でも,wordがダッシュで始まるとき は予期しない結果となるはずです.この問題は,パイプでは`x'を後で評価 するように,`echo "x$word"'を使用することで避けることが可能です.

.

通常のファイル(`test -f'を使用してください)を用いるときだけ .コマンドを使用してください.例えば,Bash 2.03は, `. /dev/null'で固まります.また,引数にスラッシュを含まない場合は .PATHを使用するので,現在のディレクトリのファイル `foo'で.を使用したい場合,`. ./foo'を使用する必要が あることを覚えておいてください.

!

!を使用することは不可能です.コードを書き換える必要があります.

break

`break 2'の使用などは安全です.

cd

POSIX 1003.1-2001では,cdが`-L' ("論理的") と`-P' ("物理的")オプションをサポートし,`-L'がデフォル トであることを要求しています.しかし,伝統的なシェルはこれらのオプション をサポートしておらず,cdコマンドは`-P'のように動作しま す.

移植性の高いスクリプトは,どちらのオプションもサポートしていると仮定すべ きではなく,どちらの動作もデフォルトと仮定すべきではありません.これは ちょっとトリッキーで,例えば,POSIXのデフォルトの動作では,現 在の論理的なディレクトリがシンボリックリンクの場合,`ls ..'と `cd ..'では異なるディレクトリを参照している可能性があります. dirに`..'の要素が無い場合,cd dirを使用しても 安全です.また,Autoconfが生成するスクリプトは,ac_top_srcdir の ような変数を計算するとき,この問題を調査するので(see section コンフィグレーション作業の実行),これらの変数でcdしても安全です.

pwdコマンドの議論も参照してください.

case

引数を引用符で囲む必要はありません.分離は実行されません.

最後の`;;'は不要ですが,使用した方が良いでしょう.

fnmatchのバグのため,bashはバックススラッシュを文字クラ スとして正しく処理することに失敗します.

 
bash-2.02$ case /tmp in [/\\]*) echo OK;; esac
bash-2.02$

このコードをUNIXMS-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'を使用することは,オプションとエスケー プシーケンスを削除しない限り不可能です.移植性を目標とする新しいアプリケー ションでは,`echo'の代わりに`printf'を使用すべきです.

オプションを期待しないでください.ECHO_Nなどの,`-c'をシミュ レーションする方法は,See section 出力変数のプリセット.

引数へのバックスラッシュは,処理について同意がとれていないので使用しない でください.`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
exit

exitのデフォルト値は$?を想定しています.残念ながら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を呼び出してください.

export

組み込みのexportは,シェル変数を環境変数(environment variable)に複製します.変数がエクスポートされて更新される度に,環境変数 も更新されます.反対に,環境変数はがシェルから読み出される度に,開始時に エクスポートされたものとして印のついたシェル変数をインポートするべきです.

ああ,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すべきです.

false

falseがステータス1で終了することを期待してはいけません. Solaris 8のネイティブなBourneシェルは,ステータス255で終了します.

for

位置の引数までループするため,以下のように使用したとします.

 
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+"$@"}' で単語の分離を試みるのことを覚えておいてください.`$@'の詳細は, シェルの代入を参照してください.

if

`!'の使用は移植性がありません.以下の例を考えます.

 
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

`-'で始まる書式化文字列は問題になります.bash(例えば 2.05b)では,それをオプション文字と解釈しエラーとなるでしょう.オプション の終りの印となる`--'は,書式化文字列をそのまま受けとるNetBSD Almquist shell (例えば0.4.6)ではよいとは言えません.`-'を`%c' や`%s'に書くのが,間違いを避ける最も簡単な方法でしょう.

 
printf %s -foo
pwd

最近のシェルを用いると,pwdは"論理的な"ディレクトリ名を出力 し,その構成要素にはシンボリックリンクがある可能性があります.これらのディ レクトリ名は,構成要素はすべてディレクトリとなっている"物理的な" ディ レクトリ名とは異なります.

POSIX 1003.1-2001では,pwdは,`-L' ("論理的") と`-P' ("物理的")オプションをサポートし,`-L'がデフォル トになっている必要があります.しかし,伝統的なシェルはこれらのオプション をサポートしておらず,pwdコマンドは`-P'のように動作しま す.

移植性の高いスクリプトでは,どちらのオプションもサポートしていると仮定す べきではなく,どちらの動作もデフォルトと仮定すべきではありません.また, 多くのホストは`/bin/pwd'が`pwd -P'と同じですが, POSIX はこの動作を要求しておらず,移植性の高いシェルではそれに 依存すべきではありません.

通常,そのままpwdを使用するのが最善でしょう.最近のホストでは, これで論理的なディレクトリ名を出力し,以下の利点があります.

cdコマンドでの議論も参照してください.

set

この組み込みコマンドは,一般的なダッシュで始まる引数の問題に直面します. BashやZshのような現在のシェルでは,オプションの終りを指定する `--'(`--'以降の全ての引数は,例えば`-x'であってもパラ メータです)を理解しますが,ほとんどのシェルは,オプションではない引数が 見つかるとすぐにオプションの処理を単純に停止します.このため,オプション の処理を終了するために`dummy'や単純に`x'を使用し,それを取り出 すためにshiftを使用してください.

 
set x $my_list; shift

すべてのオプションを認識しないこととは"反対"の問題(例えば,`set -e -x'で`-x'をコマンドラインに割り当てるといった問題)があるシェルもあ ります.以下のように省略した方が良いでしょう.

 
set -ex
shift

shiftするものが無いとき,shiftを使用することは悪い考 え方であるだけでなく,移植性が無くなってしまいことも追加されてしまいます. MIPS RISC/OS 4.52のシェルは,それを廃棄してしまいます.

source

POSIXが要求していないので,このコマンドは移植性がありません. 代わりに.を使用してください.

test

testプログラムは,多くのファイルと文字列のテストを実行する方法で す.それは別名の`['で呼び出されることも多いのですが,M4の引用符文字 という問題から,Autoconfのコードではその名前を使用することが要求されてい ます.

testを使用して複数の調査を行う必要がある場合,testの演算子 の`-a'と`-o'の代わりに,シェル演算子の`&&'と`||' で 組み合わせてください.System Vでは,`-a'と`-o'の優先順位は,単 項演算子とは間違った関係になっています.従って,POSIXはそれら を指定しないので,それを使用すると移植性が無くなります.同じ文で `&&'と`||'を組み合わせる場合,同じ優先順位があることを覚えてお いてください.

testで`!'を使用してもかまいませんが,ifではでき ません.`test ! -r foo || exit 1'.

test (files)

configureスクリプトでクロスコンパイルのサポートを可能にするた め,ホストシステムの代わりに,ビルドシステムの特徴のテストは,何もすべき ではありません.しかし,任意のファイルの存在を調査する必要があることが判 明するかもしれません.そうするために`test -f'や`test -r'を使用 してください.4.3BSDには`test -x'が無いので使用しないでく ださい.また,Solaris 2.5には`test -e'が無いので使用しないでくださ い.システム上にシンボリックリンクがあることをテストするためには, `test -L'ではなく`test -h'を使用して下さい.いずれも POSIX 1003.1-2001に準拠した様式ですが,Solaris 8の /bin/shのような古いシェルでは,`-h'だけをサポートしていま す.

test (strings)

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"'より堅牢です.

trap

少なくとも,シグナルの1,2,13,そして15をトラップすることは安全です.ま た,0のトラップも可能で,すなわち,スクリプトが終るとき(明示的な exitやスクリプトの終り)にtrapを実行するということで す.

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のみに影響します.

true

心配しないでください.我々が知っている限りtrueには移植性があり ます.それにもかかわらず,常に組み込みコマンドというわけではなく(例えば Bash 1.x),移植性の高いシェルのコミュニティは,:の使用を好みが ちです.これには副作用があります.falsetrueより移 植性が高いかどうか尋ねてみたときのAlexandre Oliva の回答です.

それらが存在しない場合,シェルは,falseに対しては正しく, trueに対しては正しくない,異常終了のステータスを生成するので, ある意味ではそのとおりです.

unset

unsetのサポートを仮定することはできません.それにもかかわらず, PS1のような邪魔な変数を利用不可能にすることは非常に役立つので,存 在をテストし,提供されていればそれを使用し,unsetがサポー トされていないときは,無効にする値を与えてください.

 
if (unset FOO) >/dev/null 2>&1; then
  unset=unset
else
  unset=false
fi
$unset PS1 || PS1='$ '

無効にする値については,See section 特殊なシェル変数. また,環境変数の caseについてはexportのドキュメントシェル組み込みの制限も参照してください.


10.10 通常のツールの制限

あらゆるマシンで見つかることが期待できる小さなツールセットには,知ってお くべき制限がいくつか含まれているはずです.

awk

ユーザ関数呼び出しで,カッコの前に空白を残さないでください. GNU awkはそれを拒絶します.

 
$ gawk 'function die () { print "Aaaaarg!"  }
        BEGIN { die () }'
gawk: cmd. line:2:         BEGIN { die () }
gawk: cmd. line:2:                      ^ parse error
$ gawk 'function die () { print "Aaaaarg!"  }
        BEGIN { die() }'
Aaaaarg!

プログラムを決定的にしたい場合,配列上のforに依存しないでください.

 
$ cat for.awk
END {
  arr["foo"] = 1
  arr["bar"] = 1
  for (i in arr)
    print i
}
$ gawk -f for.awk </dev/null
foo
bar
$ nawk -f for.awk </dev/null
bar
foo

HP-UX 11.0のネイティブのAWKのように,内部アンカーに調子が悪い正規表現の エンジンがあるものもあります.

 
$ echo xfoo | $AWK '/foo|^bar/ { print }'
$ echo bar | $AWK '/foo|^bar/ { print }'
bar
$ echo xfoo | $AWK '/^bar|foo/ { print }'
xfoo
$ echo bar | $AWK '/^bar|foo/ { print }'
bar

そのようなパターンに依存したり(すなわち,`/^(.*foo|bar)/'を使用する), そのようなAWKを拒絶する単純なテストを使用したりしないでください.

cat

オプションに依存しないようにしてください.しかし,表示不可能な文字を表示 するオプション`-v'は,移植性がありそうです

cc

`cc foo.c -o foo'のようなコンパイルが失敗したとき,(Reliant UNIXCDSのように)`foo.o'を残すコンパイラもあります.

HP-UX ccは,プリプロセスとアセンブラを行なう`.S'ファイル を受け入れません.`cc -c foo.S'は成功したように見えますが,実際には 何もしません.

`cc foo.c'で生成されるデフォルトの実行形式は,以下のようになるはず です.

cmp

cmpは,二つのファイルの生のデータの比較を実行しますが, diffは二つのテキストファイルを比較します.そのため, DOSのファイルを比較する場合,二つのファイルが異なっているかど うかを調査するだけの場合でも改行のエンコードの違いで見せかけの差が発生す ることを避けるため,diffを使用してください.

cp

伝統的に,ファイルのタイムスタンプの分解能は一秒になっていて,`cp -p'ではタイムスタンプをそのままコピーします.しかし,最近のファイルシス テムには分解能が1ナノ秒のタイムスタンプになっているものもたくさんありま す.残念ながら,`cp -p'の実装ではファイルのコピー時にタイムスタンプ を切り詰めるので,このためコピーされたファイルが結果としてソースよりも古 くなってしまいます.切り詰めの正確な量はcpが使用するシステムコー ルの分解能に依存します.伝統的に,これはutimeで,それは分解能が1 秒になっていますが,新しいcpの実装ではutimesを使用して いて,それは分解能が1マイクロ秒になっています.これらの新しい実装には, GNU coreutils 5.0.91やそれ以降,そしてSolaris 8 (sparc)のパッチ109933-02 やそれ以降が含まれます.残念ながら,2003年9月の段階では,完全なナノ秒の 分解能を持つタイムスタンプを設定するシステムコールはありません.

SunOS cpは`-f'をサポートしていませんが,その mvはサポートしています.mvcpが `-f'に関して異なっている理由については由来が推測できます. mvはデフォルトで,読み込み専用のファイルを上書きする前にプロン プトを表示します.cpはそうではありません.そのため, mvには`-f'オプションが必要ですが,cpには不要 です.mvcpは,読み込み専用のファイルに対して,動作 が異なり,その理由は,最も簡単なcpの形式では,読み込み専用のファ イルを上書きできませんが,最も簡単なmv形式では,それが可能だと いうことです.この理由は,cpはターゲットを書き込みアクセスで開 くのに対し,mvは単純にlink(または,新しいシステムでは rename)を呼び出すためです.

Bob Proulxは,`cp -p'は常に所有権のコピーを試みるとメモして います.しかし,実際に所有権をコピーするかどうかは,カーネルで実装されて いるシステムポリシーの決定に依存します.カーネルが許可している場合はそう なります.カーネルが許可していない場合は,そうなりません.cp自 身が制御しているものではありません.

SysVでは,ユーザはファイルを別のユーザにchown可能で,SysVにはstickyでは ない`/tmp'もあります.それは疑い無く,敵意のあるユーザのいないビジ ネス環境のSysVの遺産に由来しています.BSDは,rootだけがファイ ルをchown可能にし,stickyな`/tmp'を使用して,これをより安 全なモデルに変更しました.それは疑い無く,キャンパス環境のBSD の遺産に由来します.

LinuxはデフォルトでBSDに準拠していますが,chown可能 に設定することも可能です.別の例として,HP-UXはSysVに準拠していますが, 最近のセキュリティモデルを使用するよう設定し,chownできなくす ることが可能です.それは管理者が設定可能なパラメータなので,動作を示すた めにカーネル名を使用することは不可能です.

date

dateのバージョンによっては,特殊な%による指示語を理解しないも のもあり,残念ながら警告をする代わりに,それをそのまま通過させ,正しく終 了します.

 
$ uname -a
OSF1 medusa.sis.pasteur.fr V5.1 732 alpha
$ date "+%s"
%s
diff

`-u'には移植性がありません.

Tru64のように,実装によっては`/dev/null'の比較で失敗するものもあり ます.その代わりに空のファイルを使用してください.

dirname

全てのホストに動作するdirnameがあるわけではなく,その代わりに AS_DIRNAMEを使用すべきです(see section M4shでのプログラミング).例えば以 下のようにします.

 
dir=`dirname "$file"`       # This is not portable.
dir=`AS_DIRNAME(["$file"])` # This is more portable.

これは,POSIXで要求されている標準では,幾分微妙な扱いです.例 えばUN*Xでは`//1'は`/'になるのでしょうか?以下はPaul Eggertの 回答です.

古いUnixライクのものではそうはならず,前置される`//'は特殊なパス名 になります.それは"スーパールート"を参照し,他のマシンのファイルをアク セスするために使用されます.前置される`///',`////'などは, `/'と等価です.しかし,前置される`//'は特殊です.この伝統的は Apollo Domain/OSで始まったと考えていて,古いホストではまだそのOS を使用 しています.

POSIXでは可能ですが,`//'に対する特別扱いは要求されていま せん.そこでは,形式`//([^/]+/*)?'のパス名でのdirnameの動作は,実装 で定義されると告げています.これらの場合,GNU dirnameは`/'を返しますが,古いUnixライクのものでも動作す るように`//'を返した方が移植性が高いでしょう.

egrep

POSIX 1003.1-2001では,もはやegrepを要求していません が,より古いホストの多くはまだPOSIXのgrep -Eでの置換を サポートしていません.この問題を回避するため,AC_PROG_EGREP を呼 び出し,$EGREPを使用してください.

空の代入は移植性が無く,代わりに`?'を使用してください.例えば, Digital Unix v5.0では以下のようになります.

 
> printf "foo\n|foo\n" | $EGREP '^(|foo|bar)$'
|foo
> printf "bar\nbar|\n" | $EGREP '^(foo|bar|)$'
bar|
> printf "foo\nfoo|\n|bar\nbar\n" | $EGREP '^(foo||bar)$'
foo
|bar

$EGREPgrepの制限で苦しむことになります.

expr

`x'で始まるexprキーワードはないので,exprwordを間違って解釈しないように,`expr x"word" : 'xregex''を使用してください.

lengthsubstrmatch,そしてindexは使用し ないでください.

expr (`|')

`|'を使用することは可能です.POSIXでは,`expr ''' が 空の文字列を返すことを必須としていませんが,空の文字列を用いて空の文字列 (またはゼロ)とともに`|'を用いたときの結果は安全ではありません.例え ば以下を考えます.

 
expr '' \| ''

GNU/LinuxとPOSIX.2-1992では,この場合は空の文字列を 返しますが,伝統的なUNIXでは`0'を返します(Solarisはそのような 例の一つです).最近のPOSIX.1-2001ドラフトでは,その指定は伝統 的なUNIXの動作に一致するよう変更されています(信じられないことですが, これを修正するには時すでに遅しです).同じ問題が,計算結果が空の文字列に なるときにも,以下の状態では発生します.

 
expr bar : foo \| foo : bar

空の文字列を避けることで,この移植性の問題を避けてください.

expr (`:')

Solarisではサポートされていないので,パターン内に,`\?',`\+', そして`\|'を使用しないでください.

POSIX標準では,`expr 'a' : '\(b\)''が`0'を出力するか 空の文字列を出力するのかは明確ではありません.実際問題として,それはほと んどのプラットフォームで空の文字列を出力しますが,移植性の高いスクリプト では,これを仮定すべきではありません.例えば,QNX 4.25 ネイティ ブのexprは`0'を返します.

均一な動作を得る手段として,デフォルト値として空の文字列を使用することに なっていると信じているかもしれません.

 
expr a : '\(b\)' \| ''

残念ながら,これは元の式として正確に動作します.詳細は, `expr (`:')'の項目を参照してください.

古いexprの実装(例えば,SunOS 4のexprとSolaris 8の /usr/ucb/expr)には,一致したサブ文字列が120バイトより長い場合, exprが異常終了するという,思慮の欠けた長さの制限があります.こ の状況では,exprが失敗した場合,`echo|sed'に頼りたいと思 うかもしれません.

残っているものはそれだけではありません!

QNX 4.25のexprには,空の文字列ではなく`0'となる ことに加えて,終了ステータスでおかしな動作があります.それはカッコが使用 されているときには,常に1になるということです!

 
$ val=`expr 'a' : 'a'`; echo "$?: $val"
0: 1
$ val=`expr 'a' : 'b'`; echo "$?: $val"
1: 0

$ val=`expr 'a' : '\(a\)'`; echo "?: $val"
1: a
$ val=`expr 'a' : '\(b\)'`; echo "?: $val"
1: 0

実際に,(sedのような)他の手法でexprプログラムで異常 終了を捕獲する準備がある場合,結果を二回得る可能性があるので,これは大き な問題となります.例えば以下を考えます.

 
$ expr 'a' : '\(a\)' || echo 'a' | sed 's/^\(a\)$/\1/'

ほとんどのホストでは`a'を出力しますが,QNX 4.25では `aa'になります.単純な回避方法として,exprでのテストを構 成し,結果によってexprfalseで変数を設定する方法を 使用します.

fgrep

POSIX 1003.1-2001では,もはやfgrepを要求していません が,より古いホストの多くはまだPOSIXのgrep -Fでの置換を サポートしていません.この問題を回避するため,AC_PROG_FGREP を呼 び出し,$FGREPを使用してください.

find

オプション`-maxdepth'はGNU特有のようです.Tru64 v5.1, NetBSD 1.5,そしてSolaris 2.5のfindコマンドはそれを 理解しません.

`{}'の置換は,引数が正確に{}の場合のみ保証され,それが引 数の一部の場合は保証されません.例えば,DUとHP-UX 10.20とHP-UX 11では保 証されません.

 
$ touch foo
$ find . -name foo -exec echo "{}-{}" \;
{}-{}

一方,GNU findは`./foo-./foo'を報告します.

grep

System Vの`grep -s'はエラーメッセージのみ抑制し,出力を抑制しないの で,出力を抑制するために`grep -s'を使用しないでください.その代わり に(ファイルが存在しない場合) grepの標準出力と標準エラー出力を `/dev/null'へリダイレクトしてください.一致が見つかったかどうかを決 定するために,grepの終了ステータスを調査してください.

最後のパターンのみ尊重するgrep(例えば,AIX 6.5とSolaris 2.5.1)もあるので,`-e'で複数の正規表現を使用しないでください.ど ちらにしろ,Stardent Vistra SVR4のgrepには`-e' がありませ ん.... その代わりに拡張した正規表現と代入を使用してください.

Irix 6.5.16mのgrepは,それをサポートしていないので, `-w'に依存しないようにしてください.

ln

`-f'オプションがあるlnに依存しないようにしてください. 古いシステムではシンボリックリンクは利用不可能です.移植性のある代替物 `$(LN_S)'を使用してください.

2.04以前のバージョンのDJGPPに対して,lnは実行形式へのソフトリ ンクを,実際のプログラムを呼び出すスタブを生成することでエミュレートしま す.この機能は,Unix独自の実行形式以外のファイルでも動作します.そのため, `ln -s file link'は`link.exe'を生成し,それは実行された場合に `file.exe'の呼び出しを試みます.しかしこの機能は実行形式に対しての み動作するので,このシステムでは`cp -p'が代わりに使用されます. DJGPPの2.04とそれ以降では完全なシンボリックリンクがサポートされています.

ls

移植性のあるオプションは`-acdilrtu'です.最近では,`-l' で 所有者とグループを出力しますが,伝統的なlsはグループを省略しま す.

最近では,すべての診断結果は標準エラー出力に出てきますが,伝統的な `ls foo'は,`foo'が存在しない場合,メッセージ`foo not found'を標準出力に出力します.伝統的なlsでは,`.c'ファイ ルが無い場合,`sources=`ls *.c 2>/dev/null`'は`sources="*.c not found"'と等価なので,そのようなシェルコマンドを書くときに注意してく ださい.

mkdir

mkdirのオプションには移植性はありません.`mkdir -p filename'の代わりにAS_MKDIR_P(filename)を使用すべきで す(see section M4shでのプログラミング).

mv

移植性のあるオプションは,`-f'と`-i'のみです.

ファイルシステム間で個別にファイルを移動することは(V6では)移植性がありま すが,常に強力でははありません.`mv new existing'をするとき, `existing'の古いものも新しいものも実際には存在していないという危険 な状態が存在します.

ファイルを`/tmp'から移動することで,これらのファイルを作成していた としても,好ましくない(が,まったく有効な)警告を発生することがあることを 覚えておいてください.システムによっては,`/tmp'にファイルを作成す ると,guidを自分が所属していないwheelに設定するものもあります.そ のためファイルがコピーされると,chgrpで失敗します.

 
$ touch /tmp/foo
$ mv /tmp/foo .
error-->mv: ./foo: set owner/group (was: 3830/0): Operation not permitted
$ echo $?
0
$ ls foo
foo

この動作は,POSIXに準拠しています.

何らかの理由でファイル属性の複製に失敗する場合,mvは診断メッセー ジを標準エラー出力に書き出しますが,この異常終了で,mvは終了ス テータスを変更しません.

マウントポイントをまたがってディレクトリを移動することは移植性が無いので, cprmを使用してください.

開いているファイルの移動/削除は移植性がありません.以下の例はDOS/WIN32 では実行不可能です.

 
exec > foo
mv foo bar

以下も実行不可能です.

 
exec > foo
rm -f foo
sed

文字クラスの一部の場合でも,パターンに(エスケープされていない)セパレータ を含めるべきではありません.POSIX準拠では,Crayのsed は`s/[^/]*$//'を拒絶します.`s,[^/]*$,,'を使用してください.

sedのスクリプトは,八文字以上の分岐ラベルを使用すべきではなく,コメント を含めるべきでもありません.

NetBSD 1.4.2では,二番目のものをコマンドとして解釈しようと試み るので,sedによっては,余分な`;'を含めてはなりません.

 
$ echo a | sed 's/x/x/;;s/x/x/'
sed: 1: "s/x/x/;;s/x/x/": invalid command code ;

sedによっては,入力バッファに4000バイトの制限があるものもある ので,入力は妥当な長さの行にすべきです.

`\|'の交換は一般的ですが,POSIXはそのサポートを要求してい ないので,移植性の高いスクリプトでは避けるべきです.Solaris 8の sedは交換をサポートしていません.例えば,`sed '/a\|b/d'' は,リテラル文字列`a|b'を含んでいる行のみ検出します.

グループ内のアンカー(`^'と`$')は移植性がありません.

パターン内の入れ子状のカッコは,現在のホストでは完全に移植性あるものなの ですが,SVR3のように古いsedの実装ではサポートされていません.

もちろんオプション`-e'には移植性がありますが,それは不要です.ダッ シュで始まる有効なsedプログラムは無いので,明確にする役には立ちません. 唯一の有効性は,以下のように字下げを強制的に行なうときです.

 
sed -e instruction-1 \
    -e instruction-2

これは以下の代わりのものです.

 
sed instruction-1;instruction-2

もう一つの垢抜けた伝説として,"マッチしたもの"を意味するsコマン ドの一部を置換するとき,`&'を使用しても移植性はあるでしょう.すべて のベル研究所のV7 sedの子孫は(少なくとも,我々はそれより古い sedを経験したことはありません)サポートしています.

POSIXでは,`!'とそれ以降のコマンドの間に空白があってはい けません.アドレスと`!'の間の空白はOKです.例えば,Solaris 8では以 下のようになります.

 
$ echo "foo" | sed -n '/bar/ ! p'
error-->Unrecognized command: /bar/ ! p
$ echo "foo" | sed -n '/bar/! p'
error-->Unrecognized command: /bar/! p
$ echo "foo" | sed -n '/bar/ !p'
foo
sed (`t')

古いシステムには,新しいサイクルと開始するとき,その`t'フラグをリセッ トすることを"忘れる" sedがあるシステムもあります.例えば, MIPS RISC/OSとIRIX 5.3で,以下のsedスクリプトを 実行した場合を考えます(行番号は,実際にはテキストの一部ではありません).

 
s/keep me/kept/g  # a
t end             # b
s/.*/deleted/g    # c
: end             # d

ファイルの内容は以下を考えます.

 
delete me         # 1
delete me         # 2
keep me           # 3
delete me         # 4

以下のようになります.

 
deleted
delete me
kept
deleted

以下のようにはなりません.

 
deleted
deleted
kept
deleted

なぜでしょう?一行目を処理しているとき,マッチするのでtフラグがセットさ れ,b行からd行まで移動し,出力が生成されます.二行目を処理しているとき, tフラグはセットされたままです(これはバグです).しかし,a行はマッチに失敗 しますが,置換が失敗するとき,sedはtフラグをクリアすることをサ ポートしていません.そのため,フラグがセットされているように見えるb行は, それをクリアし,dへ移動し,その結果,`deleted'の代わりに `delete me'になります.三行目を処理しているとき,マッチを示すt がク リアされるため,フラグがセットされ,その結果,b行はフラグをクリアし移動 します.最終的にフラグはクリアになっているので,四行目は正しく処理されま す.

sedの`t'について覚えておくべきことは二つあります.最初に, 成功した置換によっては,置換の直前だけでなく`t'ジャンプする ことを覚えておいてください.そのため,tフラグを実際にリセットするために, ごまかしの`t clear; : clear'を使用してください.

二番目は,それぞれの新しいサイクルでフラグをクリアするのをsed に依頼することはできません.

上記のスクリプトの移植性の高い実装の一つは,以下のようになります.

 
t clear
: clear
s/keep me/kept/g
t end
s/.*/deleted/g
: end
touch

要求されるタイムスタンプ(例えば`-r'オプション)を指定する場合, touchは通常,utimeutimesシステムコールを使用し, 結果として,`cp -p'に存在するタイムスタンプの切り詰めの問題と同様の 結果になるはずです.

古いBSDシステムには,空のファイルに対するtouchのよう なコマンドで,タイムスタンプを更新しない結果となるものもあるので,回避す るために,echoのようなコマンドを使用してください.

GNU touch 3.16r(とそれ以前の全て)は,空のファイルが NFSでマウントされている4.2のボリュームのとき,SunOS 4.1.3 での 動作で異常終了します.


10.11 Makeの制限

make自身には非常に多くの制限があるので苦労します,ここではわず かですが紹介します.とにかく,シェルによってコマンドが実行されるので,そ の弱い部分の全てが継承されていくということを覚えておいてください ....

$<

POSIXでは,`makefile'の構成物の`$<'は推測される規則 と`.DEFAULT'ルールのみで使用可能だと告げています.通常のルールでの その意味は明記されていません.例えば,Solaris 8のmakeはそれを 引数で置換します.

マクロ名へのアンダースコアの前置

NEWS-OS 4.2Rのように,マクロ名にアンダースコアを前置することをサポートし ていないmakeもあります.

 
$ cat Makefile
_am_include = #
_am_quote =
all:; @echo this is test
$ make
Make: Must be a separator on rules line 2.  Stop.
$ cat Makefile2
am_include = #
am_quote =
all:; @echo this is test
$ make -f Makefile2
this is test
マクロへのバックスラッシュの後置

HP-UXのバージョンによっては,makeには,バックスラッシュ以降の 複数の改行を,空ではない行も含めて読み込むものもあります.例えば以下のよ うにします.

 
FOO = one \

BAR = two

test:
        : FOO is "$(FOO)"
        : BAR is "$(BAR)"

FOOone BAR = twoと等価です.それ以外のmakeでは, バックスラッシュは直後の行だけを含みます.

コメント内のエスケープされた改行

POSIXによると,`Makefile'のコメントは#ではじまり, エスケープされていない改行まで続きます.

 
% cat Makefile
# A = foo \
      bar \
      baz

all:
        @echo ok
% make   # GNU make
ok

しかし現実では,これは常にそうではありません.実装によっては,# から行末までを廃棄し,後置されるバックスラッシュを無視するものもあります.

 
% pmake  # BSD make
"Makefile", line 3: Need an operator
Fatal errors encountered -- cannot continue

このため,複数行の定義をコメントアウトしたい場合,最初の行だけでなく,そ れぞれの行に# を前置してください.

 
# A = foo \
#     bar \
#     baz
make macro=valueとサブ呼び出しのmake

コマンドライン変数のfoo=barのような定義は,`Makefile'の fooの定義に優先します.(GNU makeのような) makeの実装によっては,この優先はサブ呼び出しのmake に伝搬します.古い実装によっては,それ以下のmakeに代入を渡さな いものもあります.

 
% cat Makefile
foo = foo
one:
        @echo $(foo)
        $(MAKE) two
two:
        @echo $(foo)
% make foo=bar            # GNU make 3.79.1
bar
make two
make[1]: Entering directory `/home/adl'
bar
make[1]: Leaving directory `/home/adl'
% pmake foo=bar           # BSD make
bar
pmake two
foo

サブ呼び出しのmakefoo=barの優先を伝搬したい場合,移植 性を持たせる方法が無いわけではありません.その一つは,すべての環境変数を `Makefile'マクロ定義に優先させる-eオプションを使用し, fooを環境変数として定義する方法です.

 
% env foo=bar make -e

-eオプションは,自動的にサブ呼び出しのmakeに伝搬し,環 境変数はmakeの呼び出し間で継承されるので,fooマクロはサ ブ呼び出しのmakeで期待したように優先されます.

この構文(foo=bar make -e)は,`Makefile'の外で使用するときだ け,例えば,スクリプトやコマンドラインのときだけ移植性があります. makeルール内で実行するとき,GNU make 3.80とそれ以前 のバージョンは,それ以下でのmake-eオプションを伝搬さ せることを忘れています.

更に-eを使用することで,`Makefile'で通常定義されるその他のマ クロが環境変数に含まれている場合,予期しない副作用があるかもしれません. (以下のmake -eSHELLの注意も参照してください.)

サブ呼び出しのmakeに優先物を伝搬させるもう一つの方法は, `Makefile'に手動で行なうことです.

 
foo = foo
one:
        @echo $(foo)
        $(MAKE) foo=$(foo) two
two:
        @echo $(foo)

そうする場合,ユーザが優先したいと思われるすべてのマクロを予測する必要が あります.

SHELLマクロ

POSIX準拠のmakeでは,シェルプロセスを起動したり, `Makefile'ルールを実行するために,内部で$(SHELL)マクロを使用 します.これはmakeで提供される組み込みマクロですが, `Makefile'やコマンドライン引数で変更することが可能です.

すべてのmakeが,このSHELLマクロを定義するわけではありま せん.例えば,OSF/Tru64 makeがそうです.この実装では,常に /bin/shを使用します.そのため,`Makefile'で常にSHELL を定義するのは良い考えです.Autoconfを使用している場合,以下のようにして ください.

 
SHELL = @SHELL@

POSIX準拠のmakeでは,make -eが使用されている 場合でも,環境変数から$(SHELL)の値を入手してはなりません(そうでない場合, SHELL=/bin/tcshの状況でルールによって何が起こるのか考えてみてくだ さい).

しかし,すべてのmakeがこのような例外を実装しているわけではあり ません.例えば,OSF/Tru64 makeSHELLを使用しないので, 保護していなくても不思議ではありません.

 
% cat Makefile
SHELL = /bin/sh
FOO = foo
all:
        @echo $(SHELL)
        @echo $(FOO)
% env SHELL=/bin/tcsh FOO=bar make -e   # OSF1 V4.0 Make
/bin/tcsh
bar
% env SHELL=/bin/tcsh FOO=bar gmake -e  # GNU make
/bin/sh
bar
ルール内のコメント

コメントをルールに書き込まないでください.

タブで始まるものは,タブの直後に#が続いていても,すべて現在のルー ルのコマンドとして扱うmakeもあります.Tru64 Unix V5.1の makeはその一つです.以下の`Makefile'で,シェルで# fooを実行します.

 
all:
        # foo
`obj/'サブディレクトリ

びっくりしたくなければ,サブディレクトリを`obj/'と命名しないでくだ さい.

`obj/'ディレクトリが存在する場合,BSD makeは `Makefile'を読み込む前に,そのなかに入ります.このため,現在のディ レクトリの`Makefile'は読み込まれません.

 
% cat Makefile
all:
        echo Hello
% cat obj/Makefile
all:
        echo World
% make      # GNU make
echo Hello
Hello
% pmake     # BSD make
echo World
World
make -k

make -kの終了ステータスに依存しないようにしてください.終了ステー タスがエラーかどうかを反映する実装もあります.それ以外の実装では,常に成 功します.

 
% cat Makefile
all:
        false
% make -k; echo exit status: $?    # GNU make
false
make: *** [all] Error 1
exit status: 2
% pmake -k; echo exit status: $?   # BSD make
false
*** Error code 1 (continuing)
exit status: 0
VPATH

POSIXでは,VPATHサポートを指定していません.多くの makeVPATHサポートの形式がありますが,その実装は, make間で一貫していません.

VPATH機能を必要としている人々への最高の提案は,makeの実 装を選択しそれに固執するようにと言うことかもしれません.`Makefile' の結果は常に移植性があるとは限らないので,移植性の高いmakeを選 択するのが良いでしょう(ヒント,ヒント).

VPATHの実装の既知の問題には以下のものがあります.

VPATHと二重のコロンのルール

VPATHへの代入で,Sunのmakeは最初の二重コロンのルールの 組だけを実行します.(このコメントは,1994年からで,現在は無くなっていま す.SunOS 4では移植性があります.これが再生成された場合,それを説明する テストケースを送ってください.)

明示的なルールで$<がサポートされていない

他でも述べたように,明示的なルールで$<を使用するのは移植性があり ません.必要条件のファイルは,ルール内で明示的な名前にすべきです. VPATHの検索で必要条件を見つけたい場合,手動でコード全体を書く必要 があります.例えば,以下のようなパターンを使用します.

 
VPATH = ../src
foo.o: foo.c
        cc -c `test -f foo.c || echo ../src/`foo.c -o foo.o
自動的なルールの再書き込み

SunOS makeのように,VPATHで必要条件を探し,出現するたび に適切なルールにを再書き込みするmakeの実装もあります.

例えば,以下を考えます.

 
VPATH = ../src
foo.o: foo.c
        cc -c foo.c -o foo.o

`foo.c'が`../src'で見つかった場合,cc -c ../src/foo.c -o foo.oを実行します.素晴らしいと思います.

しかし,それ以外のmakeの実装では,これに依存することは不可能で, VPATHを手動で検索する必要があります.

 
VPATH = ../src
foo.o: foo.c
        cc -c `test -f foo.c || echo ../src/`foo.c -o foo.o

しかし"必要条件の再書き込み"はこれに適用されます.そのため, `../src'に`foo.c'がある場合,SunOSのmakeは以下を実行 します.

 
cc -c `test -f ../src/foo.c || echo ../src/`foo.c -o foo.o

以下を生成します.

 
cc -c foo.c -o foo.o

そしてこのために失敗します.あぁ.

回避策の一つは,ルールのなかに`foo.c'をそのまま書いていないことを確 かめることです.例えば,以下の三つのルールは安全です.

 
VPATH = ../src
foo.o: foo.c
        cc -c `test -f ./foo.c || echo ../src/`foo.c -o foo.o
foo2.o: foo2.c
        cc -c `test -f 'foo2.c' || echo ../src/`foo2.c -o foo2.o
foo3.o: foo3.c
        cc -c `test -f "foo3.c" || echo ../src/`foo3.c -o foo3.o

必要条件がマクロ内にあるとき,事態はより悪くなります.

 
VPATH = ../src
HEADERS = foo.h foo2.h foo3.h
install-HEADERS: $(HEADERS)
        for i in $(HEADERS); do \
          $(INSTALL) -m 644 `test -f $$i || echo ../src/`$$i \
            $(DESTDIR)$(includedir)/$$i; \
        done

上記のinstall-HEADERSルールは,for i in $(HEADERS);for i in foo.h foo2.h foo3.h;に展開され,foo.hfoo2.hはそのまま単語となり,このためサブジェクトはVPATH に 調整されるので,SunOSでは信頼できません.

三つのファイルが`../src'にある場合,このルールは以下のように実行さ れます.

 
for i in ../src/foo.h ../src/foo2.h foo3.h; do \
  install -m 644 `test -f $i || echo ../src/`$i \
     /usr/local/include/$i; \
done

最初の二つのinstallの呼び出しは失敗します.例えば, foo.hをインストールする事を考えます.

 
install -m 644 `test -f ../src/foo.h || echo ../src/`../src/foo.h \
  /usr/local/include/../src/foo.h;

以下を生成します.

 
install -m 644 ../src/foo.h /usr/local/include/../src/foo.h;

手動のVPATHの検索には問題が無いことに注意してください.しかし,こ のコマンドは,間違ったディレクトリに`foo.h'をインストールします.

ここまで,いくつかの`Makefile'でfoo.cに対して行なってきた, $(HEADERS)をどうにかして引用符で囲むことは役に立ちません.

 
install-HEADERS: $(HEADERS)
        headers='$(HEADERS)'; for i in $$headers; do \
          $(INSTALL) -m 644 `test -f $$i || echo ../src/`$$i \
            $(DESTDIR)$(includedir)/$$i; \
        done

実際,headers='$(HEADERS)'headers='foo.h foo2.h foo3.h' に展開され,foo2.hはそのまま単語になります.(一方, headers='$(HEADERS)'; for i in $$headers;の慣用句は,for i in;で構文エラーになるシェルもあるので,$(HEADERS)が空の場合は良 い考えです.)

回避方法の一つは,不要な`../src/'の接頭辞を手動で削除する事です.

 
VPATH = ../src
HEADERS = foo.h foo2.h foo3.h
install-HEADERS: $(HEADERS)
        headers='$(HEADERS)'; for i in $$headers; do \
          i=`expr "$$i" : '../src/\(.*\)'`;
          $(INSTALL) -m 644 `test -f $$i || echo ../src/`$$i \
            $(DESTDIR)$(includedir)/$$i; \
        done

Automakeも同様なことを行ないます.

OSF/Tru64 makemakeは,不思議なディレクトリの必要条件を作成する

必要条件がVPATHのサブディレクトリにある場合,Tru64 make はそれを現在のディレクトリに作成します.

 
% mkdir -p foo/bar build
% cd build
% cat >Makefile <<END
VPATH = ..
all: foo/bar
END
% make
mkdir foo
mkdir foo/bar

ルールは,前に存在している手動のVPATH検索を使用するので,これは予 想外の結果になるはずです.

 
VPATH = ..
all : foo/bar
        command `test -d foo/bar || echo ../`foo/bar

上記のcommandは,現在のディレクトリに作成された,空の `foo/bar'ディレクトリで実行されます.

ターゲットの探索

GNU makeは,VPATHで見つかったファイルを使用す べきとき決定するアルゴリズムは幾分複雑です.See (make)Search Algorithm section `How Directory Searches are Performed' in The GNU Make Manual.

ターゲットのリビルドが必要な場合,GNU makeは,このター ゲットをVPATHで検索している間に見つかったファイル名を廃棄し, `Makefile'で与えられたファイル名を使用して,ローカルなファイルをビ ルドします.ターゲットをリビルドする必要が無い場合は,GNU makeVPATHで検索している間に見つかったファイル名を使用 します.

NetBSD makeのような,その他のmakeの実装は,より簡単 に記述できます.VPATHで検索している間に見つかったファイル名は,ター ゲットがリビルドを必要としているかどうかにかかわらず使用されます.このた め<新しいファイルはローカルに作成されますが,VPATHに位置する既存 のファイルは更新されます.

しかし,OpenBSDとFreeBSDのmakeは,明示的なルールを持つ依存性を 探すためにVPATHを実行しません.これは非常にイライラします.

VPATHで,autoconfパッケージのビルドを試みるとき(例えば, mkdir build && cd build && ../configure),GNU make makeは`build'ディレクトリですべてのローカ ルにビルドしますが,BSD makeはローカルな新しいファイ ルをビルドし,ソースディレクトリの既存のファイルを更新する事を意味します.

 
% cat Makefile
VPATH = ..
all: foo.x bar.x
foo.x bar.x: newer.x
        @echo Building $@
% touch ../bar.x
% touch ../newer.x
% make        # GNU make
Building foo.x
Building bar.x
% pmake       # NetBSD make
Building foo.x
Building ../bar.x
% fmake       # FreeBSD make, OpenBSD make
Building foo.x
Building bar.x
% tmake       # Tru64 make
Building foo.x
Building bar.x
% touch ../bar.x
% make        # GNU make
Building foo.x
% pmake       # NetBSD make
Building foo.x
% fmake       # FreeBSD make, OpenBSD make
Building foo.x
Building bar.x
% tmake       # Tru64 make
Building foo.x
Building bar.x

NetBSDのmakeが`../bar.x'をVPATHのある場所で更新し, FreeBSD,OpenBSD,そしてTru64のmakeは,`../bar.x'が最新の ときでも,常に`bar.x'を更新することに注意して下さい.

言及する価値のあるもう一つの点は,GNU makeが一度 VPATHのファイル名を無視する事に決めると(例えば,上記の例の `../bar.x'を無視する),ターゲットが他のルールの必要条件になったとき, それを無視し続けます.

以下の例では,bar.x: newer.xのルールを実行している間に `bar.x' のVPATHの結果を無視するので,.x.yのルールを実 行する前に,GNU makeVPATHの`bar.x'を探 さない事を示しています.

 
% cat Makefile
VPATH = ..
all: bar.y
bar.x: newer.x
        @echo Building $@
.SUFFIXES: .x .y
.x.y:
        cp $< $@
% touch ../bar.x
% touch ../newer.x
% make        # GNU make
Building bar.x
cp bar.x bar.y
cp: cannot stat `bar.x': No such file or directory
make: *** [bar.y] Error 1
% pmake       # NetBSD make
Building ../bar.x
cp ../bar.x bar.y
% rm bar.y
% fmake       # FreeBSD make, OpenBSD make
echo Building bar.x
cp bar.x bar.y
cp: cannot stat `bar.x': No such file or directory
*** Error code 1
% tmake       # Tru64 make
Building bar.x
cp: bar.x: No such file or directory
*** Exit 1

bar.x: newer.xルールからコマンドを削除した場合,GNU makeでは手品のように動作し始める事に注意してください.それは, bar.xが更新されていない事を知っているので,VPATH (`../bar.x')の結果がうまく使用できるという結果を廃棄しません.Tru64 でも動作しますが,FreeBSDとOpenBSDではまだそうではありません.

 
% cat Makefile
VPATH = ..
all: bar.y
bar.x: newer.x
.SUFFIXES: .x .y
.x.y:
        cp $< $@
% touch ../bar.x
% touch ../newer.x
% make        # GNU make
cp ../bar.x bar.y
% rm bar.y
% pmake       # NetBSD make
cp ../bar.x bar.y
% rm bar.y
% fmake       # FreeBSD make, OpenBSD make
cp bar.x bar.y
cp: cannot stat `bar.x': No such file or directory
*** Error code 1
% tmake       # True64 make
cp ../bar.x bar.y

すべてのmakeの実装がVPATHによるダーゲットの検索に依存し ないようにお願いするのが,唯一の解決方法だと思います.言い替えると, VPATHはビルドされていないソースへの予約にすべきです.

単一のサフィックスルールと分離された依存性

単一のサフィックスルール(Single Suffix Rule)は,基本的に(推測され る)通常のサフィックスルール(`.from.to:')ですが,ディスティネー ション(destination)サフィックスは空(`.from:')です.

分離された依存性(Separated dependencies)は,ルールを定義すること無 く,ターゲットの必要条件のリストを単純に参照します.通常,一方ではルール を,もう一方で依存性をリストアップすることが可能です.

Solarisのmakeは,単一のサフィックスルールで定義されたターゲッ トに対する,分離された依存性をサポートしていません.

 
$ cat Makefile
.SUFFIXES: .in
foo: foo.in
.in:
        cp $< $ $ touch foo.in
$ make
$ ls
Makefile  foo.in

一方GNU Makeはサポートしています.

 
$ gmake
cp foo.in foo
$ ls
Makefile  foo       foo.in

それは`foo: foo.in'の依存性無しで動作することに注意してください.

 
$ cat Makefile
.SUFFIXES: .in
.in:
        cp $< $ $ make foo
cp foo.in foo

そして,それは二重のサフィックスの継承ルールで動作することにも注意してく ださい.

 
$ cat Makefile
foo.out: foo.in
.SUFFIXES: .in .out
.in.out:
        cp $< $ $ make
cp foo.in foo.out

結果として,そのような状況では,ターゲットルールを書く必要があります.

タイムスタンプの分解能

伝統的に,ファイルのタイムスタンプの分解能は1秒になっていて, makeはファイルがそれ以外のものより新しいかどうかを決定するため これらのタイムスタンプを使用していました.しかし,最近のファイルシステム には分解能が1ナノ秒のタイムスタンプになっているものもたくさんあります. makeの実装には,タイムスタンプ全体を見るものもあります.それ以 外では,分数部分を無視し,結果として間違ったものとなるはずです.通常,こ れは問題ありませんが,非常に稀な状況では,タイムスタンプの切り詰めのバグ を回避するため,`sleep 1'のような手法を使用する必要があるかもしれま せん.

`cp -p'と `touch -r'のようなコマンドでは,通常はファイルのタイ ムスタンプを完全な分解能でコピーしません(see section 通常のツールの制限).このため,以下のようなルールは気を付けるべきです.

 
dest: src
        cp -p src dest

それは,タイムスタンプの切り詰め後では,`src'よりも`dest'が古 くなり,このためmakeは次回も不必要な仕事を再び行なうはずです. この問題を回避するため,タイムスタンプファイルを使用することが可能です. 例えば以下のようにします.

 
dest-stamp: src
        cp -p src dest
        date >dest-stamp

[ << ] [ >> ]           [Top] [Contents] [Index] [ ? ]

This document was generated by Akihiro Sagawa on June, 15 2005 using texi2html 1.70.