[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [表紙] | [目次] | [索引] | [検索] [上端 / 下端] [?] |
独自の調査を書いているとき,コードを移植性の高いものにするため,使用を避 けるべきシェルスクリプトプログラムのテクニックもあります.Bourneシェルと, BashとKornシェルのような上位互換性があるシェルは,何年もかけて進展しまし たが,問題を避けるために,UNIXバージョン7の以降の1977年頃に加えられ た機能を利用しないでください(see 節 6.7 システム).
シェル関数,エイリアス,無効な文字クラスや,Bourneシェル互換のものでは見 つからないすべての機能を使用するべきではありません.最小公倍数に制限され てます.unset
さえ,全てのシェルではサポートしていません!また,以 下のように,インタプリタ仕様として,感嘆符の後にスペースを含めてください.
#! /usr/bin/perl |
パスの前のスペースを省略する場合,(DYNIXのような)4.2BSDを基本 とするシステムは,`#! /'は4バイトのマジックナンバーとして解釈される ので,その行を無視します.古いシステムでは,`#!'行の長さにも小さな 制限があり,例えばSunOS 4では,(改行を含めず)32バイトになります.
configure
スクリプトで実行すべき外部プログラムの設定は,かなり 小さくなっています.リストは,See 節 `Utilities in Makefiles' in
これらの外部ユーティリティには,移植性の高い機能のサブセットがあります. 10.10 通常のツールの制限を参照してください.
シェルに関するドキュメントのソースは他にもあります.例えば, the Shell FAQsを参照し てください.
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [表紙] | [目次] | [索引] | [検索] [上端 / 下端] [?] |
いくつかのシェルのファミリーがあり,最も重要なものは,Bourneファミリーと Cシェルファミリーで,それらは全く互換性がありません.移植性の高いシェル スクリプトを書きたい場合,Cシェルファミリーのメンバーは避けてください. the Shell difference FAQには,Unixシェルの小さな歴史と,それらの間の比較が 書かれています.
以下で,Bourneシェルファミリーのメンバーを,いくつか説明していきます.
ash
は,動作の軽いBourne互換シェルとしてGNU/Linuxと BSDシステムでよく使用されています.Ash 0.2には0.3.xシリーズで 修正されているバグがいくつかありますが,バージョン0.2は多くの GNU/Linux 配布物で配布されているので,移植性の高いシェルスクリ プトではそれを回避すべきです.
Ash 0.2での互換性のため以下のようにしてください.
foo= false $foo echo "Don't use it: $?" |
cat ${FOO=`bar`} |
bash
を実行しているかどうかを検出するために, BASH_VERSION
が設定されているかどうかをテストしてください.その拡 張を利用不可能にし,POSIX互換性を要求するため,`set -o posix' を実行してください.詳細は,See 節 `Bash POSIX Mode' in
bash
は,set
組み込みコマン ドの出力に対して,その出力をより容易に評価できるように設計されているので, 異なる書式を使用しています.しかし,この出力はそれ以前のバージョンの bash
(や,おそらくそれ以外の多くのシェル)と互換性がありません. そのため,bash
2.05やそれ以上のものをconfigure
の実行 に使用している場合,それ以外のすべてのビルドの作業に対しても,同じように bash
2.05を使用する必要があるでしょう.
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_VERSION
が設定されているかどうかをテストしてください.デフォル トで,zsh
はBourneと互換性はありません.`emulate sh'を実行し,NULLCMD
を`:'に設定する必要があります.詳細は, See 節 `Compatibility' in
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標準に適合しているシェ ルがどこかにあるのですが,問題はそれを見つけることです.
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [表紙] | [目次] | [索引] | [検索] [上端 / 下端] [?] |
`\'は,次のシンボルと一緒になって特別の意味を持たないので,維持され る`\'に依存しないでください.OpenBSD 2.7のネイティブな /bin/sh
では,`\"'は`"'に展開され,ヒアドキュメントで は引用符で囲まれていない分離子として用いられます.一般的な規則として, `\\'が`\'に展開される場合,`\'を得るために`\\' を使 用してください.
OpenBSD 2.7の/bin/sh
では,以下のようになります.
$ cat < |
そして,Bashでは以下のようになります.
bash-2.04$ cat < |
多くの古い(Bourneシェルを含む)シェルでは,ヒアドキュメントは非効率に実装 されています.大きなヒアドキュメントを間違って扱うシェルもあります.例え ば,Solaris 8 dtksh
はksh
M-12/28/93dで提供されていて, ヒアドキュメントを1024バイトのバッファの境界で間違った変数の展開を生じま す.ユーザは一般的に,より速くより信頼性の高いシェルを使用して,これらの 問題を修正することが可能で,例えば,そのまま`./configure'するのでは なく,コマンド`bash ./configure'を使用します.
シェルによっては,単一の文の中にヒアドキュメントが多過ぎるとき,非常に非 効率になるものもあります.例えば,`configure.ac'に以下のようなもの 含めたとします.
if |
シェルは,その中のそれぞれのヒアドキュメントに対して一時ファイルを作成し ながら,if
/fi
の文脈全体をパースします.fork
ごとにそ のようなヒアドキュメントに対してリンクを作成するシェルもあり,インストー ルされた後のクリーンアップコードで正しく削除されます.それは,シェルが永 久に受け入れられるリンクを作成しているのです.
if
/fi
の外部のテストを移動したり,複数のif
/fi
の文脈を作成したりすることで,かなり動作が改善されるでしょう.とにかく, こういった構成は,典型的なAutoconfの使用では正しくありません.実際,M4マ クロは,シェルの条件文を見ることができないので,それは推奨されておらず, 条件分岐の前にそれが展開され,実行時に条件文が失敗だと分かるとき,マクロ 展開に失敗するかもしれず,マクロの実行を完全に終了できないでしょう.
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [表紙] | [目次] | [索引] | [検索] [上端 / 下端] [?] |
システムによっては,明らかに不可解なのですが,特殊な目的で使用しているた め,ファイルディスクリプタには使用すべきではないものもあります.
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.9 シェル組み込みの制限を参照してください.
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [表紙] | [目次] | [索引] | [検索] [上端 / 下端] [?] |
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 節 10.9 シェル組み込みの制限).
また,コロンがデバイス指定の一部として使用されているので,これらのシステ ムではそれをパスの分離子として使用していません.パスを作成しているときや パスにアクセスしているときは,代わりにPATH_SEPARATOR
出力変数を使 用してください.configure
は,開始時にこれを適切な値(`:' または`;')に設定します.
ファイル名にも余計な注意が必要になります.(DJGPPのような) autoconf
を十分に実行できるUnixのようなDOSベースの環 境では,通常長いファイル名を適切に扱うことが可能ですが,パッケージを壊し てしまう深刻な制限も残っています.これらの問題のいくつかは, doschk パッ ケージで容易に検出することが可能です.
以下は簡単な全体像です.問題には,適用を示すためSFN/LFNで印が ついています.SFNは,Windows下のDOS窓ではなく,プレーンな DOSにのみ関連する問題を意味し,一方LFNは,Windowsでも存在 する問題を意味しています.
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 |
autoconf
に対してはあまり重要ではない問題です.
make
にも影響します.ディレクトリに `INSTALL'という名のファイルがある場合,`make install'は (`install'がPHONY として印がついていないとき)何もしません.
注意:これは通常,ファイル名をユニークにするために短いバージョンでは数字 の後置を使用するので,Windowsでは問題になりません.しかし,レジストリの 設定でこの動作を停止可能です.これで長いファイル名を含むファイルのツリー を,SFNとLFNの環境で共有することが可能になりますが,上記の問題 は同様に存在します.
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [表紙] | [目次] | [索引] | [検索] [上端 / 下端] [?] |
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!).
$@
この移植性の問題を回避する伝統的な方法は,`${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}
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}
default="yu,yaa" : ${var="$default"} |
それはvarを`M-yM-uM-,M-yM-aM-a'に設定し,すなわち,全ての文字 の八番目のビットがセットされるでしょう.`$var'を展開するとき,シェ ルが八番目のビットを明示的にリセットするので,単純に`echo $var'を使 用している現象が分かりません.このシェルにその違反で混乱させる二つの方法 は,以下のようになります.
$ cat -v < |
それと以下です.
$ 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は,listをdefaultの最後の項目 に設定することです!
移植性の高い方法は,Ultrixで八番目のビットを二回切替えるために,二重(引 用符による)代入を使用することです.
list=${list="$default"} |
...しかし,Solarisの`}'のバグ(上記を参照してください)には用心 してください.安全にするには,以下を使用してください.
test "${var+set}" = set || var={value} |
`commands`
例えば,cd
が何も出力しないことを調査したい場合,以下のことが生 じるかもしれないので,`test -z "`cd /`"'を使用しないでください.
$ pwd /tmp $ test -z "`cd /`" && pwd / |
`foo=`exit 1`'の結果は,読者への演習問題として残しておきます.
$(commands)
$ 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))'は現在のシェルではコマンドではなく数式だと勘 違いするので,コマンドがカッコで始まらないように確かめて下さい.この勘違 いを避けるため,二つの開カッコの間にはスペースを挿入して下さい.
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [表紙] | [目次] | [索引] | [検索] [上端 / 下端] [?] |
列にいくつかの変数を設定するとき,評価の順序が定義されていないことを覚え ておいてください.例えば,`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 |
デフォルト値を代入するために,以下のアルゴリズムを使用してください.
: ${var='my literal'} |
: ${var="$default"} |
var=${var="$default"} |
test "${var+set}" = set || var='${indirection}' |
ほとんどの場合,`var=${var="$default"}'で良いのですが,駄目なとき は後者を使用してください.正当性のための, `${var:-value}'と`${var=value}' の 項目は,See 節 10.5 シェルの代入.
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [表紙] | [目次] | [索引] | [検索] [上端 / 下端] [?] |
一列にある二つの開カッコは,シェルの実装によっては間違って処理されること を覚えておいて下さい.例えば,`pdksh' 5.2.14では以下のコードのパー スを失敗します.
if ((true) || false); then echo ok fi |
この問題を回避するため,二つの開カッコの間にスペースを挿入して下さい. `$(('に関する同様な問題と回避方法があります.10.5 シェルの代入を参照して下さい.
POSIXでは,以下のような開カッコを用いたcase
パターンのサ ポートを要求しています.
case $filename in (*.c) echo "C source code";; esac |
しかし,この例の(
には古いBourneシェルの実装で移植性がないものもた くさんあります.安全のため削除すべきでしょう.
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [表紙] | [目次] | [索引] | [検索] [上端 / 下端] [?] |
シェルの動作に深く影響するため,使用すべきではないシェル変数もあります. シェルからまともな動作に戻るため,unsetすべき変数もありますが, unset
は移植性が無く(see 節 10.9 シェル組み込みの制限),代替値が 必要になります.これらの変数を以下にリストアップします.
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
の実装 を試みます.
実行時の動作が異なるので,eval
でLINENO
に依存すべきでは ありません.また,Sedに前もって渡す手法を用いる可能性は,引用符で囲んで いるとき,ヒアドキュメントのとき,または行を跨るほど長いコマンドのとき, $LINENO
に依存すべきではないことを意味しています.ただし,サブシェ ルは問題ありません.以下の例では,一行目,六行目,そして九行目は移植性が ありますが,それ以外のLINENO
のインスタンスは移植性がありません.
$ cat lineno echo 1. $LINENO cat < |
NULLCMD
zsh
は`$NULLCMD >foo'を実行します.BourneシェルはNULLCMD
が`:'だと考えますが, zsh
はBourneシェル互換モードでも,NULLCMD
を`cat'に 設定します.NULLCMD
の設定を忘れた場合,スクリプトは標準入力からの データ待ちのためサスペンド状態になるかもしれません.
ENV
MAIL
MAILPATH
PS1
PS2
PS4
ksh
)はそれが対話的かどうかを混同し,つまり (例えば)PS1
の副作用として,`$?'を予期せず変更するはずです.こ のバグを回避するために,Autoconfが生成したスクリプトは以下のようなことを します.
(unset ENV) >/dev/null 2>&1 && unset ENV MAIL MAILPATH PS1='$ ' PS2='> ' PS4='+ ' |
PWD
cd
とpwd
が現在のディレ クトリの論理的なパスを示す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
を使用して監視すべきです.[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [表紙] | [目次] | [索引] | [検索] [上端 / 下端] [?] |
だめだよ全く,我々は本気なのに.制限のあるシェルもあるんです! :)
全ての組み込みコマンドやコマンドは,オプションをサポートし,そのため,ダッ シュで始まる引数を用いると,全く異なる動作をすることを覚えておくべきです. 例えば罪の無い`echo "$word"'でも,word
がダッシュで始まるとき は予期しない結果となるはずです.この問題は,パイプでは`x'を後で評価 するように,`echo "x$word"'を使用することで避けることが可能です.
.
.
コマンドを使用してください.例えば,Bash 2.03は, `. /dev/null'で固まります.また,引数にスラッシュを含まない場合は .
はPATH
を使用するので,現在のディレクトリのファイル `foo'で.
を使用したい場合,`. ./foo'を使用する必要が あることを覚えておいてください.
!
!
を使用することは不可能です.コードを書き換える必要があります.
break
cd
cd
が`-L' ("論理的") と`-P' ("物理的")オプションをサポートし,`-L'がデフォル トであることを要求しています.しかし,伝統的なシェルはこれらのオプション をサポートしておらず,cd
コマンドは`-P'のように動作しま す.
移植性の高いスクリプトは,どちらのオプションもサポートしていると仮定すべ きではなく,どちらの動作もデフォルトと仮定すべきではありません.これは ちょっとトリッキーで,例えば,POSIXのデフォルトの動作では,現 在の論理的なディレクトリがシンボリックリンクの場合,`ls ..'と `cd ..'では異なるディレクトリを参照している可能性があります. dirに`..'の要素が無い場合,cd dir
を使用しても 安全です.また,Autoconfが生成するスクリプトは,ac_top_srcdir
の ような変数を計算するとき,この問題を調査するので(see 節 4.5 コンフィグレーション作業の実行),これらの変数でcd
しても安全です.
pwd
コマンドの議論も参照してください.
case
最後の`;;'は不要ですが,使用した方が良いでしょう.
fnmatch
のバグのため,bash
はバックススラッシュを文字クラ スとして正しく処理することに失敗します.
bash-2.02$ case /tmp in [/\\]*) echo OK;; esac bash-2.02$ |
このコードをUNIXやMS-DOSの絶対パスとして使用したいとき,非常に 残念なことになります.このバグを回避するために,常にバックスラッシュを最 初に書いてください.
bash-2.02$ case '\TMP' in [\\/]*) echo OK;; esac OK bash-2.02$ case /tmp in [\\/]*) echo OK;; esac OK |
Ash 0.3.8のように,シェルによっては空のcase
/esac
で混乱する ものもあります.
ash-0.3.8 $ case foo in esac; error-->Syntax error: ";" unexpected (expecting ")") |
多くのシェルでは,カッコで囲まれているケース文をサポートしておらず,それ は,対になっているカッコに依存しているツールを使用している我々のような人 間にとっては残念なことです.例えば,Solaris 8のBourneシェルがそうです.
$ case foo in (foo) echo foo;; esac error-->syntax error: `(' unexpected |
echo
echo
ですが,移植性の問題の根源として最も驚くべきものかもし れません.移植性の高い`echo'を使用することは,オプションとエスケー プシーケンスを削除しない限り不可能です.移植性を目標とする新しいアプリケー ションでは,`echo'の代わりに`printf'を使用すべきです.
オプションを期待しないでください.ECHO_N
などの,`-c'をシミュ レーションする方法は,See 節 4.7.1 出力変数のプリセット.
引数へのバックスラッシュは,処理について同意がとれていないので使用しない でください.`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 < |
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+"$@"}' で単語の分離を試みるのことを覚えておいてください.`$@'の詳細は, 10.5 シェルの代入を参照してください.
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
を使用するのが最善でしょう.最近のホストでは, これで論理的なディレクトリ名を出力し,以下の利点があります.
pwd
をそのまま使うと,この理由で失敗するはずは ありません.cd
コマンドでの議論も参照してください.
set
shift
を使用してください.
set x $my_list; shift |
すべてのオプションを認識しないこととは"反対"の問題(例えば,`set -e -x'で`-x'をコマンドラインに割り当てるといった問題)があるシェルもあ ります.以下のように省略した方が良いでしょう.
set -ex |
shift
shift
するものが無いとき,shift
を使用することは悪い考 え方であるだけでなく,移植性が無くなってしまいことも追加されてしまいます. MIPS RISC/OS 4.52のシェルは,それを廃棄してしまいます.
source
.
を使用してください.
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
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),移植性の高いシェルのコミュニティは,:
の使用を好みが ちです.これには副作用があります.false
がtrue
より移 植性が高いかどうか尋ねてみたときの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 節 10.8 特殊なシェル変数. また,環境変数の caseについてはexport
のドキュメント10.9 シェル組み込みの制限も参照してください.
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [表紙] | [目次] | [索引] | [検索] [上端 / 下端] [?] |
あらゆるマシンで見つかることが期待できる小さなツールセットには,知ってお くべき制限がいくつか含まれているはずです.
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 foo bar $ nawk -f for.awk 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
cc
HP-UX cc
は,プリプロセスとアセンブラを行なう`.S'ファイル を受け入れません.`cc -c foo.S'は成功したように見えますが,実際には 何もしません.
`cc foo.c'で生成されるデフォルトの実行形式は,以下のようになるはず です.
gcc
を含む).gcc
が移植されたDJGPP.cc
ラッパー.cmp
cmp
は,二つのファイルの生のデータの比較を実行しますが, diff
は二つのテキストファイルを比較します.そのため, DOSのファイルを比較する場合,二つのファイルが異なっているかど うかを調査するだけの場合でも改行のエンコードの違いで見せかけの差が発生す ることを避けるため,diff
を使用してください.
cp
cp
が使用するシステムコー ルの分解能に依存します.伝統的に,これはutime
で,それは分解能が1 秒になっていますが,新しいcp
の実装ではutimes
を使用して いて,それは分解能が1マイクロ秒になっています.これらの新しい実装には, GNU coreutils 5.0.91やそれ以降,そしてSolaris 8 (sparc)のパッチ109933-02 やそれ以降が含まれます.残念ながら,2003年9月の段階では,完全なナノ秒の 分解能を持つタイムスタンプを設定するシステムコールはありません.
SunOS cp
は`-f'をサポートしていませんが,その mv
はサポートしています.mv
とcp
が `-f'に関して異なっている理由については由来が推測できます. mv
はデフォルトで,読み込み専用のファイルを上書きする前にプロン プトを表示します.cp
はそうではありません.そのため, mv
には`-f'オプションが必要ですが,cp
には不要 です.mv
とcp
は,読み込み専用のファイルに対して,動作 が異なり,その理由は,最も簡単な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
Tru64のように,実装によっては`/dev/null'の比較で失敗するものもあり ます.その代わりに空のファイルを使用してください.
dirname
dirname
があるわけではなく,その代わりに AS_DIRNAME
を使用すべきです(see 節 8.4 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
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 |
$EGREP
もgrep
の制限で苦しむことになります.
expr
expr
キーワードはないので,expr
が wordを間違って解釈しないように,`expr x"word" : 'xregex''を使用してください.
length
,substr
,match
,そしてindex
は使用し ないでください.
expr
(`|')
expr '' \| '' |
GNU/LinuxとPOSIX.2-1992では,この場合は空の文字列を 返しますが,伝統的なUNIXでは`0'を返します(Solarisはそのような 例の一つです).最近のPOSIX.1-2001ドラフトでは,その指定は伝統 的なUNIXの動作に一致するよう変更されています(信じられないことですが, これを修正するには時すでに遅しです).同じ問題が,計算結果が空の文字列に なるときにも,以下の状態では発生します.
expr bar : foo \| foo : bar |
空の文字列を避けることで,この移植性の問題を避けてください.
expr
(`:')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
でのテストを構 成し,結果によってexpr
やfalse
で変数を設定する方法を 使用します.
fgrep
fgrep
を要求していません が,より古いホストの多くはまだPOSIXのgrep -F
での置換を サポートしていません.この問題を回避するため,AC_PROG_FGREP
を呼 び出し,$FGREP
を使用してください.
find
find
コマンドはそれを 理解しません.
`{}'の置換は,引数が正確に{}の場合のみ保証され,それが引 数の一部の場合は保証されません.例えば,DUとHP-UX 10.20とHP-UX 11では保 証されません.
$ touch foo $ find . -name foo -exec echo "{}-{}" \; {}-{} |
一方,GNU find
は`./foo-./foo'を報告します.
grep
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
ln
に依存しないようにしてください. 古いシステムではシンボリックリンクは利用不可能です.移植性のある代替物 `$(LN_S)'を使用してください.
2.04以前のバージョンのDJGPPに対して,ln
は実行形式へのソフトリ ンクを,実際のプログラムを呼び出すスタブを生成することでエミュレートしま す.この機能は,Unix独自の実行形式以外のファイルでも動作します.そのため, `ln -s file link'は`link.exe'を生成し,それは実行された場合に `file.exe'の呼び出しを試みます.しかしこの機能は実行形式に対しての み動作するので,このシステムでは`cp -p'が代わりに使用されます. DJGPPの2.04とそれ以降では完全なシンボリックリンクがサポートされています.
ls
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 節 8.4 M4shでのプログラミング).
mv
ファイルシステム間で個別にファイルを移動することは(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
は終了ス テータスを変更しません.
マウントポイントをまたがってディレクトリを移動することは移植性が無いので, cp
とrm
を使用してください.
開いているファイルの移動/削除は移植性がありません.以下の例はDOS/WIN32 では実行不可能です.
exec > foo mv foo bar |
以下も実行不可能です.
exec > foo rm -f foo |
sed
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')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
touch
は通常,utime
やutimes
システムコールを使用し, 結果として,`cp -p'に存在するタイムスタンプの切り詰めの問題と同様の 結果になるはずです.
古いBSDシステムには,空のファイルに対するtouch
のよう なコマンドで,タイムスタンプを更新しない結果となるものもあるので,回避す るために,echo
のようなコマンドを使用してください.
GNU touch
3.16r(とそれ以前の全て)は,空のファイルが NFSでマウントされている4.2のボリュームのとき,SunOS 4.1.3 での 動作で異常終了します.
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [表紙] | [目次] | [索引] | [検索] [上端 / 下端] [?] |
make
自身には非常に多くの制限があるので苦労します,ここではわず かですが紹介します.とにかく,シェルによってコマンドが実行されるので,そ の弱い部分の全てが継承されていくということを覚えておいてください ....
$<
make
はそれを 引数で置換します.
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 |
make
には,バックスラッシュ以降の 複数の改行を,空ではない行も含めて読み込むものもあります.例えば以下のよ うにします.
FOO = one \ BAR = two test: : FOO is "$(FOO)" : BAR is "$(BAR)" |
FOO
はone 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 |
サブ呼び出しのmake
にfoo=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 -e
とSHELL
の注意も参照してください.)
サブ呼び出しの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 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 |
コメントをルールに書き込まないでください.
タブで始まるものは,タブの直後に#
が続いていても,すべて現在のルー ルのコマンドとして扱うmake
もあります.Tru64 Unix V5.1の make
はその一つです.以下の`Makefile'で,シェルで# foo
を実行します.
all: # foo |
びっくりしたくなければ,サブディレクトリを`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
サポートを指定していません.多くの make
はVPATH
サポートの形式がありますが,その実装は, 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 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.h
と foo2.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も同様なことを行ないます.
make
のmake
は,不思議なディレクトリの必要条件を作成する必要条件がVPATH
のサブディレクトリにある場合,Tru64 make
はそれを現在のディレクトリに作成します.
% mkdir -p foo/bar build % cd build % cat >Makefile < |
ルールは,前に存在している手動のVPATH
検索を使用するので,これは予 想外の結果になるはずです.
VPATH = .. all : foo/bar command `test -d foo/bar || echo ../`foo/bar |
上記のcommand
は,現在のディレクトリに作成された,空の `foo/bar'ディレクトリで実行されます.
GNU make
は,VPATH
で見つかったファイルを使用す べきとき決定するアルゴリズムは幾分複雑です.See 節 `How Directory Searches are Performed' in
ターゲットのリビルドが必要な場合,GNU make
は,このター ゲットをVPATH
で検索している間に見つかったファイル名を廃棄し, `Makefile'で与えられたファイル名を使用して,ローカルなファイルをビ ルドします.ターゲットをリビルドする必要が無い場合は,GNU make
はVPATH
で検索している間に見つかったファイル名を使用 します.
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 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 # 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
はビルドされていないソースへの予約にすべきです.
分離された依存性(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 |
結果として,そのような状況では,ターゲットルールを書く必要があります.
make
はファイルがそれ以外のものより新しいかどうかを決定するため これらのタイムスタンプを使用していました.しかし,最近のファイルシステム には分解能が1ナノ秒のタイムスタンプになっているものもたくさんあります. make
の実装には,タイムスタンプ全体を見るものもあります.それ以 外では,分数部分を無視し,結果として間違ったものとなるはずです.通常,こ れは問題ありませんが,非常に稀な状況では,タイムスタンプの切り詰めのバグ を回避するため,`sleep 1'のような手法を使用する必要があるかもしれま せん.
`cp -p'と `touch -r'のようなコマンドでは,通常はファイルのタイ ムスタンプを完全な分解能でコピーしません(see 節 10.10 通常のツールの制限).このため,以下のようなルールは気を付けるべきです.
dest: src cp -p src dest |
それは,タイムスタンプの切り詰め後では,`src'よりも`dest'が古 くなり,このためmake
は次回も不必要な仕事を再び行なうはずです. この問題を回避するため,タイムスタンプファイルを使用することが可能です. 例えば以下のようにします.
dest-stamp: src cp -p src dest date >dest-stamp |
[ << ] | [ >> ] | [表紙] | [目次] | [索引] | [検索] [上端 / 下端] [?] |