Go to the first, previous, next, last section, table of contents.


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

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

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

#! /usr/bin/perl

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

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

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

シェルに関するドキュメントのソースは他にもあります.例えば, @href{http://www.faqs.org/faqs/unix-faq/shell/, the Shell FAQs}を参照 してください.

シェル

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

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

Ash
@command{ash}は,動作の軽いBourne互換シェルとして@acronym{GNU}/Linuxと @acronym{BSD}システムでよく使用されています.Ash 0.2には0.3.xシリーズ で修正されているバグがいくつかありますが,バージョン0.2は多くの @acronym{GNU}/Linux 配布物で配布されているので,移植性の高いシェルスク リプトではそれを回避すべきです. Ash 0.2での互換性のため以下のようにしてください.
Bash
@command{bash}を実行しているかどうかを検出するために, BASH_VERSIONが設定されているかどうかをテストしてください.その 拡張を利用不可能にし,@acronym{POSIX}互換性を要求するため,@samp{set -o posix} を実行してください.詳細は,@xref{Bash POSIX Mode,, Bash @acronym{POSIX} Mode, bash, The @acronym{GNU} Bash Reference Manual}.
Bash 2.05とそれ以降
バージョン2.05とそれ以降の@command{bash}は,@command{set}組み込みコマ ンドの出力に対して,その出力をより容易に評価できるように設計されている ので,異なる書式を使用しています.しかし,この出力はそれ以前のバージョ ンの@command{bash}(や,おそらくそれ以外の多くのシェル)と互換性がありま せん.そのため,@command{bash} 2.05やそれ以上のものを @command{configure}の実行に使用している場合,それ以外のすべてのビルド の作業に対しても,同じように@command{bash} 2.05を使用する必要があるで しょう.
Solarisの@command{/usr/xpg4/bin/sh}
Solarisシステム上の@acronym{POSIX}互換のBourneシェルは, @command{/usr/xpg4/bin/sh}で,それはオプションパッケージの一部です.こ のパッケージに対しては追加料金は不要ですが,最小のOSのインストールには ないので,持っていない人もいるでしょう.
Zsh
@command{zsh}が実行されているかどうかを検出するために, ZSH_VERSIONが設定されているかどうかをテストしてください.デフォ ルトで,@command{zsh}はBourneと互換性はありません`emulate sh'を実行し,NULLCMD`:'に設定する必要があ ります.詳細は,See section `Compatibility' in The Z Shell Manual. Zsh 3.0.8は,Mac OS X 10.0.3でのネイティブな@command{/bin/sh}です.

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

Russ Allbery:

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

Robert Lipe:

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

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

ヒアドキュメント

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

Open@acronym{BSD} 2.7の@command{/bin/sh}では,以下のようになります.

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

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

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

多くの古い(Bourneシェルを含む)シェルでは,ヒアドキュメントは非効率に実 装されています.大きなヒアドキュメントを間違って扱うシェルもあります. 例えば,Solaris 8 @command{dtksh}は@command{ksh} 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マクロは,シェルの条件文を見ることができないので, それは推奨されておらず,条件分岐の前にそれが展開され,実行時に条件文が 失敗だと分かるとき,マクロ展開に失敗するかもしれず,マクロの実行を完全 に終了できないでしょう.

ファイルディスクリプタ

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

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

様々なレベルの詳細を認めるでしょう@enddots{}

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

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

ファイルシステムの条件

@command{autoconf}とその仲間達は,通常様々なUnixで実行されますが,それ はその他のシステムでも使用され,最も顕著なものとしては@acronym{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出力変 数を使用してください.@command{configure}は,開始時にこれを適切な値 (`:' または`;')に設定します.

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

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

複数のドットの禁止 (SFN)
@acronym{DOS}はファイル名に複数のドットがあるものを扱うことが不可能で す.これは,@command{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'が必要になるので,@acronym{DOS}では問題があります.パッ ケージを@acronym{DOS}ベースの環境でより移植性を高くするため,その代わ りに以下を使用すべきです.
AC_CONFIG_HEADERS([config.h:config.hin])
AC_CONFIG_FILES([source.c:source.cin foo.bar:foobar.in])
AC_OUTPUT
ドットの前置の禁止 (SFN)
@acronym{DOS}はドットで始まるファイル名を扱うことが不可能です.これは 通常,@command{autoconf}に対してはあまり重要ではない問題です.
大文字小文字を区別しない (LFN)
@acronym{DOS}は大文字小文字を区別しないので,例えば,`INSTALL'と いう名のファイルと`install'という名のディレクトリの両方を持つこと ができません.これは,@command{make}にも影響します.ディレクトリに `INSTALL'という名のファイルがある場合,`make install'は (`install'がPHONY として印がついていないとき)何もしません.
8+3の制限 (SFN)
@acronym{DOS}ファイルシステムでは,ファイル名の最初の8文字と最初の3文 字の拡張子のみ保存され,それらはユニークである必要があります.それは, `foobar-part1.c'`foobar-part2.c',そして `foobar-prettybird.c'の全ては同じ名前(`FOOBAR-P.C')になりま す.`foo.bar'`foo.bartender'も同じものになります. 注意:これは通常,ファイル名をユニークにするために短いバージョンでは数 字の後置を使用するので,Windowsでは問題になりません.しかし,レジスト リの設定でこの動作を停止可能です.これで長いファイル名を含むファイルの ツリーを,SFNとLFNの環境で共有することが可能になりますが,上 記の問題は同様に存在します.
無効な文字
@acronym{DOS}ファイル名で無効な文字もあり,そのため避けた方が良いでしょ う.LFNの環境では,`/'`\'`?'`*'`:'`<'`>'`|',そして`"'です.SFN環 境では,それ以外にも無効になります.これには,`+'`,'`[',そして`]'が含まれます.

シェルの代入

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!).

$@
最も有名なシェルの移植性の問題の一つは,`"$@"'との関連です.位置 に依存する引数が無いとき,@acronym{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を含め,古い@acronym{BSD}シェルはシェルの代入に対して コロンを受け入れず,文句を言って終了します.
${var=literal}
引用符で囲まれていることを確かめてください.
: ${var='Some words'}
それ以外のDigital Unix V 5.0のようなシェルでは,"bad substitution"の ために終了します. Solarisの@command{/bin/sh}にはこの解釈に恐ろしいバグがあります.変数を `}'を含む文字列に設定する必要があることを想像してください.この `}'文字で,影響ある変数が既に設定されているとき,Solarisの @command{/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}'で正確に動作 することです.すなわち,引用符で囲まない場合です.悪いニュース としては,@acronym{QNX} 4.25は,listdefaultの@emph{最後 の}項目に設定することです! 移植性の高い方法は,Ultrixで八番目のビットを二回切替えるために,二重 (引用符による)代入を使用することです.
list=${list="$default"}
...しかし,Solarisの`}'のバグ(上記を参照してください)には用 心してください.安全にするには,以下を使用してください.
test "${var+set}" = set || var={value}
`commands`
一般的には意味がありませんが,Ash 0.2では最適化のためコマンドを実行す るためサブシェルをforkしないので,副作用のある単一の組み込み物を代入し ないでください. 例えば,@command{cd}が何も出力しないことを調査したい場合,以下のことが 生じるかもしれないので,`test -z "`cd /`"'を使用しないでください.
$ pwd
/tmp
$ test -n "`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)

代入

列にいくつかの変数を設定するとき,評価の順序が定義されていないことを覚 えておいてください.例えば,`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

そして,更に悪いことに,@acronym{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 シェルの代入.

特殊なシェル変数

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

CDPATH
この変数が設定されているとき,それはcdが相対的なファイル名で呼 び出されるときに検索するディレクトリのリストを設定します. @acronym{POSIX} 1003.1-2001では,CDPATHで空ではないディレクトリ 名が正しく使用されている場合,cdは絶対的なファイル名を結果とし て出力することになっています.残念ながらこの出力では,absがパス を二回受けとるので,`abs=`cd src && pwd`'のような慣用句が駄目にな ります.また,多くのシェルは,この部分の@acronym{POSIX}に準拠していま せん.例えば,@command{zsh} は,`.'以外のディレクトリ名が CDPATHで選択されている場合以外,結果を出力しません. 実際,この問題があるシェルは@command{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ロケールを仮定していて, @acronym{POSIX}ではCロケールが要求される場合はロケールの環境変数を `C'に設定する必要があるので,Autoconfが生成したスクリプトは通常, これらのすべての変数を`C'に設定します.しかし,非標準の古いシステ ム(特に@acronym{SCO})では,ロケールの環境変数が`C'に設定されてい る場合は壊れてしまうので,これらのシステムでAutoconfが生成したスクリプ トを実行するとき,代わりに変数を未設定(unset)にしてください.
LANGUAGE
@env{LANGUAGE}は@acronym{POSIX}で指定されていませんが,それは場合によっ ては@env{LC_ALL}に優先させる@acronym{GNU}の拡張なので,Autoconfが生成 したスクリプトはそれも設定します.
LC_ADDRESS
LC_IDENTIFICATION
LC_MEASUREMENT
LC_NAME
LC_PAPER
LC_TELEPHONE
これらのロケール環境変数は@acronym{GNU}の拡張です.それらは,上記の @acronym{POSIX}の仲間(@env{LC_COLLATE}など)のように扱われます.
LINENO
ほとんどの近代的なシェルは,現在の行番号をLINENOで提供していま す.その値は,現在のコマンドの最初の行番号です.Autoconfは近代的なシェ ルで@command{configure}の実行を試みます.利用可能なそのようなシェルが 無い場合,それぞれの文字列$LINENO(英数文字が続かない)をインスタ ンスを行番号で置換するために,Sedに前もって渡す手法を用いて, LINENOの実装を試みます. 実行時の動作が異なるので,@command{eval}でLINENOに依存すべきで はありません.また,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'を実行しているとき,@command{zsh}は@samp{$NULLCMD >foo}を実行します.BourneシェルはNULLCMD`:'だと考えます が,@command{zsh}はBourneシェル互換モードでも,NULLCMD`cat'に設定します.NULLCMDの設定を忘れた場合,スクリプトは 標準入力からのデータ待ちのためサスペンド状態になるかもしれません.
ENV
MAIL
MAILPATH
PS1
PS2
PS4
これらの変数は,対話的なシェルに対してのみ影響すると考えられるので,シェ ルスクリプトに対して問題はありません.しかし,少なくとも一つのシェル (pre-3.0 UWIN @command{ksh})はそれが対話的かどうかを混同し,つま り(例えば)@env{PS1}の副作用として,`$?'を予期せず変更するはずです. このバグを回避するために,Autoconfが生成したスクリプトは以下のようなこ とをします.
(unset ENV) >/dev/null 2>&1 && unset ENV MAIL MAILPATH
PS1='$ '
PS2='> '
PS4='+ '
PWD
@acronym{POSIX} 1003.1-2001は,@command{cd}と@command{pwd}が現在のディ レクトリの論理的なパスを示す@env{PWD}環境変数を必ず更新することを要求 していますが,伝統的なシェルはこれをサポートしていません.一つのシェル の実体が@env{PWD}を管理していて,サブディレクトリと別のシェルは @env{PWD}を知らずに@command{cd}を実行する場合,これで混乱するはずです. この状況では,@env{PWD}は間違ったディレクトリを示します.`$PWD'の 代わりに``pwd`'を使用してください.
status
この変数は,zsh(少なくとも3.1.6)での`$?'へのエイリアスで, そのため読み出し専用になっています.使用しないでください.
PATH_SEPARATOR
設定されていない場合,@command{configure}はビルドシステムに対する適切 なパスの分離子を検出し,PATH_SEPARATOR出力変数をそれに応じて設 定します. DJGPPシステムでは,パス分離子を制御するために,PATH_SEPARATOR環 境変数を@command{bash}が(PATHのような)特定の環境変数を設定する ために使用している`:'または`;'のいずれかに設定することが可能 です.これは@command{bash}内部でのみ動作するので,パス分離子として `;'がサポートされていないファイル内で代入する方が安全だろうという 理由から,@command{configure}で標準的な@acronym{DOS}のパス分離子 (`;')を検出したいことでしょう.そのため,この変数をunsetするか, `;'に設定してください.
RANDOM
RANDOMを提供するシェルも多くあり,その変数は使用するたびに異な る整数を返します.その値が使用されていないとき,変更さることはほとんど ありませんが,IRIXQ 6.5では毎回値が変更されます.これは, @command{set}を使用して監視すべきです.

シェル組み込みの制限

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

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

@command{.}
通常のファイル(`test -f'を使用してください)を用いるときだけ @command{.}コマンドを使用してください.例えば,Bash 2.03は, `. /dev/null'で固まります.また,引数にスラッシュを含まない場合は @command{.}は@env{PATH}を使用するので,現在のディレクトリのファイル `foo'で@command{.}を使用したい場合,`. ./foo'を使用する必要 があることを覚えておいてください.
@command{!}
@command{!}を使用することは不可能です.コードを書き換える必要がありま す.
@command{break}
`break 2'の使用などは安全です.
@command{cd}
@acronym{POSIX} 1003.1-2001では,@command{cd}が@option{-L} ("論理的") と@option{-P} ("物理的")オプションをサポートし,@option{-L}がデフォ ルトであることを要求しています.しかし,伝統的なシェルはこれらのオプショ ンをサポートしておらず,@command{cd}コマンドは@option{-P}のように動作 します. 移植性の高いスクリプトは,どちらのオプションもサポートしていると仮定す べきではなく,どちらの動作もデフォルトと仮定すべきではありません.これ はちょっとトリッキーで,例えば,@acronym{POSIX}のデフォルトの動作では, 現在の論理的なディレクトリがシンボリックリンクの場合,`ls ..'`cd ..'では異なるディレクトリを参照している可能性があります. dir`..'の要素が無い場合,@command{cd dir}を使用して も安全です.また,Autoconfが生成するスクリプトは,ac_top_srcdir のような変数を計算するとき,この問題を調査するので (see section コンフィグレーション作業の実行),これらの変数で@command{cd}しても安全 です. @command{pwd}コマンドの議論も参照してください.
@command{case}
引数を引用符で囲む必要はありません.分離は実行されません. 最後の`;;'は不要ですが,使用した方が良いでしょう. fnmatchのバグのため,@command{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 2.8のBourneシェルがそ うです.
$ case foo in (foo) echo foo;; esac
error-->syntax error: `(' unexpected
@command{echo}
単純なechoですが,移植性の問題の根源として最も驚くべきものかも しれません.移植性の高い`echo'を使用することは,オプションとエス ケープシーケンスを削除しない限り不可能です.移植性を目標とする新しいア プリケーションでは,`echo'の代わりに`printf'を使用すべきです. オプションを期待しないでください.ECHO_Nなどの,@option{-c}をシ ミュレーションする方法は,See section 出力変数のプリセット. 引数へのバックスラッシュは,処理について同意がとれていないので使用しな いでください.`echo '\n' | wc -l'を用いれば,Digital Unix 4.0と @acronym{MIPS RISC/OS} 4.52の@command{sh}では答えは2になりますが, Solarisの@command{sh},Bash,そしてZsh(の@command{sh}エミュレーション モード)では答えは1になります.問題が本当に@command{echo}にあることに注 意してください.全てのシェルは,`'\n''をバックスラッシュと `n'の組み合わせであると解釈します. これらの問題のため,不定の文字を含む文字列を@command{echo}に渡さないで ください.例えば,fooの値がバックスラッシュを含んでおらず, `-' で始まらないことを知っている場合,`echo "$foo"'は安全で すが,それ以外では以下のようなヒアドキュメントを使用すべきではありませ ん.
cat <<EOF
$foo
EOF
@command{exit}
@command{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 $?'を使用すると期待される動作に復帰します. @command{autoconf}が生成するようなシェルスクリプトなどには,以前の終了 状態をクリーンアップする仕掛けを使用しているものもあります.シェルの最 後のコマンドがゼロではないステータスで終了した場合も,呼び出し側がエラー の発生を報告できるように,ゼロでないステータスで終了する仕掛けがありま す. 残念ながら,Solaris 8 @command{sh}のように,シェルによっては exitコマンドの引数を無視する仕掛けが存在するものもあります.こ れらのシェルでは,その仕掛けで呼び出しがプレーンのexitによるも のなのか,exit 1によるものなのか決定できません.exitを直 接呼び出す代わりに,この問題を回避するためにAC_MSG_ERRORを呼び 出してください.
@command{export}
組み込みの@command{export}は,シェル変数を@dfn{環境変数(environment variable)}に複製します.変数がエクスポートされて更新される度に,環境変 数も更新されます.反対に,環境変数はがシェルから読み出される度に,開始 時にエクスポートされたものとして印のついたシェル変数をインポートするべ きです. ああ,Solaris 2.5,IRIX 6.3,IRIX 5.2,@acronym{AIX} 4.1.5, そしてDigital UNIX 4.0のような多くのシェルは,受けとった環境変数 を@command{export}することを忘れています.結果として,二つの変数は共存 しています.環境変数とシェル変数の二つです.以下のコードは,この失敗を 説明するものです.
#! /bin/sh
echo $FOO
FOO=bar
echo $FOO
exec /bin/sh $0
環境変数で`FOO=foo'として実行した場合,これらのシェルはそれぞれ `foo'`bar'を交互に出力しますが,`foo'を出力した後に, 続けて`bar'を出力します. このため,それぞれ更新した環境変数を再び@command{export}すべきです.
@command{false}
@command{false}がステータス1で終了することを期待してはいけません. Solaris 8のネイティブなBourneシェルは,ステータス255で終了します.
@command{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+"$@"}'で単語の分離を試みるのことを覚えておいてください. `$@'の詳細は,section シェルの代入を参照してください.
@command{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
@command{if}の終了ステータスをリセットしないシェルもあります.
$ if (exit 42); then true; fi; echo $?
42
そこでは,適切なシェルなら`0'を出力すべきです.これは,異常終了と なるので,`Makefile'では特に問題です.これが,Automakeが生成する ような適切に書かれている`Makefile'がごちゃごちゃした構成になって いる理由です.
if test -f "$file"; then
  install "$file" "$dest"
else
  :
fi
@command{pwd}
最近のシェルを用いると,@command{pwd}は"論理的な"ディレクトリ名を出 力し,その構成要素にはシンボリックリンクがある可能性があります.これら のディレクトリ名は,構成要素はすべてディレクトリとなっている"物理的な" ディレクトリ名とは異なります. @acronym{POSIX} 1003.1-2001では,@command{pwd}は,@option{-L} ("論理 的") と@option{-P} ("物理的")オプションをサポートし,@option{-L}が デフォルトになっている必要があります.しかし,伝統的なシェルはこれらの オプションをサポートしておらず,@command{pwd}コマンドは@option{-P}のよ うに動作します. 移植性の高いスクリプトでは,どちらのオプションもサポートしていると仮定 すべきではなく,どちらの動作もデフォルトと仮定すべきではありません.ま た,多くのホストは`/bin/pwd'`pwd -P'と同じですが, @acronym{POSIX} はこの動作を要求しておらず,移植性の高いシェルではそれ に依存すべきではありません. 通常,そのまま@command{pwd}を使用するのが最善でしょう.最近のホストで は,これで論理的なディレクトリ名を出力し,以下の利点があります. @command{cd}コマンドでの議論も参照してください.
@command{set}
この組み込みコマンドは,一般的なダッシュで始まる引数の問題に直面します. BashやZshのような現在のシェルでは,オプションの終りを指定する @option{--}(@option{--}以降の全ての引数は,例えば`-x'であってもパ ラメータです)を理解しますが,ほとんどのシェルは,オプションではない引 数が見つかるとすぐにオプションの処理を単純に停止します.このため,オプ ションの処理を終了するために`dummy'や単純に`x'を使用し,それ を取り出すために@command{shift}を使用してください.
set x $my_list; shift
すべてのオプションを認識しないこととは"反対"の問題(例えば,`set -e -x'`-x'をコマンドラインに割り当てるといった問題)があるシェル もあります.以下のように省略した方が良いでしょう.
set -ex
@command{shift}
@command{shift}するものが無いとき,@command{shift}を使用することは悪い 考え方であるだけでなく,移植性が無くなってしまいことも追加されてしまい ます.@acronym{MIPS RISC/OS} 4.52のシェルは,それを廃棄してしまいます.
@command{source}
@acronym{POSIX}が要求していないので,このコマンドは移植性がありません. 代わりに@command{.}を使用してください.
@command{test}
testプログラムは,多くのファイルと文字列のテストを実行する方法 です.それは別名の`['で呼び出されることも多いのですが,M4の引用符 文字という問題から,Autoconfのコードではその名前を使用することが要求さ れています. testを使用して複数の調査を行う必要がある場合,testの演算 子の`-a'`-o'の代わりに,シェル演算子の`&&'`||' で組み合わせてください.System Vでは,`-a'`-o'の優先順位は, 単項演算子とは間違った関係になっています.従って,@acronym{POSIX}はそ れらを指定しないので,それを使用すると移植性が無くなります.同じ文で `&&'`||'を組み合わせる場合,同じ優先順位があることを覚えて おいてください. @command{test}で`!'を使用してもかまいませんが,@command{if}ではで きません.`test ! -r foo || exit 1'
@command{test (files)}
@command{configure}スクリプトでクロスコンパイルのサポートを可能にする ため,ホストシステムの代わりに,ビルドシステムの特徴のテストは,何もす べきではありません.しかし,任意のファイルの存在を調査する必要があるこ とが判明するかもしれません.そうするために`test -f'`test -r'を使用してください.4.3@acronym{BSD}には`test -x'が無いので使 用しないでください.また,Solaris 2.5には`test -e'が無いので使用 しないでください.
@command{test (strings)}
testは引数をオプションとして解釈するので(例えば, `string = "-n"'),特にstringがダッシュで始まる場合, `test "string"'を避けてください. 一般に信じられていることとは反対に,`test -n string'`test -z string'は,移植性があります.それにもかか わらず,(Solaris 2.5,@acronym{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
ああ,@acronym{POSIX}の構文`[!...]'をサポートしていないシェル は知りませんが,文字クラスの否定は移植性が無いかもしれません(対話的モー ドでは,@command{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"'より堅牢です.
@command{trap}
少なくとも,シグナルの1,2,13,そして15をトラップすることは安全です. また,0のトラップも可能で,すなわち,スクリプトが終るとき(明示的な @command{exit}やスクリプトの終り)に@command{trap}を実行するということ です. @acronym{POSIX}では,この点は絶対的に明確ではありませんが,`$?'を トラップするとき,トラップの前の最後に実行されたコマンドの終了ステータ スが設定されるべきだということは,広く認められています.曖昧な部分は以 下のように要約可能です."トラップが@command{exit}で開始されるとき,実 行された最後のコマンドは何でしょう?@command{exit}の直前ですか? それとも@command{exit}自身ですか?" Bashは@command{exit}を最後のコマンドと考えますが,ZshとSolaris 8 @command{sh}は,トラップが実行されたとき@command{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'を実行し,最初の@command{exit}はZshに対する42の終了ステー タスを設定するために使用され,二番目は,トラップを誘発し,Bashに対して 終了ステータスとしての42を渡すためです. Free@acronym{BSD} 4.0のシェルには,以下のバグがあります.コードが内部 @command{trap}の場合,空行で`$?'が0にリセットされます.
$ trap 'false

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

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

@command{unset}
@command{unset}のサポートを仮定することはできません.それにもかかわら ず,PS1のような邪魔な変数を利用不可能にすることは非常に役立つの で,存在をテストし,提供されていればそれを使用し, @command{unset}がサポートされていないときは,無効にする値を与えてくだ さい.
if (unset FOO) >/dev/null 2>&1; then
  unset=unset
else
  unset=false
fi
$unset PS1 || PS1='$ '
無効にする値については,See section 特殊なシェル変数. また,環境変数 のcaseについては@command{export}のドキュメントsection シェル組み込みの制限も参照してください.

通常のツールの制限

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

@command{awk}
ユーザ関数呼び出しで,カッコの前に空白を残さないでください. @acronym{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を拒絶する単純なテストを使用したりしないでください.
@command{cat}
オプションに依存しないようにしてください.しかし,表示不可能な文字を表 示するオプション@option{-v}は,移植性がありそうです
@command{cc}
`cc foo.c -o foo'のようなコンパイルが失敗したとき,(Reliant UNIXのCDSのように)`foo.o'を残すコンパイラもあります. HP-UX @command{cc}は,プリプロセスとアセンブラを行なう`.S'ファイ ルを受け入れません.`cc -c foo.S'は成功したように見えますが,実際 には何もしません. `cc foo.c'で生成されるデフォルトの実行形式は,以下のようになるは ずです.
@command{cmp}
@command{cmp}は,二つのファイルの生のデータの比較を実行しますが, @command{diff}は二つのテキストファイルを比較します.そのため, @acronym{DOS}のファイルを比較する場合,二つのファイルが異なっているか どうかを調査するだけの場合でも改行のエンコードの違いで見せかけの差が発 生することを避けるため,@command{diff}を使用してください.
@command{cp}
SunOS @command{cp}は@option{-f}をサポートしていませんが,その @command{mv}はサポートしています.@command{mv}と@command{cp}が @option{-f}に関して異なっている理由については由来が推測できます. @command{mv}はデフォルトで,読み込み専用のファイルを上書きする前にプロ ンプトを表示します.@command{cp}はそうではありません.そのため, @command{mv}には@option{-f}オプションが必要ですが,@command{cp}には不 要です.@command{mv}と@command{cp}は,読み込み専用のファイルに対して, 動作が異なり,その理由は,最も簡単な@command{cp}の形式では,読み込み専 用のファイルを上書きできませんが,最も簡単な@command{mv}形式では,それ が可能だということです.この理由は,@command{cp}はターゲットを書き込み アクセスで開くのに対し,@command{mv}は単純にlink(または,新しい システムではrename)を呼び出すためです. Bob Proulxは,`cp -p'は常に所有権のコピーを試みるとメモし ています.しかし,実際に所有権をコピーするかどうかは,カーネルで実装さ れているシステムポリシーの決定に依存します.カーネルが許可している場合 はそうなります.カーネルが許可していない場合は,そうなりません. @command{cp}自身が制御しているものではありません. SysVでは,ユーザはファイルを別のユーザにchown可能で,SysVにはstickyで はない`/tmp'もあります.それは疑い無く,敵意のあるユーザのいない ビジネス環境のSysVの遺産に由来しています.@acronym{BSD}は,rootだけが ファイルを@command{chown}可能にし,stickyな`/tmp'を使用して,これ をより安全なモデルに変更しました.それは疑い無く,キャンパス環境の @acronym{BSD}の遺産に由来します. Linuxはデフォルトで@acronym{BSD}に準拠していますが,@command{chown}可 能に設定することも可能です.別の例として,HP-UXはSysVに準拠しています が,最近のセキュリティモデルを使用するよう設定し,@command{chown}でき なくすることが可能です.それは管理者が設定可能なパラメータなので,動作 を示すためにカーネル名を使用することは不可能です.
@command{date}
@command{date}のバージョンによっては,特殊な%による指示語を理解しない ものもあり,残念ながら警告をする代わりに,それをそのまま通過させ,正し く終了します.
$ uname -a
OSF1 medusa.sis.pasteur.fr V5.1 732 alpha
$ date "+%s"
%s
@command{diff}
@option{-u}には移植性がありません, Tru64のように,実装によっては`/dev/null'の比較で失敗するものもあ ります.その代わりに空のファイルを使用してください.
@command{dirname}
全てのホストに動作する@command{dirname}があるわけではなく,その代わり にAS_DIRNAMEを使用すべきです(see section M4shでのプログラミング).例え ば以下のようにします.
dir=`dirname "$file"`       # This is not portable.
dir=`AS_DIRNAME(["$file"])` # This is more portable.
これは,@acronym{POSIX}で要求されている標準では,幾分微妙な扱いです. 例えばUN*Xでは`//1'`/'になるのでしょうか?以下はPaul Eggertの回答です.

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

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

@command{egrep}
@acronym{POSIX} 1003.1-2001では,もはや@command{egrep}を要求していませ んが,より古いホストの多くはまだ@acronym{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
@command{$EGREP}も@command{grep}の制限で苦しむことになります.
@command{expr}
`x'で始まる@command{expr}キーワードはないので,@command{expr}が wordを間違って解釈しないように,`expr x"word" : 'xregex''を使用してください. lengthsubstrmatch,そしてindexは使用し ないでください.
@command{expr (`|')}
`|'を使用することは可能です.@acronym{POSIX}では,`expr "' が空の文字列を返すことを必須としていませんが,空の文字列を用いて空の文 字列(またはゼロ)とともに`|'を用いたときの結果は安全ではありません. 例えば以下を考えます.
expr '' \| ''
@acronym{GNU}/Linuxと@acronym{POSIX}.2-1992では,この場合は空の文字列 を返しますが,伝統的なUNIXでは`0'を返します(Solarisはそのよ うな例の一つです).最近の@acronym{POSIX}.1-2001ドラフトでは,その指定 は伝統的なUNIXの動作に一致するよう変更されています(信じられないこ とですが,これを修正するには時すでに遅しです).同じ問題が,計算結果が 空の文字列になるときにも,以下の状態では発生します.
expr bar : foo \| foo : bar
空の文字列を避けることで,この移植性の問題を避けてください.
@command{expr (`:')}
Solarisではサポートされていないので,パターン内に,`\?'`\+',そして`\|'を使用しないでください. @acronym{POSIX}標準では,`expr 'a' : '\(b\)''`0'を出力する か空の文字列を出力するのかは明確ではありません.実際問題として,それは ほとんどのプラットフォームで空の文字列を出力しますが,移植性の高いスク リプトでは,これを仮定すべきではありません.例えば,@acronym{QNX} 4.25 ネイティブの@command{expr}は`0'を返します. 均一な動作を得る手段として,デフォルト値として空の文字列を使用すること になっていると信じているかもしれません.
expr a : '\(b\)' \| ''
残念ながら,これは元の式として正確に動作します.詳細は, `@command{expr' (`:')}の項目を参照してください. 古い@command{expr}の実装(例えば,SunOS 4の@command{expr}とSolaris 8の @command{/usr/ucb/expr})には,一致したサブ文字列が120バイトより長い場 合,@command{expr}が異常終了するという,思慮の欠けた長さの制限がありま す.この状況では,@command{expr}が失敗した場合,`echo|sed'に頼り たいと思うかもしれません. 残っているものはそれだけではありません! @acronym{QNX} 4.25の@command{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
実際に,(@command{sed}のような)他の手法で@command{expr}プログラムで異 常終了を捕獲する準備がある場合,結果を二回得る可能性があるので,これは 大きな問題となります.例えば以下を考えます.
$ expr 'a' : '\(a\)' || echo 'a' | sed 's/^\(a\)$/\1/'
ほとんどのホストでは`a'を出力しますが,@acronym{QNX} 4.25では `aa'になります.単純な回避方法として,@command{expr}でのテストを 構成し,結果によって@command{expr}や@command{false}で変数を設定する方 法を使用します.
@command{fgrep}
@acronym{POSIX} 1003.1-2001では,もはや@command{fgrep}を要求していませ んが,より古いホストの多くはまだ@acronym{POSIX}のgrep -Fでの置 換をサポートしていません.この問題を回避するため,AC_PROG_FGREP を呼び出し,$FGREPを使用してください.
@command{find}
オプション@option{-maxdepth}は@acronym{GNU}特有のようです.Tru64 v5.1, Net@acronym{BSD} 1.5,そしてSolaris 2.5の@command{find}コマンドはそれ を理解しません. `{}'の置換は,引数が正確に{}の場合のみ保証され,それが 引数の一部の場合は保証されません.例えば,DUとHP-UX 10.20とHP-UX 11で は保証されません.
$ touch foo
$ find . -name foo -exec echo "{}-{}" \;
{}-{}
一方,@acronym{GNU} @command{find}は`./foo-./foo'を報告します.
@command{grep}
System Vの`grep -s'はエラーメッセージのみ抑制し,出力を抑制しない ので,出力を抑制するために`grep -s'を使用しないでください.その代 わりに(ファイルが存在しない場合) grepの標準出力と標準エラー出力 を`/dev/null'へリダイレクトしてください.一致が見つかったかどうか を決定するために,grepの終了ステータスを調査してください. 最後のパターンのみ尊重するgrep(例えば,@acronym{AIX} 6.5と Solaris 2.5.1)もあるので,@option{-e}で複数の正規表現を使用しないでく ださい.どちらにしろ,Stardent Vistra SVR4のgrepには@option{-e} がありません@enddots{} その代わりに拡張した正規表現と代入を使用してく ださい. Irix 6.5.16mの@command{grep}は,それをサポートしていないので, @option{-w}に依存しないようにしてください.
@command{ln}
@option{-f}オプションがある@command{ln}に依存しないようにしてください. 古いシステムではシンボリックリンクは利用不可能です.移植性のある代替物 `$(LN_S)'を使用してください. 2.04以前のバージョンのDJGPPに対して,@command{ln}は実行形式へのソフト リンクを,実際のプログラムを呼び出すスタブを生成することでエミュレート します.この機能は,Unix独自の実行形式以外のファイルでも動作します.そ のため,`ln -s file link'`link.exe'を生成し,それは実行さ れた場合に`file.exe'の呼び出しを試みます.しかしこの機能は実行形 式に対してのみ動作するので,このシステムでは`cp -p'が代わりに使用 されます.DJGPPの2.04とそれ以降では完全なシンボリックリンクがサポート されています.
@command{ls}
移植性のあるオプションは@option{-acdilrtu}です.最近では,@option{-l} で所有者とグループを出力しますが,伝統的な@command{ls}はグループを省略 します. 最近では,すべての診断結果は標準エラー出力に出てきますが,伝統的な `ls foo'は,`foo'が存在しない場合,メッセージ`foo not found'を標準出力に出力します.伝統的な@command{ls}では,`.c'ファ イルが無い場合,`sources=`ls *.c 2>/dev/null`'`sources="*.c not found"'と等価なので,そのようなシェルコマンドを 書くときに注意してください.
@command{mkdir}
@command{mkdir}のオプションには移植性はありません.@samp{mkdir -p filename}の代わりにAS_MKDIR_P(filename)を使用すべき です(see section M4shでのプログラミング).
@command{mv}
移植性のあるオプションは,@option{-f}と@option{-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
この動作は,@acronym{POSIX}に準拠しています.

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

マウントポイントを跨ってディレクトリを移動することは移植性が無いので, @command{cp}と@command{rm}を使用してください. 開いているファイルの移動/削除は移植性がありません.以下の例はDOS/WIN32 では実行不可能です.
exec > foo
mv foo bar
以下も実行不可能です.
exec > foo
rm -f foo
@command{sed}
文字クラスの一部の場合でも,パターンに(エスケープされていない)セパレー タを含めるべきではありません.@acronym{POSIX}準拠では,Crayの @command{sed}は`s/[^/]*$//'を拒絶します.`s,[^/]*$,,'を使用 してください. sedのスクリプトは,八文字以上の分岐ラベルを使用すべきではなく,コメントを 含めるべきでもありません. Net@acronym{BSD} 1.4.2では,二番目のものをコマンドとして解釈しようと試 みるので,@command{sed}によっては,余分な`;'を含めてはなりません.
$ echo a | sed 's/x/x/;;s/x/x/'
sed: 1: "s/x/x/;;s/x/x/": invalid command code ;
@command{sed}によっては,入力バッファに4000バイトの制限があるものもあ るので,入力は妥当な長さの行にすべきです. `\|'の交換は一般的ですが,@acronym{POSIX}はそのサポートを要求して いないので,移植性の高いスクリプトでは避けるべきです.Solaris 8の @command{sed}は交換をサポートしていません.例えば,@samp{sed '/a\|b/d'} は,リテラル文字列`a|b'を含んでいる行のみ検出します. グループ内のアンカー(`^'`$')は移植性がありません. パターン内の入れ子状のカッコは,現在のホストでは完全に移植性あるものな のですが,SVR3のように古い@command{sed}の実装ではサポートされていませ ん. もちろんオプション@option{-e}には移植性がありますが,それは不要です. ダッシュで始まる有効なsedプログラムは無いので,明確にする役には立ちま せん.唯一の有効性は,以下のように字下げを強制的に行なうときです.
sed -e instruction-1 \
    -e instruction-2
これは以下の代わりのものです.
sed instruction-1;instruction-2
もう一つの垢抜けた伝説として,"マッチしたもの"を意味するsコマ ンドの一部を置換するとき,`&'を使用しても移植性はあるでしょう.す べてのベル研究所のV7 @command{sed}の子孫は(少なくとも,我々はそれより 古い@command{sed}を経験したことはありません)サポートしています. @acronym{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
@command{sed (`t')}
古いシステムには,新しいサイクルと開始するとき,その`t'フラグをリ セットすることを"忘れる" @command{sed}があるシステムもあります.例え ば,@acronym{MIPS RISC/OS}とIRIX 5.3で,以下の@command{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行はマッチ に失敗しますが,置換が失敗するとき,@command{sed}はtフラグをクリアする ことをサポートしていません.そのため,フラグがセットされているように見 えるb行は,それをクリアし,dへ移動し,その結果,`deleted'の代わり に`delete me'になります.三行目を処理しているとき,マッチを示すt がクリアされるため,フラグがセットされ,その結果,b行はフラグをクリア し移動します.最終的にフラグはクリアになっているので,四行目は正しく処 理されます. @command{sed}の`t'について覚えておくべきことは二つあります.最初 に,成功した置換によっては,置換の直前だけでなく`t'ジャン プすることを覚えておいてください.そのため,tフラグを実際にリセットす るために,ごまかしの`t clear; : clear'を使用してください. 二番目は,それぞれの新しいサイクルでフラグをクリアするのを @command{sed} に依頼することはできません. 上記のスクリプトの移植性の高い実装の一つは,以下のようになります.
t clear
: clear
s/keep me/kept/g
t end
s/.*/deleted/g
: end
@command{touch}
古い@acronym{BSD}システムには,空のファイルに対する@command{touch}のよ うなコマンドで,タイムスタンプを更新しない結果となるものもあるので,回 避するために,echoのようなコマンドを使用してください. @acronym{GNU} @command{touch} 3.16r(とそれ以前の全て)は,空のファイル が@acronym{NFS}でマウントされている4.2のボリュームのとき,SunOS 4.1.3 での動作で異常終了します.

Makeの制限

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

$<
@acronym{POSIX}では,`makefile'の構成物の`$<'は推測される規 則と`.DEFAULT'ルールのみで使用可能だと告げています.通常のルールでの その意味は明記されていません.例えば,Solaris 8の@command{make}はそれ を引数で置換します.
マクロ名へのアンダースコアの前置
NEWS-OS 4.2Rのように,マクロ名にアンダースコアを前置することをサポート していない@command{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のバージョンによっては,@command{make}には,バックスラッシュ以降 の複数の改行を,空ではない行も含めて読み込むものもあります.例えば以下 のようにします.
FOO = one \

BAR = two

test:
        : FOO is "$(FOO)"
        : BAR is "$(BAR)"
FOOone BAR = twoと等価です.それ以外の@command{make}で は,バックスラッシュは直後の行だけを含みます.
コメント内のエスケープされた改行
@acronym{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とサブ呼び出しの@command{make}
コマンドライン変数のfoo=barのような定義は,`Makefile'fooの定義に優先します.(@acronym{GNU} @command{make}のような) @command{make}の実装によっては,この優先はサブ呼び出しの@command{make} に伝搬します.これは可能ですが,@acronym{POSIX}では要求されていません.
% 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
サブ呼び出しの@command{make}にfoo=barの優先を伝搬したい場合,移 植性を持たせる方法が無いわけではありません.その一つは,すべての環境変 数を`Makefile'マクロ定義に優先させる-eオプションを使用し, fooを環境変数として定義する方法です.
% env foo=bar make -e
-eオプションは,自動的にサブ呼び出しの@command{make}に伝搬し, 環境変数は@command{make}の呼び出し間で継承されるので,fooマクロ はサブ呼び出しのmakeで期待したように優先されます. -eを使用することで,`Makefile'で通常定義されるその他のマク ロが環境変数に含まれている場合,予期しない副作用があるかもしれません. (以下のmake -eSHELLの注意も参照してください.) サブ呼び出しの@command{make}に優先物を伝搬させるもう一つの方法は, `Makefile'に手動で行なうことです.
foo = foo
one:
        @echo $(foo)
        $(MAKE) foo=$(foo) two
two:
        @echo $(foo)
そうする場合,ユーザが優先したいと思われるすべてのマクロを予測する必要 があります.
SHELLマクロ
@acronym{POSIX}準拠の@command{make}では,シェルプロセスを起動したり, `Makefile'ルールを実行するために,内部で$(SHELL)マクロを使用 します.これは@command{make}で提供される組み込みマクロですが, `Makefile'やコマンドライン引数で変更することが可能です. すべての@command{make}が,このSHELLマクロを定義するわけではあり ません.例えば,OSF/Tru64 @command{make}がそうです.この実装では,常に /bin/shを使用します.そのため,`Makefile'で常に SHELL を定義するのは良い考えです.Autoconfを使用している場合, 以下のようにしてください.
SHELL = @SHELL@
@acronym{POSIX}準拠の@command{make}では,make -eが使用されてい る場合でも,環境変数から$(SHELL)の値を入手してはなりません(そうでない 場合,SHELL=/bin/tcshの状況でルールによって何が起こるのか考えてみ てください). しかし,すべての@command{make}がこのような例外を実装しているわけではあ りません.例えば,OSF/Tru64 @command{make}はSHELLを使用しないの で,保護していなくても不思議ではありません.
% 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
ルール内のコメント
コメントをルールに書き込まないでください. タブで始まるものは,タブの直後に#が続いていても,すべて現在のルー ルのコマンドとして扱う@command{make}もあります.Tru64 Unix V5.1の @command{make}はその一つです.以下の`Makefile'で,シェルで@code{# foo}を実行します.
all:
        # foo
`obj/'サブディレクトリ
びっくりしたくなければ,サブディレクトリを`obj/'と命名しないでく ださい. `obj/'ディレクトリが存在する場合,@acronym{BSD} @command{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
@acronym{POSIX}では,VPATHサポートを指定していません.多くの @command{make}はVPATHサポートの形式がありますが,その実装は, @command{make}間で一貫していません. VPATH機能を必要としている人々への最高の提案は,@command{make}の 実装を選択しそれに固執するようにと言うことかもしれません. `Makefile' の結果は常に移植性があるとは限らないので,移植性の高い @command{make}を選択するのが良いでしょう(ヒント,ヒント). VPATHの実装の既知の問題には以下のものがあります.
VPATHと二重のコロンのルール
VPATHへの代入で,Sunの@command{make}は最初の二重コロンのルールの 組だけを実行します.(このコメントは,1994年からで,現在は無くなってい ます.SunOS 4では移植性があります.これが再生成された場合,それを説明 するテストケースを送ってください.)
推論ルールの$<
@command{make}の一つの実装では,VPATHディレクトリにこの必要条件 が見つかっても,$<が前置されません.これは以下のことを意味しま す.
VPATH = ../src
.c.o:
        cc -c $< -o $@
これで,`foo.c'が実際には`../src/'で見つかった場合でも, cc -c foo.c -o foo.oを実行します. これは以下のようにして修正可能です.
VPATH = ../src
.c.o:
        cc -c `test -f $< || echo ../src/`$< -o $@
この間に合わせ手法は,2000年のAutomakeで導入されましたが,正確な内容は 失われています.@command{make}の実装がこのように複雑になったか知ってい る場合,我々に報告してください.
明示的なルールで$<がサポートされていない
他でも述べたように,明示的なルールで$<を使用するのは移植性があり ません.必要条件のファイルは,ルール内で明示的な名前にすべきです. VPATHの検索で必要条件を見つけたい場合,手動でコード全体を書く必 要があります.例えば,上記と同じパターンを用いると以下のようになります.
VPATH = ../src
foo.o: foo.c
        cc -c `test -f foo.c || echo ../src/`foo.c -o foo.o
自動的なルールの再書き込み
SunOS @command{make}のように,VPATHで必要条件を探し,出現するた びに適切なルールにを再書き込みする@command{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を実行します.素晴らしいと思います. しかし,それ以外の@command{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の@command{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
最初の二つの@command{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
OSF/Tru64 @command{makeの@command{make}は,不思議なディレクトリの必要条件を作成する}
必要条件がVPATHのサブディレクトリにある場合,Tru64 @command{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{command}は,現在のディレクトリに作成された,空の `foo/bar'ディレクトリで実行されます.
ターゲットの探索
@acronym{GNU} @command{make}は,VPATHで見つかったファイルを使用 すべきとき決定するアルゴリズムは幾分複雑です.See section `How Directory Searches are Performed' in The @acronym{GNU Make Manual}. ターゲットのリビルドが必要な場合,@acronym{GNU} @command{make}は,この ターゲットをVPATHで検索している間に見つかったファイル名を廃棄し, `Makefile'で与えられたファイル名を使用して,ローカルなファイルを ビルドします.ターゲットをリビルドする必要が無い場合は,@acronym{GNU} @command{make}はVPATHで検索している間に見つかったファイル名を使 用します. @acronym{BSD} @command{make}のような,その他の@command{make}の実装は, より簡単に記述できます.VPATHで検索している間に見つかったファイ ル名は,ターゲットがリビルドを必要としているかどうかにかかわらず使用さ れます.このため<新しいファイルはローカルに作成されますが, VPATHに位置する既存のファイルは更新されます. VPATHで,autoconfパッケージのビルドを試みるとき(例えば, mkdir build; ../configure),@acronym{GNU} @command{make} @command{make}は`build'ディレクトリですべてのローカルにビルドしま すが,@acronym{BSD} @command{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       # BSD make
Building foo.x
Building ../bar.x
言及する価値のあるもう一つの点は,@acronym{GNU} @command{make}が一度 VPATHのファイル名を無視する事に決めると(例えば,上記の例の `../bar.x'を無視する),ターゲットが他のルールの必要条件になったとき, それを無視し続けます. 以下の例では,bar.x: newer.xのルールを実行している間に `bar.x'VPATHの結果を無視するので,.x.yのルールを実 行する前に,@acronym{GNU} @command{make}はVPATH`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       # BSD make
Building ../bar.x
cp ../bar.x bar.y
bar.x: newer.xルールからコマンドを削除した場合,手品のように動 作し始める事に注意してください.@acronym{GNU} @command{make}は, bar.xが更新されていない事を知っているので,VPATH (`../bar.x')の結果がうまく使用できるという結果を廃棄しません.
% 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       # BSD make
cp ../bar.x bar.y
単一のサフィックスルールと分離された依存性
単一のサフィックスルール(Single Suffix Rule)は,基本的に(推測さ れる)通常のサフィックスルール(`.from.to:')ですが,ディスティ ネーション(destination)サフィックスは空(`.from:')です. 分離された依存性(Separated dependencies)は,ルールを定義すること 無く,ターゲットの必要条件のリストを単純に参照します.通常,一方ではルー ルを,もう一方で依存性をリストアップすることが可能です. Solarisの@command{make}は,単一のサフィックスルールで定義されたターゲッ トに対する,分離された依存性をサポートしていません.
$ cat Makefile
.SUFFIXES: .in
foo: foo.in
.in:
        cp $< $
$ touch foo.in
$ make
$ ls
Makefile  foo.in
一方@acronym{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
結果として,そのような状況では,ターゲットルールを書く必要があります.


Go to the first, previous, next, last section, table of contents.