[ << ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
独自の調査を書いているとき,コードを移植性の高いものにするため,使用を避 けるべきシェルスクリプトプログラムのテクニックもあります.Bourneシェルと, BashとKornシェルのような上位互換性があるシェルは,何年もかけて進展しまし たが,問題を避けるために,UNIXバージョン7の以降の1977年頃に加えられ た機能を利用しないでください(see section システム).
シェル関数,エイリアス,無効な文字クラスや,Bourneシェル互換のものでは見
つからないすべての機能を使用するべきではありません.最小公倍数に制限され
てます.unset
さえ,全てのシェルではサポートしていません!また,以
下のように,インタプリタ仕様として,感嘆符の後にスペースを含めてください.
#! /usr/bin/perl |
パスの前のスペースを省略する場合,(DYNIXのような)4.2BSDを基本 とするシステムは,`#! /'は4バイトのマジックナンバーとして解釈される ので,その行を無視します.古いシステムでは,`#!'行の長さにも小さな 制限があり,例えばSunOS 4では,(改行を含めず)32バイトになります.
configure
スクリプトで実行すべき外部プログラムの設定は,かなり
小さくなっています.リストは,See (standards)Utilities in Makefiles section `Utilities in Makefiles' in GNU Coding Standards. この制限で,
ユーザは,かなり小さいプログラム設定から残りをビルドすることが可能になっ
ていて,パッケージ間の独立部分を多くし過ぎることを避けることができます.
これらの外部ユーティリティには,移植性の高い機能のサブセットがあります. 通常のツールの制限を参照してください.
シェルに関するドキュメントのソースは他にもあります.例えば, the Shell FAQsを参照し てください.
10.1 シェル | A zoology of shells | |
10.2 ヒアドキュメント | Quirks and tricks | |
10.3 ファイルディスクリプタ | FDs and redirections | |
10.4 ファイルシステムの条件 | File- and pathnames | |
10.5 シェルの代入 | Variable and command expansions | |
10.6 代入 | Varying side effects of assignments | |
10.7 シェルスクリプト内のカッコ | Parentheses in shell scripts | |
10.8 特殊なシェル変数 | Variables you should not change | |
10.9 シェル組み込みの制限 | Portable use of not so portable /bin/sh | |
10.10 通常のツールの制限 | Portable use of portable tools | |
10.11 Makeの制限 | Portable Makefiles |
いくつかのシェルのファミリーがあり,最も重要なものは,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`} |
単一の組み込みの代入は,その効果が現在のシェルに適用されるため,サブシェ ルで実行されないことに注意してください.アイテム"コマンドの代入"につい ては,See section シェルの代入.
bash
を実行しているかどうかを検出するために,
BASH_VERSION
が設定されているかどうかをテストしてください.その拡
張を利用不可能にし,POSIX互換性を要求するため,`set -o
posix' を実行してください.詳細は,See (bash)Bash POSIX Mode section `Bash POSIX Mode' in The GNU Bash Reference Manual.
バージョン2.05とそれ以降のbash
は,set
組み込みコマン
ドの出力に対して,その出力をより容易に評価できるように設計されているので,
異なる書式を使用しています.しかし,この出力はそれ以前のバージョンの
bash
(や,おそらくそれ以外の多くのシェル)と互換性がありません.
そのため,bash
2.05やそれ以上のものをconfigure
の実行
に使用している場合,それ以外のすべてのビルドの作業に対しても,同じように
bash
2.05を使用する必要があるでしょう.
KornシェルはBourneファミリーと互換性があり,POSIXにほとんど準
拠しています.一般的に`ksh88'と`ksh93'という二つの有名な変種が
あり,最初のリリースの年で後から命名されました.それらは通常
ksh
と呼ばれていますが,Solarisシステムには三つの変種があります.
/usr/bin/ksh
は`ksh88'です.
/usr/xpg4/bin/sh
はPOSIX準拠の`ksh88'の類似物で
す.
/usr/dt/bin/dtksh
は`ksh93'です./usr/bin/ksh
が
Soralisの標準です.それ以外の類似物はオプションパッケージの一部です.こ
れらのパッケージには追加の変更はありませんが.それらは最小限のOSのインス
トールの一部ではないので,インストール状況によっては無いかもしれません.
パブリックドメインの`pdksh'と呼ばれているKornシェルのクローンも広く
利用可能になっています.それは,いくつか独自のものがありますが
`ksh88'の機能のほとんどがあります.
zsh
が実行されているかどうかを検出するために,
ZSH_VERSION
が設定されているかどうかをテストしてください.デフォル
トで,zsh
はBourneと互換性はありません.`emulate
sh'を実行し,NULLCMD
を`:'に設定する必要があります.詳細は,
See (zsh)Compatibility section `Compatibility' in The Z Shell Manual.
Zsh 3.0.8は,Mac OS X 10.0.3でのネイティブな/bin/sh
です.
Russ AllberyとRobert Lipeの間でなされた,以下の議論は読む価値があります.
Russ Allbery:
/bin/sh
が唯一のシェルであるというGNU仮定では,永久に 行き詰まってしまいます.ベンダーは,ユーザの既存のシェルスクリプトを壊し たくはありませんし,BourneシェルにはPOSIXシェルと完全に互換で はない部分もあります.このため,この方法を採用するベンダーは,決し て (OK…"決して,決してとは言わないよ")Bourneシェルを (/bin/sh
として)POSIXシェルで置き換えないでしょう.
Robert Lipe:
これは本当に問題です.ほとんどのもの(少なくともほとんどのSystem V)はシェ ル関数を受け入れるBourneシェルがあるのですが,ほとんどのベンダーの
/bin/sh
はPOSIXシェルではありません.そのため,ほとんど現在のシステムはPOSIX標準に適合しているシェ ルがどこかにあるのですが,問題はそれを見つけることです.
`\'は,次のシンボルと一緒になって特別の意味を持たないので,維持され
る`\'に依存しないでください.OpenBSD 2.7のネイティブな
/bin/sh
では,`\"'は`"'に展開され,ヒアドキュメントで
は引用符で囲まれていない分離子として用いられます.一般的な規則として,
`\\'が`\'に展開される場合,`\'を得るために`\\' を使
用してください.
OpenBSD 2.7の/bin/sh
では,以下のようになります.
$ cat <<EOF > \" \\ > EOF " \ |
そして,Bashでは以下のようになります.
bash-2.04$ cat <<EOF > \" \\ > EOF \" \ |
多くの古い(Bourneシェルを含む)シェルでは,ヒアドキュメントは非効率に実装
されています.大きなヒアドキュメントを間違って扱うシェルもあります.例え
ば,Solaris 8 dtksh
は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 |
様々なレベルの詳細が分かるでしょう....
一つの回避方法は,興味がない行をgrepで削除することで,良い行は削除しない ことを期待しつつ....
`exec >foo; mv foo bar'のように,開いているファイルの移動/削除の試
みはしないようにしてください.mv
の詳細は,シェル組み込みの制限を参照してください.
autoconf
とその仲間達は,通常様々なUnixで実行されますが,それは
その他のシステムでも使用され,最も顕著なものとしてはDOSの仲間
があげられます.このことは,ファイルとパス名に関する仮定に衝突します.
例えば,以下のようなコードを考えます.
case $foo_dir in /*) # Absolute ;; *) foo_dir=$dots$foo_dir ;; esac |
それらのシステムではドライブスペックを使用していて,通常はディレクトリの 分離子としてバックスラッシュを使用しているため,絶対パスを正しく検出する ことに失敗するでしょう.絶対パスに対する調査の標準的な方法は以下のとおり です.
case $foo_dir in [\\/]* | ?:[\\/]* ) # Absolute ;; *) foo_dir=$dots$foo_dir ;; esac |
適切な場合は角カッコの引用符で囲み,最初の文字としてのバックスラッシュを 保持していることを確認してください(see section シェル組み込みの制限).
また,コロンがデバイス指定の一部として使用されているので,これらのシステ
ムではそれをパスの分離子として使用していません.パスを作成しているときや
パスにアクセスしているときは,代わりにPATH_SEPARATOR
出力変数を使
用してください.configure
は,開始時にこれを適切な値(`:'
または`;')に設定します.
ファイル名にも余計な注意が必要になります.(DJGPPのような)
autoconf
を十分に実行できるUnixのようなDOSベースの環
境では,通常長いファイル名を適切に扱うことが可能ですが,パッケージを壊し
てしまう深刻な制限も残っています.これらの問題のいくつかは,
doschk パッ
ケージで容易に検出することが可能です.
以下は簡単な全体像です.問題には,適用を示すためSFN/LFNで印が ついています.SFNは,Windows下のDOS窓ではなく,プレーンな DOSにのみ関連する問題を意味し,一方LFNは,Windowsでも存在 する問題を意味しています.
DOSはファイル名に複数のドットがあるものを扱うことが不可能です.
これは,autoconf
は`.in'をテンプレートファイルの接尾子に使
用するので,移植性の高いconfigureスクリプトを構築しているときに覚えてお
く必要がある,特に重要なことです.
以下はUnix上では完全にOKです.
AC_CONFIG_HEADERS([config.h]) AC_CONFIG_FILES([source.c foo.bar]) AC_OUTPUT |
しかし,それは`config.h.in',`source.c.in',そして `foo.bar.in'が必要になるので,DOSでは問題があります.パッ ケージをDOSベースの環境でより移植性を高くするため,その代わり に以下を使用すべきです.
AC_CONFIG_HEADERS([config.h:config.hin]) AC_CONFIG_FILES([source.c:source.cin foo.bar:foobar.in]) AC_OUTPUT |
DOSはドットで始まるファイル名を扱うことが不可能です.これは通
常,autoconf
に対してはあまり重要ではない問題です.
DOSは大文字小文字を区別しないので,例えば,`INSTALL'とい
う名のファイルと`install'という名のディレクトリの両方を持つことがで
きません.これは,make
にも影響します.ディレクトリに
`INSTALL'という名のファイルがある場合,`make install'は
(`install'がPHONY として印がついていないとき)何もしません.
DOSファイルシステムでは,ファイル名の最初の8文字と最初の3文字 の拡張子のみ保存され,それらはユニークである必要があります.それは, `foobar-part1.c',`foobar-part2.c',そして `foobar-prettybird.c'の全ては同じ名前(`FOOBAR-P.C')になります. `foo.bar'と`foo.bartender'も同じものになります.
注意:これは通常,ファイル名をユニークにするために短いバージョンでは数字 の後置を使用するので,Windowsでは問題になりません.しかし,レジストリの 設定でこの動作を停止可能です.これで長いファイル名を含むファイルのツリー を,SFNとLFNの環境で共有することが可能になりますが,上記の問題 は同様に存在します.
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!).
$@
最も有名なシェルの移植性の問題の一つは,`"$@"'との関連です.位置に 依存する引数が無いとき,POSIXでは`"$@"'を何もないことと 等価になっていますが,オリジナルのUnixバージョン7のBourneシェルはその代 わりに`""'として扱い,この動作はDigital Unix 5.0のようにそれ以降の 実装でも提供されています.
この移植性の問題を回避する伝統的な方法は,`${1+"$@"}'を使用する ことです.残念ながら,この手法はMac OS Xでも使用されている,Zsh (3.x と 4.x)では動作しません.Bourneシェルをエミュレートしているとき,Zshは `${1+"$@"}'で単語の分離を実行します.
zsh $ emulate sh zsh $ for i in "$@"; do echo $i; done Hello World ! zsh $ for i in ${1+"$@"}; do echo $i; done Hello World ! |
Zshは,プレーンの`"$@"'をおそらく処理しますが,上記の移植性の問題 のため,プレーンの`"$@"'を使用することはできません.回避する方法の 一つは,`${1+"$@"}'を`"$@"'に変換するZshの"global aliases"に依存します.
test "${ZSH_VERSION+set}" = set && alias -g '${1+"$@"}'='"$@"' |
より保守的な回避方法は,位置に依存する引数を用いなくても良い限り, `"$@"'を避けることです.例えば,以下の代わりを考えます.
cat conftest.c "$@" |
この代わりに以下を使用することが可能です.
case $# in 0) cat conftest.c;; *) cat conftest.c "$@";; esac |
${var:-value}
Ultrix sh
を含め,古いBSDシェルはシェルの代入に対してコ
ロンを受け入れず,文句を言って終了します.
${var=literal}
引用符で囲まれていることを確かめてください.
: ${var='Some words'} |
それ以外のDigital Unix V 5.0のようなシェルでは,"bad substitution"のた めに終了します.
Solarisの/bin/sh
にはこの解釈に恐ろしいバグがあります.変数を
`}'を含む文字列に設定する必要があることを想像してください.この
`}'文字で,影響ある変数が既に設定されているとき,Solarisの
/bin/sh
は混乱します.このバグは,以下のように実行することで作
動されるはずです.
$ unset foo $ foo=${foo='}'} $ echo $foo } $ foo=${foo='}' # no error; this hints to what the bug is $ echo $foo } $ foo=${foo='}'} $ echo $foo }} ^ ugh! |
`}'は,シングル引用符で囲まれている場合でも,`${'に一致する ものとして解釈されているようです.二重引用符を使用すると問題は生じません.
${var=expanded-value}
Ultrixで,以下を実行したとします.
default="yu,yaa" : ${var="$default"} |
それはvarを`M-yM-uM-,M-yM-aM-a'に設定し,すなわち,全ての文字 の八番目のビットがセットされるでしょう.`$var'を展開するとき,シェ ルが八番目のビットを明示的にリセットするので,単純に`echo $var'を使 用している現象が分かりません.このシェルにその違反で混乱させる二つの方法 は,以下のようになります.
$ cat -v <<EOF $var EOF |
それと以下です.
$ set | grep '^var=' | cat -v |
このバグの古典的で典型的なものの一つは以下のものです.
default="a b c" : ${list="$default"} for c in $list; do echo $c done |
単一行に`a b c'を得るでしょう.なぜでしょうか?それは,`$list' にスペースが無いためです.`M- ',すなわち八ビット目を設定するスペー スがあるので,IFSによる分離が実行されないのです!!!
良いニュースの一つは,Ultrixが`: ${list=$default}'で正確に動作す ることです.すなわち,引用符で囲まない場合です.悪いニュースとし ては,QNX 4.25は,listをdefaultの最後の項目 に設定することです!
移植性の高い方法は,Ultrixで八番目のビットを二回切替えるために,二重(引 用符による)代入を使用することです.
list=${list="$default"} |
…しかし,Solarisの`}'のバグ(上記を参照してください)には用心 してください.安全にするには,以下を使用してください.
test "${var+set}" = set || var={value} |
`commands`
一般的には意味がありませんが,Ash 0.2では最適化のためコマンドを実行する ためサブシェルをforkしないので,副作用のある単一の組み込み物を代入しない でください.
例えば,cd
が何も出力しないことを調査したい場合,以下のことが生
じるかもしれないので,`test -z "`cd /`"'を使用しないでください.
$ pwd /tmp $ test -z "`cd /`" && pwd / |
`foo=`exit 1`'の結果は,読者への演習問題として残しておきます.
$(commands)
この構成は,``commands`'を置換するという意味があります.それ らを入れ子状にすることは可能ですが,バッククオートを用いて移植することは 不可能です.残念ながら,まだ全体的にサポートされていません.特に,現在の Solarisリリースでもサポートされていません.
$ showrev -c /bin/sh | grep version Command version: SunOS 5.8 Generic 109324-02 February 2001 $ echo $(echo blah) syntax error: `(' unexpected |
また,IRIX 6.5のBourneシェルもサポートされていません.
$ uname -a IRIX firebird-image 6.5 07151432 IP22 $ echo $(echo blah) $(echo blah) |
`$(commands)'を使用する場合,異なる表記方法 `$((expression))'は現在のシェルではコマンドではなく数式だと勘 違いするので,コマンドがカッコで始まらないように確かめて下さい.この勘違 いを避けるため,二つの開カッコの間にはスペースを挿入して下さい.
列にいくつかの変数を設定するとき,評価の順序が定義されていないことを覚え
ておいてください.例えば,`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'} |
デフォルト値が閉じカッコを含まず,展開されず,初期化されている変数がIFS で分けられていない(すなわち,リストでない)場合,以下を使用してください.
: ${var="$default"} |
デフォルト値が閉じカッコを含まず,展開されず,初期化されている変数がIFS で分けられる(すなわち,リストの)場合,以下を使用してください.
var=${var="$default"} |
デフォルト値が閉じカッコを含む場合,以下を使用してください.
test "${var+set}" = set || var='${indirection}' |
ほとんどの場合,`var=${var="$default"}'で良いのですが,駄目なとき は後者を使用してください.正当性のための, `${var:-value}'と`${var=value}' の 項目は,See section シェルの代入.
一列にある二つの開カッコは,シェルの実装によっては間違って処理されること を覚えておいて下さい.例えば,`pdksh' 5.2.14では以下のコードのパー スを失敗します.
if ((true) || false); then echo ok fi |
この問題を回避するため,二つの開カッコの間にスペースを挿入して下さい. `$(('に関する同様な問題と回避方法があります.シェルの代入を参照して下さい.
POSIXでは,以下のような開カッコを用いたcase
パターンのサ
ポートを要求しています.
case $filename in (*.c) echo "C source code";; esac |
しかし,この例の(
には古いBourneシェルの実装で移植性がないものもた
くさんあります.安全のため削除すべきでしょう.
シェルの動作に深く影響するため,使用すべきではないシェル変数もあります.
シェルからまともな動作に戻るため,unsetすべき変数もありますが,
unset
は移植性が無く(see section シェル組み込みの制限),代替値が
必要になります.これらの変数を以下にリストアップします.
CDPATH
この変数が設定されているとき,それはcd
が相対的なファイル名で呼び
出されるときに検索するディレクトリのリストを設定します.POSIX
1003.1-2001では,CDPATH
で空ではないディレクトリ名が正しく使用され
ている場合,cd
は絶対的なファイル名を結果として出力することになっ
ています.残念ながらこの出力では,abs
がパスを二回受けとるので,
`abs=`cd src && pwd`'のような慣用句が駄目になります.また,多くのシェ
ルは,この部分のPOSIXに準拠していません.例えば,zsh
は,`.'以外のディレクトリ名がCDPATH
で選択されている場合以外,
結果を出力しません.
実際,この問題があるシェルはunset
もサポートしているので,以下
のようにしてその問題を回避することが可能です.
(unset CDPATH) >/dev/null 2>&1 && unset CDPATH |
Autoconfが生成したスクリプトは自動的にCDPATH
をunsetするので,これ
らのスクリプトのこの問題を心配する必要はありません.
IFS
IFS
の最初の文字をバックスラッシュに設定しないでください.実際,
`"$@"'で要素を加えるときは,Bourneシェルは最初の文字(バックスラッ
シュ)を使用し,そして,バックスラッシュエスケープをもう一度解釈する(!)
シェルもあり,そのため,バックスペースとその他の奇妙な文字で終ることが可
能になっています.
(splitを実行していないとき,標準的なコードの)IFS
の適切な値は,
`SPCTABRET'です.`@*'の引数を連結するために
使用するので,最初の文字は特に重要です.
LANG
LC_ALL
LC_COLLATE
LC_CTYPE
LC_MESSAGES
LC_MONETARY
LC_NUMERIC
LC_TIME
あまりに多くのコンフィグレーションコードがCロケールを仮定していて, POSIXではCロケールが要求される場合はロケールの環境変数を `C'に設定する必要があるので,Autoconfが生成したスクリプトは通常,こ れらのすべての変数を`C'に設定します.しかし,非標準の古いシステム (特にSCO)では,ロケールの環境変数が`C'に設定されている場 合は壊れてしまうので,これらのシステムでAutoconfが生成したスクリプトを実 行するとき,代わりに変数を未設定(unset)にしてください.
LANGUAGE
LANGUAGE
はPOSIXで指定されていませんが,それは場合によっ
てはLC_ALL
に優先させるGNUの拡張なので,Autoconfが生成し
たスクリプトはそれも設定します.
LC_ADDRESS
LC_IDENTIFICATION
LC_MEASUREMENT
LC_NAME
LC_PAPER
LC_TELEPHONE
これらのロケール環境変数はGNUの拡張です.それらは,上記の
POSIXの仲間(LC_COLLATE
など)のように扱われます.
LINENO
ほとんどの近代的なシェルは,現在の行番号をLINENO
で提供しています.
その値は,現在のコマンドの最初の行番号です.Autoconfは近代的なシェルで
configure
の実行を試みます.利用可能なそのようなシェルが無い場
合,それぞれの文字列$LINENO
(英数文字が続かない)をインスタンスを行
番号で置換するために,Sedに前もって渡す手法を用いて,LINENO
の実装
を試みます.
実行時の動作が異なるので,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'を実行しているとき,zsh
は`$NULLCMD
>foo'を実行します.BourneシェルはNULLCMD
が`:'だと考えますが,
zsh
はBourneシェル互換モードでも,NULLCMD
を`cat'に
設定します.NULLCMD
の設定を忘れた場合,スクリプトは標準入力からの
データ待ちのためサスペンド状態になるかもしれません.
ENV
MAIL
MAILPATH
PS1
PS2
PS4
これらの変数は,対話的なシェルに対してのみ影響すると考えられるので,シェ
ルスクリプトに対して問題はありません.しかし,少なくとも一つのシェル
(pre-3.0 UWIN ksh
)はそれが対話的かどうかを混同し,つまり
(例えば)PS1
の副作用として,`$?'を予期せず変更するはずです.こ
のバグを回避するために,Autoconfが生成したスクリプトは以下のようなことを
します.
(unset ENV) >/dev/null 2>&1 && unset ENV MAIL MAILPATH PS1='$ ' PS2='> ' PS4='+ ' |
PWD
POSIX 1003.1-2001は,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
を使用して監視すべきです.
だめだよ全く,我々は本気なのに.制限のあるシェルもあるんです! :)
全ての組み込みコマンドやコマンドは,オプションをサポートし,そのため,ダッ
シュで始まる引数を用いると,全く異なる動作をすることを覚えておくべきです.
例えば罪の無い`echo "$word"'でも,word
がダッシュで始まるとき
は予期しない結果となるはずです.この問題は,パイプでは`x'を後で評価
するように,`echo "x$word"'を使用することで避けることが可能です.
.
通常のファイル(`test -f'を使用してください)を用いるときだけ
.
コマンドを使用してください.例えば,Bash 2.03は,
`. /dev/null'で固まります.また,引数にスラッシュを含まない場合は
.
はPATH
を使用するので,現在のディレクトリのファイル
`foo'で.
を使用したい場合,`. ./foo'を使用する必要が
あることを覚えておいてください.
!
!
を使用することは不可能です.コードを書き換える必要があります.
break
`break 2'の使用などは安全です.
cd
POSIX 1003.1-2001では,cd
が`-L' ("論理的")
と`-P' ("物理的")オプションをサポートし,`-L'がデフォル
トであることを要求しています.しかし,伝統的なシェルはこれらのオプション
をサポートしておらず,cd
コマンドは`-P'のように動作しま
す.
移植性の高いスクリプトは,どちらのオプションもサポートしていると仮定すべ
きではなく,どちらの動作もデフォルトと仮定すべきではありません.これは
ちょっとトリッキーで,例えば,POSIXのデフォルトの動作では,現
在の論理的なディレクトリがシンボリックリンクの場合,`ls ..'と
`cd ..'では異なるディレクトリを参照している可能性があります.
dirに`..'の要素が無い場合,cd dir
を使用しても
安全です.また,Autoconfが生成するスクリプトは,ac_top_srcdir
の
ような変数を計算するとき,この問題を調査するので(see section コンフィグレーション作業の実行),これらの変数でcd
しても安全です.
pwd
コマンドの議論も参照してください.
case
引数を引用符で囲む必要はありません.分離は実行されません.
最後の`;;'は不要ですが,使用した方が良いでしょう.
fnmatch
のバグのため,bash
はバックススラッシュを文字クラ
スとして正しく処理することに失敗します.
bash-2.02$ case /tmp in [/\\]*) echo OK;; esac bash-2.02$ |
このコードを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 section 出力変数のプリセット.
引数へのバックスラッシュは,処理について同意がとれていないので使用しない
でください.`echo '\n' | wc -l'を用いれば,Digital Unix 4.0と
MIPS RISC/OS 4.52のsh
では答えは2になりますが,
Solarisのsh
,Bash,そしてZsh(のsh
エミュレーションモー
ド)では答えは1になります.問題が本当にecho
にあることに注意して
ください.全てのシェルは,`'\n''をバックスラッシュと`n'の組み
合わせであると解釈します.
これらの問題のため,不定の文字を含む文字列をecho
に渡さないでく
ださい.例えば,fooの値がバックスラッシュを含んでおらず,`-'
で始まらないことを知っている場合,`echo "$foo"'は安全ですが,それ以
外では以下のようなヒアドキュメントを使用すべきではありません.
cat <<EOF $foo EOF |
exit
exit
のデフォルト値は$?
を想定しています.残念ながらBash
2.04を移植したDJGPPのように,シェルによっては`exit 0'を実行します.
bash-2.04$ foo=`exit 1` || echo fail fail bash-2.04$ foo=`(exit 1)` || echo fail fail bash-2.04$ foo=`(exit 1); exit` || echo fail bash-2.04$ |
`exit $?'を使用すると期待される動作に復帰します.
autoconf
が生成するようなシェルスクリプトなどには,以前の終了状
態をクリーンアップする仕掛けを使用しているものもあります.シェルの最後の
コマンドがゼロではないステータスで終了した場合も,呼び出し側がエラーの発
生を報告できるように,ゼロでないステータスで終了する仕掛けがあります.
残念ながら,Solaris 8 sh
のように,シェルによってはexit
コマンドの引数を無視する仕掛けが存在するものもあります.これらのシェルで
は,その仕掛けで呼び出しがプレーンのexit
によるものなのか,
exit 1
によるものなのか決定できません.exit
を直接呼び出す代
わりに,この問題を回避するためにAC_MSG_ERROR
を呼び出してください.
export
組み込みのexport
は,シェル変数を環境変数(environment
variable)に複製します.変数がエクスポートされて更新される度に,環境変数
も更新されます.反対に,環境変数はがシェルから読み出される度に,開始時に
エクスポートされたものとして印のついたシェル変数をインポートするべきです.
ああ,Solaris 2.5,IRIX 6.3,IRIX 5.2,AIX 4.1.5,そ
してDigital UNIX 4.0のような多くのシェルは,受けとった環境変数を
export
することを忘れています.結果として,二つの変数は共存して
います.環境変数とシェル変数の二つです.以下のコードは,この失敗を説明す
るものです.
#! /bin/sh echo $FOO FOO=bar echo $FOO exec /bin/sh $0 |
環境変数で`FOO=foo'として実行した場合,これらのシェルはそれぞれ `foo'と`bar'を交互に出力しますが,`foo'を出力した後に,続 けて`bar'を出力します.
このため,それぞれ更新した環境変数を再びexport
すべきです.
false
false
がステータス1で終了することを期待してはいけません.
Solaris 8のネイティブなBourneシェルは,ステータス255で終了します.
for
位置の引数までループするため,以下のように使用したとします.
for arg do echo "$arg" done |
シェルによっては,間違って解釈するので,for
と同じ行にdo
を
書いてはいけません.
for arg; do echo "$arg" done |
明示的に位置に依存する引数を参照したい場合,`$@'のバグがあるので, 以下のように使用してください.
for arg in ${1+"$@"}; do echo "$arg" done |
しかし,ZshはBourneシェルエミュレーションモードでも,`${1+"$@"}' で単語の分離を試みるのことを覚えておいてください.`$@'の詳細は, シェルの代入を参照してください.
if
`!'の使用は移植性がありません.以下の例を考えます.
if ! cmp -s file file.new; then mv file.new file fi |
その代わりに以下を使用してください.
if cmp -s file file.new; then :; else mv file.new file fi |
if
の終了ステータスをリセットしないシェルもあります.
$ if (exit 42); then true; fi; echo $? 42 |
そこでは,適切なシェルなら`0'を出力すべきです.これは,異常終了とな るので,`Makefile'では特に問題です.これが,Automakeが生成するよう な適切に書かれている`Makefile'がごちゃごちゃした構成になっている理 由です.
if test -f "$file"; then install "$file" "$dest" else : fi |
printf
`-'で始まる書式化文字列は問題になります.bash
(例えば
2.05b)では,それをオプション文字と解釈しエラーとなるでしょう.オプション
の終りの印となる`--'は,書式化文字列をそのまま受けとるNetBSD
Almquist shell (例えば0.4.6)ではよいとは言えません.`-'を`%c'
や`%s'に書くのが,間違いを避ける最も簡単な方法でしょう.
printf %s -foo |
pwd
最近のシェルを用いると,pwd
は"論理的な"ディレクトリ名を出力
し,その構成要素にはシンボリックリンクがある可能性があります.これらのディ
レクトリ名は,構成要素はすべてディレクトリとなっている"物理的な" ディ
レクトリ名とは異なります.
POSIX 1003.1-2001では,pwd
は,`-L' ("論理的")
と`-P' ("物理的")オプションをサポートし,`-L'がデフォル
トになっている必要があります.しかし,伝統的なシェルはこれらのオプション
をサポートしておらず,pwd
コマンドは`-P'のように動作しま
す.
移植性の高いスクリプトでは,どちらのオプションもサポートしていると仮定す べきではなく,どちらの動作もデフォルトと仮定すべきではありません.また, 多くのホストは`/bin/pwd'が`pwd -P'と同じですが, POSIX はこの動作を要求しておらず,移植性の高いシェルではそれに 依存すべきではありません.
通常,そのままpwd
を使用するのが最善でしょう.最近のホストでは,
これで論理的なディレクトリ名を出力し,以下の利点があります.
論理的な名前はユーザが指定するものです.
物理的な名前は,インストールしたホストと,それ以外のネットワークファイル システムとで移植性が無いかもしれません.
最近のホストでは,親ディレクトリに許可が無いと`pwd -P'は失敗するか
もしれませんが,pwd
をそのまま使うと,この理由で失敗するはずは
ありません.
cd
コマンドでの議論も参照してください.
set
この組み込みコマンドは,一般的なダッシュで始まる引数の問題に直面します.
BashやZshのような現在のシェルでは,オプションの終りを指定する
`--'(`--'以降の全ての引数は,例えば`-x'であってもパラ
メータです)を理解しますが,ほとんどのシェルは,オプションではない引数が
見つかるとすぐにオプションの処理を単純に停止します.このため,オプション
の処理を終了するために`dummy'や単純に`x'を使用し,それを取り出
すためにshift
を使用してください.
set x $my_list; shift |
すべてのオプションを認識しないこととは"反対"の問題(例えば,`set -e -x'で`-x'をコマンドラインに割り当てるといった問題)があるシェルもあ ります.以下のように省略した方が良いでしょう.
set -ex |
shift
shift
するものが無いとき,shift
を使用することは悪い考
え方であるだけでなく,移植性が無くなってしまいことも追加されてしまいます.
MIPS RISC/OS 4.52のシェルは,それを廃棄してしまいます.
source
POSIXが要求していないので,このコマンドは移植性がありません.
代わりに.
を使用してください.
test
test
プログラムは,多くのファイルと文字列のテストを実行する方法で
す.それは別名の`['で呼び出されることも多いのですが,M4の引用符文字
という問題から,Autoconfのコードではその名前を使用することが要求されてい
ます.
test
を使用して複数の調査を行う必要がある場合,test
の演算子
の`-a'と`-o'の代わりに,シェル演算子の`&&'と`||' で
組み合わせてください.System Vでは,`-a'と`-o'の優先順位は,単
項演算子とは間違った関係になっています.従って,POSIXはそれら
を指定しないので,それを使用すると移植性が無くなります.同じ文で
`&&'と`||'を組み合わせる場合,同じ優先順位があることを覚えてお
いてください.
test
で`!'を使用してもかまいませんが,if
ではでき
ません.`test ! -r foo || exit 1'.
test
(files)configure
スクリプトでクロスコンパイルのサポートを可能にするた
め,ホストシステムの代わりに,ビルドシステムの特徴のテストは,何もすべき
ではありません.しかし,任意のファイルの存在を調査する必要があることが判
明するかもしれません.そうするために`test -f'や`test -r'を使用
してください.4.3BSDには`test -x'が無いので使用しないでく
ださい.また,Solaris 2.5には`test -e'が無いので使用しないでくださ
い.システム上にシンボリックリンクがあることをテストするためには,
`test -L'ではなく`test -h'を使用して下さい.いずれも
POSIX 1003.1-2001に準拠した様式ですが,Solaris 8の
/bin/sh
のような古いシェルでは,`-h'だけをサポートしていま
す.
test
(strings)test
は引数をオプションとして解釈するので(例えば,
`string = "-n"'),特にstringがダッシュで始まる場合,
`test "string"'を避けてください.
一般に信じられていることとは反対に,`test -n string'と `test -z string'は,移植性があります.それにもかかわ らず,(Solaris 2.5,AIX 3.2,UNICOS 10.0.0.6,Digital UNIX 4等の)多くのシェルには信じられない優先順位があり,string がオペレータのように見える場合は混乱するかもしれません.
$ test -n = test: argument expected |
危険はありますが,代わりに`test "xstring" = x'や`test "xstring" != x'を使用してください.
以下のような慣用句はのバリエーションは普通に見つかります.
test -n "`echo $ac_feature | sed 's/[-a-zA-Z0-9_]//g'`" && action |
与えられているパターンに一致するとき動作します.そのような構文は,常に使 用を避けるべきです.
echo "$ac_feature" | grep '[^-a-zA-Z0-9_]' >/dev/null 2>&1 && action |
シェルの組み込みコマンドなのでより速くなっているため,可能な場所では
case
を使用してください.
case $ac_feature in *[!-a-zA-Z0-9_]*) action;; esac |
ああ,POSIXの構文`[!…]'をサポートしていないシェルは
知りませんが,文字クラスの否定は移植性が無いかもしれません(対話的モード
では,zsh
は`[!…]'の構文で混乱し,`!'のため,ヒ
ストリ内のイベントを探します).多くのシェルは,構文`[^…]' の
代替物をサポートしていません(Solaris,Digital Unix等).
以下は解決方法の一つです.
expr "$ac_feature" : '.*[^-a-zA-Z0-9_]' >/dev/null && action |
以下の方が良いかもしれません.
expr "x$ac_feature" : '.*[^-a-zA-Z0-9_]' >/dev/null && action |
`foo'がバックスラッシュを含んでいるとき問題を回避するので, `expr "Xfoo" : "Xbar"'は,`echo "Xfoo" | grep "^Xbar"'より堅牢です.
trap
少なくとも,シグナルの1,2,13,そして15をトラップすることは安全です.ま
た,0のトラップも可能で,すなわち,スクリプトが終るとき(明示的な
exit
やスクリプトの終り)にtrap
を実行するということで
す.
POSIXでは,この点は絶対的に明確ではありませんが,`$?'をト
ラップするとき,トラップの前の最後に実行されたコマンドの終了ステータスが
設定されるべきだということは,広く認められています.曖昧な部分は以下のよ
うに要約可能です."トラップがexit
で開始されるとき,実行された
最後のコマンドは何でしょう?exit
の直前ですか?それとも
exit
自身ですか?"
Bashはexit
を最後のコマンドと考えますが,ZshとSolaris 8
sh
は,トラップが実行されたときexit
の処理中 な
ので,トラップを受信する前の終了ステータスだと考えられます.
$ cat trap.sh trap 'echo $?' 0 (exit 42); exit 0 $ zsh trap.sh 42 $ bash trap.sh 0 |
移植性の解決方法は簡単です.`exit 42'にしたいとき,`(exit 42);
exit 42'を実行し,最初のexit
はZshに対する42の終了ステータスを
設定するために使用され,二番目は,トラップを誘発し,Bashに対して終了ステー
タスとしての42を渡すためです.
FreeBSD 4.0のシェルには,以下のバグがあります.コードが内部
trap
の場合,空行で`$?'が0にリセットされます.
$ trap 'false echo $?' 0 $ exit 0 |
幸運にもこのバグはtrap
のみに影響します.
true
心配しないでください.我々が知っている限りtrue
には移植性があり
ます.それにもかかわらず,常に組み込みコマンドというわけではなく(例えば
Bash 1.x),移植性の高いシェルのコミュニティは,:
の使用を好みが
ちです.これには副作用があります.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 section 特殊なシェル変数. また,環境変数の
caseについてはexport
のドキュメントシェル組み込みの制限も参照してください.
あらゆるマシンで見つかることが期待できる小さなツールセットには,知ってお くべき制限がいくつか含まれているはずです.
awk
ユーザ関数呼び出しで,カッコの前に空白を残さないでください. GNU awkはそれを拒絶します.
$ gawk 'function die () { print "Aaaaarg!" } BEGIN { die () }' gawk: cmd. line:2: BEGIN { die () } gawk: cmd. line:2: ^ parse error $ gawk 'function die () { print "Aaaaarg!" } BEGIN { die() }' Aaaaarg! |
プログラムを決定的にしたい場合,配列上のfor
に依存しないでください.
$ cat for.awk END { arr["foo"] = 1 arr["bar"] = 1 for (i in arr) print i } $ gawk -f for.awk </dev/null foo bar $ nawk -f for.awk </dev/null bar foo |
HP-UX 11.0のネイティブのAWKのように,内部アンカーに調子が悪い正規表現の エンジンがあるものもあります.
$ echo xfoo | $AWK '/foo|^bar/ { print }' $ echo bar | $AWK '/foo|^bar/ { print }' bar $ echo xfoo | $AWK '/^bar|foo/ { print }' xfoo $ echo bar | $AWK '/^bar|foo/ { print }' bar |
そのようなパターンに依存したり(すなわち,`/^(.*foo|bar)/'を使用する), そのようなAWKを拒絶する単純なテストを使用したりしないでください.
cat
オプションに依存しないようにしてください.しかし,表示不可能な文字を表示 するオプション`-v'は,移植性がありそうです.
cc
`cc foo.c -o foo'のようなコンパイルが失敗したとき,(Reliant UNIXのCDSのように)`foo.o'を残すコンパイラもあります.
HP-UX cc
は,プリプロセスとアセンブラを行なう`.S'ファイル
を受け入れません.`cc -c foo.S'は成功したように見えますが,実際には
何もしません.
`cc foo.c'で生成されるデフォルトの実行形式は,以下のようになるはず です.
`a.out' -- 通常のUnixの慣習です.
`b.out' -- i960 コンパイラ(gcc
を含む).
`a.exe' -- gcc
が移植されたDJGPP.
`a_out.exe' -- OpenVMS上のDEC Cに対するGNV cc
ラッパー.
`foo.exe' -- 様々なMS-DOSのコンパイラ.
cmp
cmp
は,二つのファイルの生のデータの比較を実行しますが,
diff
は二つのテキストファイルを比較します.そのため,
DOSのファイルを比較する場合,二つのファイルが異なっているかど
うかを調査するだけの場合でも改行のエンコードの違いで見せかけの差が発生す
ることを避けるため,diff
を使用してください.
cp
伝統的に,ファイルのタイムスタンプの分解能は一秒になっていて,`cp
-p'ではタイムスタンプをそのままコピーします.しかし,最近のファイルシス
テムには分解能が1ナノ秒のタイムスタンプになっているものもたくさんありま
す.残念ながら,`cp -p'の実装ではファイルのコピー時にタイムスタンプ
を切り詰めるので,このためコピーされたファイルが結果としてソースよりも古
くなってしまいます.切り詰めの正確な量はcp
が使用するシステムコー
ルの分解能に依存します.伝統的に,これはutime
で,それは分解能が1
秒になっていますが,新しいcp
の実装ではutimes
を使用して
いて,それは分解能が1マイクロ秒になっています.これらの新しい実装には,
GNU coreutils 5.0.91やそれ以降,そしてSolaris 8 (sparc)のパッチ109933-02
やそれ以降が含まれます.残念ながら,2003年9月の段階では,完全なナノ秒の
分解能を持つタイムスタンプを設定するシステムコールはありません.
SunOS cp
は`-f'をサポートしていませんが,その
mv
はサポートしています.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
`-u'には移植性がありません.
Tru64のように,実装によっては`/dev/null'の比較で失敗するものもあり ます.その代わりに空のファイルを使用してください.
dirname
全てのホストに動作するdirname
があるわけではなく,その代わりに
AS_DIRNAME
を使用すべきです(see section M4shでのプログラミング).例えば以
下のようにします.
dir=`dirname "$file"` # This is not portable. dir=`AS_DIRNAME(["$file"])` # This is more portable. |
これは,POSIXで要求されている標準では,幾分微妙な扱いです.例 えばUN*Xでは`//1'は`/'になるのでしょうか?以下はPaul Eggertの 回答です.
古いUnixライクのものではそうはならず,前置される`//'は特殊なパス名 になります.それは"スーパールート"を参照し,他のマシンのファイルをアク セスするために使用されます.前置される`///',`////'などは, `/'と等価です.しかし,前置される`//'は特殊です.この伝統的は Apollo Domain/OSで始まったと考えていて,古いホストではまだそのOS を使用 しています.
POSIXでは可能ですが,`//'に対する特別扱いは要求されていま せん.そこでは,形式`//([^/]+/*)?'のパス名でのdirnameの動作は,実装 で定義されると告げています.これらの場合,GNU
dirname
は`/'を返しますが,古いUnixライクのものでも動作す るように`//'を返した方が移植性が高いでしょう.
egrep
POSIX 1003.1-2001では,もはやegrep
を要求していません
が,より古いホストの多くはまだPOSIXのgrep -E
での置換を
サポートしていません.この問題を回避するため,AC_PROG_EGREP
を呼
び出し,$EGREP
を使用してください.
空の代入は移植性が無く,代わりに`?'を使用してください.例えば, Digital Unix v5.0では以下のようになります.
> printf "foo\n|foo\n" | $EGREP '^(|foo|bar)$' |foo > printf "bar\nbar|\n" | $EGREP '^(foo|bar|)$' bar| > printf "foo\nfoo|\n|bar\nbar\n" | $EGREP '^(foo||bar)$' foo |bar |
$EGREP
もgrep
の制限で苦しむことになります.
expr
`x'で始まるexpr
キーワードはないので,expr
が
wordを間違って解釈しないように,`expr x"word" :
'xregex''を使用してください.
length
,substr
,match
,そしてindex
は使用し
ないでください.
expr
(`|')`|'を使用することは可能です.POSIXでは,`expr ''' が 空の文字列を返すことを必須としていませんが,空の文字列を用いて空の文字列 (またはゼロ)とともに`|'を用いたときの結果は安全ではありません.例え ば以下を考えます.
expr '' \| '' |
GNU/LinuxとPOSIX.2-1992では,この場合は空の文字列を 返しますが,伝統的なUNIXでは`0'を返します(Solarisはそのような 例の一つです).最近のPOSIX.1-2001ドラフトでは,その指定は伝統 的なUNIXの動作に一致するよう変更されています(信じられないことですが, これを修正するには時すでに遅しです).同じ問題が,計算結果が空の文字列に なるときにも,以下の状態では発生します.
expr bar : foo \| foo : bar |
空の文字列を避けることで,この移植性の問題を避けてください.
expr
(`:')Solarisではサポートされていないので,パターン内に,`\?',`\+', そして`\|'を使用しないでください.
POSIX標準では,`expr 'a' : '\(b\)''が`0'を出力するか
空の文字列を出力するのかは明確ではありません.実際問題として,それはほと
んどのプラットフォームで空の文字列を出力しますが,移植性の高いスクリプト
では,これを仮定すべきではありません.例えば,QNX 4.25 ネイティ
ブのexpr
は`0'を返します.
均一な動作を得る手段として,デフォルト値として空の文字列を使用することに なっていると信じているかもしれません.
expr a : '\(b\)' \| '' |
残念ながら,これは元の式として正確に動作します.詳細は,
`expr
(`:')'の項目を参照してください.
古いexpr
の実装(例えば,SunOS 4のexpr
とSolaris 8の
/usr/ucb/expr
)には,一致したサブ文字列が120バイトより長い場合,
expr
が異常終了するという,思慮の欠けた長さの制限があります.こ
の状況では,expr
が失敗した場合,`echo|sed'に頼りたいと思
うかもしれません.
残っているものはそれだけではありません!
QNX 4.25のexpr
には,空の文字列ではなく`0'となる
ことに加えて,終了ステータスでおかしな動作があります.それはカッコが使用
されているときには,常に1になるということです!
$ val=`expr 'a' : 'a'`; echo "$?: $val" 0: 1 $ val=`expr 'a' : 'b'`; echo "$?: $val" 1: 0 $ val=`expr 'a' : '\(a\)'`; echo "?: $val" 1: a $ val=`expr 'a' : '\(b\)'`; echo "?: $val" 1: 0 |
実際に,(sed
のような)他の手法でexpr
プログラムで異常
終了を捕獲する準備がある場合,結果を二回得る可能性があるので,これは大き
な問題となります.例えば以下を考えます.
$ expr 'a' : '\(a\)' || echo 'a' | sed 's/^\(a\)$/\1/' |
ほとんどのホストでは`a'を出力しますが,QNX 4.25では
`aa'になります.単純な回避方法として,expr
でのテストを構
成し,結果によってexpr
やfalse
で変数を設定する方法を
使用します.
fgrep
POSIX 1003.1-2001では,もはやfgrep
を要求していません
が,より古いホストの多くはまだPOSIXのgrep -F
での置換を
サポートしていません.この問題を回避するため,AC_PROG_FGREP
を呼
び出し,$FGREP
を使用してください.
find
オプション`-maxdepth'はGNU特有のようです.Tru64 v5.1,
NetBSD 1.5,そしてSolaris 2.5のfind
コマンドはそれを
理解しません.
`{}'の置換は,引数が正確に{}の場合のみ保証され,それが引 数の一部の場合は保証されません.例えば,DUとHP-UX 10.20とHP-UX 11では保 証されません.
$ touch foo $ find . -name foo -exec echo "{}-{}" \; {}-{} |
一方,GNU find
は`./foo-./foo'を報告します.
grep
System Vの`grep -s'はエラーメッセージのみ抑制し,出力を抑制しないの
で,出力を抑制するために`grep -s'を使用しないでください.その代わり
に(ファイルが存在しない場合) grep
の標準出力と標準エラー出力を
`/dev/null'へリダイレクトしてください.一致が見つかったかどうかを決
定するために,grep
の終了ステータスを調査してください.
最後のパターンのみ尊重するgrep
(例えば,AIX 6.5とSolaris
2.5.1)もあるので,`-e'で複数の正規表現を使用しないでください.ど
ちらにしろ,Stardent Vistra SVR4のgrep
には`-e' がありませ
ん.... その代わりに拡張した正規表現と代入を使用してください.
Irix 6.5.16mのgrep
は,それをサポートしていないので,
`-w'に依存しないようにしてください.
ln
`-f'オプションがあるln
に依存しないようにしてください.
古いシステムではシンボリックリンクは利用不可能です.移植性のある代替物
`$(LN_S)'を使用してください.
2.04以前のバージョンのDJGPPに対して,ln
は実行形式へのソフトリ
ンクを,実際のプログラムを呼び出すスタブを生成することでエミュレートしま
す.この機能は,Unix独自の実行形式以外のファイルでも動作します.そのため,
`ln -s file link'は`link.exe'を生成し,それは実行された場合に
`file.exe'の呼び出しを試みます.しかしこの機能は実行形式に対しての
み動作するので,このシステムでは`cp -p'が代わりに使用されます.
DJGPPの2.04とそれ以降では完全なシンボリックリンクがサポートされています.
ls
移植性のあるオプションは`-acdilrtu'です.最近では,`-l' で
所有者とグループを出力しますが,伝統的なls
はグループを省略しま
す.
最近では,すべての診断結果は標準エラー出力に出てきますが,伝統的な
`ls foo'は,`foo'が存在しない場合,メッセージ`foo not
found'を標準出力に出力します.伝統的なls
では,`.c'ファイ
ルが無い場合,`sources=`ls *.c 2>/dev/null`'は`sources="*.c
not found"'と等価なので,そのようなシェルコマンドを書くときに注意してく
ださい.
mkdir
mkdir
のオプションには移植性はありません.`mkdir -p
filename'の代わりにAS_MKDIR_P(filename)
を使用すべきで
す(see section M4shでのプログラミング).
mv
移植性のあるオプションは,`-f'と`-i'のみです.
ファイルシステム間で個別にファイルを移動することは(V6では)移植性がありま すが,常に強力でははありません.`mv new existing'をするとき, `existing'の古いものも新しいものも実際には存在していないという危険 な状態が存在します.
ファイルを`/tmp'から移動することで,これらのファイルを作成していた
としても,好ましくない(が,まったく有効な)警告を発生することがあることを
覚えておいてください.システムによっては,`/tmp'にファイルを作成す
ると,guidを自分が所属していないwheel
に設定するものもあります.そ
のためファイルがコピーされると,chgrp
で失敗します.
$ touch /tmp/foo $ mv /tmp/foo . error-->mv: ./foo: set owner/group (was: 3830/0): Operation not permitted $ echo $? 0 $ ls foo foo |
この動作は,POSIXに準拠しています.
何らかの理由でファイル属性の複製に失敗する場合,
mv
は診断メッセー ジを標準エラー出力に書き出しますが,この異常終了で,mv
は終了ス テータスを変更しません.
マウントポイントをまたがってディレクトリを移動することは移植性が無いので,
cp
とrm
を使用してください.
開いているファイルの移動/削除は移植性がありません.以下の例はDOS/WIN32 では実行不可能です.
exec > foo mv foo bar |
以下も実行不可能です.
exec > foo rm -f foo |
sed
文字クラスの一部の場合でも,パターンに(エスケープされていない)セパレータ
を含めるべきではありません.POSIX準拠では,Crayのsed
は`s/[^/]*$//'を拒絶します.`s,[^/]*$,,'を使用してください.
sedのスクリプトは,八文字以上の分岐ラベルを使用すべきではなく,コメント を含めるべきでもありません.
NetBSD 1.4.2では,二番目のものをコマンドとして解釈しようと試み
るので,sed
によっては,余分な`;'を含めてはなりません.
$ echo a | sed 's/x/x/;;s/x/x/' sed: 1: "s/x/x/;;s/x/x/": invalid command code ; |
sed
によっては,入力バッファに4000バイトの制限があるものもある
ので,入力は妥当な長さの行にすべきです.
`\|'の交換は一般的ですが,POSIXはそのサポートを要求してい
ないので,移植性の高いスクリプトでは避けるべきです.Solaris 8の
sed
は交換をサポートしていません.例えば,`sed '/a\|b/d''
は,リテラル文字列`a|b'を含んでいる行のみ検出します.
グループ内のアンカー(`^'と`$')は移植性がありません.
パターン内の入れ子状のカッコは,現在のホストでは完全に移植性あるものなの
ですが,SVR3のように古いsed
の実装ではサポートされていません.
もちろんオプション`-e'には移植性がありますが,それは不要です.ダッ シュで始まる有効なsedプログラムは無いので,明確にする役には立ちません. 唯一の有効性は,以下のように字下げを強制的に行なうときです.
sed -e instruction-1 \ -e instruction-2 |
これは以下の代わりのものです.
sed instruction-1;instruction-2 |
もう一つの垢抜けた伝説として,"マッチしたもの"を意味するs
コマン
ドの一部を置換するとき,`&'を使用しても移植性はあるでしょう.すべて
のベル研究所のV7 sed
の子孫は(少なくとも,我々はそれより古い
sed
を経験したことはありません)サポートしています.
POSIXでは,`!'とそれ以降のコマンドの間に空白があってはい けません.アドレスと`!'の間の空白はOKです.例えば,Solaris 8では以 下のようになります.
$ echo "foo" | sed -n '/bar/ ! p' error-->Unrecognized command: /bar/ ! p $ echo "foo" | sed -n '/bar/! p' error-->Unrecognized command: /bar/! p $ echo "foo" | sed -n '/bar/ !p' foo |
sed
(`t')古いシステムには,新しいサイクルと開始するとき,その`t'フラグをリセッ
トすることを"忘れる" sed
があるシステムもあります.例えば,
MIPS RISC/OSとIRIX 5.3で,以下のsed
スクリプトを
実行した場合を考えます(行番号は,実際にはテキストの一部ではありません).
s/keep me/kept/g # a t end # b s/.*/deleted/g # c : end # d |
ファイルの内容は以下を考えます.
delete me # 1 delete me # 2 keep me # 3 delete me # 4 |
以下のようになります.
deleted delete me kept deleted |
以下のようにはなりません.
deleted deleted kept deleted |
なぜでしょう?一行目を処理しているとき,マッチするのでtフラグがセットさ
れ,b行からd行まで移動し,出力が生成されます.二行目を処理しているとき,
tフラグはセットされたままです(これはバグです).しかし,a行はマッチに失敗
しますが,置換が失敗するとき,sed
はtフラグをクリアすることをサ
ポートしていません.そのため,フラグがセットされているように見えるb行は,
それをクリアし,dへ移動し,その結果,`deleted'の代わりに
`delete me'になります.三行目を処理しているとき,マッチを示すt がク
リアされるため,フラグがセットされ,その結果,b行はフラグをクリアし移動
します.最終的にフラグはクリアになっているので,四行目は正しく処理されま
す.
sed
の`t'について覚えておくべきことは二つあります.最初に,
成功した置換によっては,置換の直前だけでなく`t'ジャンプする
ことを覚えておいてください.そのため,tフラグを実際にリセットするために,
ごまかしの`t clear; : clear'を使用してください.
二番目は,それぞれの新しいサイクルでフラグをクリアするのをsed
に依頼することはできません.
上記のスクリプトの移植性の高い実装の一つは,以下のようになります.
t clear : clear s/keep me/kept/g t end s/.*/deleted/g : end |
touch
要求されるタイムスタンプ(例えば`-r'オプション)を指定する場合,
touch
は通常,utime
やutimes
システムコールを使用し,
結果として,`cp -p'に存在するタイムスタンプの切り詰めの問題と同様の
結果になるはずです.
古いBSDシステムには,空のファイルに対するtouch
のよう
なコマンドで,タイムスタンプを更新しない結果となるものもあるので,回避す
るために,echo
のようなコマンドを使用してください.
GNU touch
3.16r(とそれ以前の全て)は,空のファイルが
NFSでマウントされている4.2のボリュームのとき,SunOS 4.1.3 での
動作で異常終了します.
make
自身には非常に多くの制限があるので苦労します,ここではわず
かですが紹介します.とにかく,シェルによってコマンドが実行されるので,そ
の弱い部分の全てが継承されていくということを覚えておいてください
....
$<
POSIXでは,`makefile'の構成物の`$<'は推測される規則
と`.DEFAULT'ルールのみで使用可能だと告げています.通常のルールでの
その意味は明記されていません.例えば,Solaris 8のmake
はそれを
引数で置換します.
NEWS-OS 4.2Rのように,マクロ名にアンダースコアを前置することをサポートし
ていないmake
もあります.
$ cat Makefile _am_include = # _am_quote = all:; @echo this is test $ make Make: Must be a separator on rules line 2. Stop. $ cat Makefile2 am_include = # am_quote = all:; @echo this is test $ make -f Makefile2 this is test |
HP-UXのバージョンによっては,make
には,バックスラッシュ以降の
複数の改行を,空ではない行も含めて読み込むものもあります.例えば以下のよ
うにします.
FOO = one \ BAR = two test: : FOO is "$(FOO)" : BAR is "$(BAR)" |
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 <<END VPATH = .. all: foo/bar END % make mkdir foo mkdir foo/bar |
ルールは,前に存在している手動のVPATH
検索を使用するので,これは予
想外の結果になるはずです.
VPATH = .. all : foo/bar command `test -d foo/bar || echo ../`foo/bar |
上記のcommand
は,現在のディレクトリに作成された,空の
`foo/bar'ディレクトリで実行されます.
GNU make
は,VPATH
で見つかったファイルを使用す
べきとき決定するアルゴリズムは幾分複雑です.See (make)Search Algorithm section `How Directory Searches are Performed' in The GNU Make Manual.
ターゲットのリビルドが必要な場合,GNU make
は,このター
ゲットをVPATH
で検索している間に見つかったファイル名を廃棄し,
`Makefile'で与えられたファイル名を使用して,ローカルなファイルをビ
ルドします.ターゲットをリビルドする必要が無い場合は,GNU
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
はビルドされていないソースへの予約にすべきです.
単一のサフィックスルール(Single Suffix Rule)は,基本的に(推測され る)通常のサフィックスルール(`.from.to:')ですが,ディスティネー ション(destination)サフィックスは空(`.from:')です.
分離された依存性(Separated dependencies)は,ルールを定義すること無 く,ターゲットの必要条件のリストを単純に参照します.通常,一方ではルール を,もう一方で依存性をリストアップすることが可能です.
Solarisのmake
は,単一のサフィックスルールで定義されたターゲッ
トに対する,分離された依存性をサポートしていません.
$ cat Makefile .SUFFIXES: .in foo: foo.in .in: cp $< $ $ touch foo.in $ make $ ls Makefile foo.in |
一方GNU Makeはサポートしています.
$ gmake cp foo.in foo $ ls Makefile foo foo.in |
それは`foo: foo.in'の依存性無しで動作することに注意してください.
$ cat Makefile .SUFFIXES: .in .in: cp $< $ $ make foo cp foo.in foo |
そして,それは二重のサフィックスの継承ルールで動作することにも注意してく ださい.
$ cat Makefile foo.out: foo.in .SUFFIXES: .in .out .in.out: cp $< $ $ make cp foo.in foo.out |
結果として,そのような状況では,ターゲットルールを書く必要があります.
伝統的に,ファイルのタイムスタンプの分解能は1秒になっていて,
make
はファイルがそれ以外のものより新しいかどうかを決定するため
これらのタイムスタンプを使用していました.しかし,最近のファイルシステム
には分解能が1ナノ秒のタイムスタンプになっているものもたくさんあります.
make
の実装には,タイムスタンプ全体を見るものもあります.それ以
外では,分数部分を無視し,結果として間違ったものとなるはずです.通常,こ
れは問題ありませんが,非常に稀な状況では,タイムスタンプの切り詰めのバグ
を回避するため,`sleep 1'のような手法を使用する必要があるかもしれま
せん.
`cp -p'と `touch -r'のようなコマンドでは,通常はファイルのタイ ムスタンプを完全な分解能でコピーしません(see section 通常のツールの制限).このため,以下のようなルールは気を付けるべきです.
dest: src cp -p src dest |
それは,タイムスタンプの切り詰め後では,`src'よりも`dest'が古
くなり,このためmake
は次回も不必要な仕事を再び行なうはずです.
この問題を回避するため,タイムスタンプファイルを使用することが可能です.
例えば以下のようにします.
dest-stamp: src cp -p src dest date >dest-stamp |
[ << ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
This document was generated by Akihiro Sagawa on June, 15 2005 using texi2html 1.70.