独自の調査を書いているとき,コードを移植性の高いものにするため,使用を 避けるべきシェルスクリプトプログラムのテクニックもあります.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シェルファミリーのメンバーを,いくつか説明していきます.
foo= false $foo echo "Don't use it: $?"
cat ${FOO=`bar`}
BASH_VERSION
が設定されているかどうかをテストしてください.その
拡張を利用不可能にし,@acronym{POSIX}互換性を要求するため,@samp{set
-o posix} を実行してください.詳細は,@xref{Bash POSIX Mode,, Bash @acronym{POSIX} Mode, bash, The @acronym{GNU} Bash Reference Manual}.
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で も存在する問題を意味しています.
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
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!).
$@
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
を含め,古い@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}
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は,listをdefaultの@emph{最後 の}項目に設定することです! 移植性の高い方法は,Ultrixで八番目のビットを二回切替えるために,二重 (引用符による)代入を使用することです.
list=${list="$default"}...しかし,Solarisの`}'のバグ(上記を参照してください)には用 心してください.安全にするには,以下を使用してください.
test "${var+set}" = set || var={value}
`commands`
$ pwd /tmp $ test -n "`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)
列にいくつかの変数を設定するとき,評価の順序が定義されていないことを覚
えておいてください.例えば,`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
デフォルト値を代入するために,以下のアルゴリズムを使用してください.
: ${var='my literal'}
: ${var="$default"}
var=${var="$default"}
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 CDPATHAutoconfが生成したスクリプトは自動的に
CDPATH
をunsetするので,こ
れらのスクリプトのこの問題を心配する必要はありません.
IFS
IFS
の最初の文字をバックスラッシュに設定しないでください.実際,
`"$@"'で要素を加えるときは,Bourneシェルは最初の文字(バックスラッ
シュ)を使用し,そして,バックスラッシュエスケープをもう一度解釈する(!)
シェルもあり,そのため,バックスペースとその他の奇妙な文字で終ることが
可能になっています.
(splitを実行していないとき,標準的なコードの)IFS
の適切な値は,
`SPCTABRET'です.`@*'の引数を連結するため
に使用するので,最初の文字は特に重要です.
LANG
LC_ALL
LC_COLLATE
LC_CTYPE
LC_MESSAGES
LC_MONETARY
LC_NUMERIC
LC_TIME
LANGUAGE
LC_ADDRESS
LC_IDENTIFICATION
LC_MEASUREMENT
LC_NAME
LC_PAPER
LC_TELEPHONE
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
NULLCMD
が`:'だと考えます
が,@command{zsh}はBourneシェル互換モードでも,NULLCMD
を
`cat'に設定します.NULLCMD
の設定を忘れた場合,スクリプトは
標準入力からのデータ待ちのためサスペンド状態になるかもしれません.
ENV
MAIL
MAILPATH
PS1
PS2
PS4
(unset ENV) >/dev/null 2>&1 && unset ENV MAIL MAILPATH PS1='$ ' PS2='> ' PS4='+ '
PWD
status
zsh
(少なくとも3.1.6)での`$?'へのエイリアスで,
そのため読み出し専用になっています.使用しないでください.
PATH_SEPARATOR
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"'を使用することで避け
ることが可能です.
ac_top_srcdir
のような変数を計算するとき,この問題を調査するので
(see section コンフィグレーション作業の実行),これらの変数で@command{cd}しても安全
です.
@command{pwd}コマンドの議論も参照してください.
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 OKAsh 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
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
$?
を想定しています.残念ながら
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
を呼び
出してください.
#! /bin/sh echo $FOO FOO=bar echo $FOO exec /bin/sh $0環境変数で`FOO=foo'として実行した場合,これらのシェルはそれぞれ `foo'と`bar'を交互に出力しますが,`foo'を出力した後に, 続けて`bar'を出力します. このため,それぞれ更新した環境変数を再び@command{export}すべきです.
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 シェルの代入を参照してください.
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
set x $my_list; shiftすべてのオプションを認識しないこととは"反対"の問題(例えば,`set -e -x'で`-x'をコマンドラインに割り当てるといった問題)があるシェル もあります.以下のように省略した方が良いでしょう.
set -ex
test
プログラムは,多くのファイルと文字列のテストを実行する方法
です.それは別名の`['で呼び出されることも多いのですが,M4の引用符
文字という問題から,Autoconfのコードではその名前を使用することが要求さ
れています.
test
を使用して複数の調査を行う必要がある場合,test
の演算
子の`-a'と`-o'の代わりに,シェル演算子の`&&'と`||'
で組み合わせてください.System Vでは,`-a'と`-o'の優先順位は,
単項演算子とは間違った関係になっています.従って,@acronym{POSIX}はそ
れらを指定しないので,それを使用すると移植性が無くなります.同じ文で
`&&'と`||'を組み合わせる場合,同じ優先順位があることを覚えて
おいてください.
@command{test}で`!'を使用してもかまいませんが,@command{if}ではで
きません.`test ! -r foo || exit 1'.
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"'より堅牢です.
$ 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{false}に対しては正しく, @command{true}に対しては正しくない,異常終了のステータスを生成するので, ある意味ではそのとおりです.
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 シェル組み込みの制限も参照してください.
あらゆるマシンで見つかることが期待できる小さなツールセットには,知って おくべき制限がいくつか含まれているはずです.
$ 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 fooHP-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を拒絶する単純なテストを使用したりしないでください.
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}でき
なくすることが可能です.それは管理者が設定可能なパラメータなので,動作
を示すためにカーネル名を使用することは不可能です.
$ uname -a OSF1 medusa.sis.pasteur.fr V5.1 732 alpha $ date "+%s" %s
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ライクのものでも動作 するように`//'を返した方が移植性が高いでしょう.
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}の制限で苦しむことになります.
length
,substr
,match
,そしてindex
は使用し
ないでください.
expr '' \| ''@acronym{GNU}/Linuxと@acronym{POSIX}.2-1992では,この場合は空の文字列 を返しますが,伝統的なUNIXでは`0'を返します(Solarisはそのよ うな例の一つです).最近の@acronym{POSIX}.1-2001ドラフトでは,その指定 は伝統的なUNIXの動作に一致するよう変更されています(信じられないこ とですが,これを修正するには時すでに遅しです).同じ問題が,計算結果が 空の文字列になるときにも,以下の状態では発生します.
expr bar : foo \| foo : bar空の文字列を避けることで,この移植性の問題を避けてください.
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}で変数を設定する方 法を使用します.
grep -F
での置
換をサポートしていません.この問題を回避するため,AC_PROG_FGREP
を呼び出し,$FGREP
を使用してください.
$ touch foo $ find . -name foo -exec echo "{}-{}" \; {}-{}一方,@acronym{GNU} @command{find}は`./foo-./foo'を報告します.
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}に依存しないようにしてください.
AS_MKDIR_P(filename)
を使用すべき
です(see section M4shでのプログラミング).
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{cp}と@command{rm}を使用してください. 開いているファイルの移動/削除は移植性がありません.以下の例はDOS/WIN32 では実行不可能です.何らかの理由でファイル属性の複製に失敗する場合,@command{mv}は診断メッ セージを標準エラー出力に書き出しますが,この異常終了で,@command{mv}は 終了ステータスを変更しません.
exec > foo mv foo bar以下も実行不可能です.
exec > foo rm -f foo
$ 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
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
echo
のようなコマンドを使用してください.
@acronym{GNU} @command{touch} 3.16r(とそれ以前の全て)は,空のファイル
が@acronym{NFS}でマウントされている4.2のボリュームのとき,SunOS 4.1.3
での動作で異常終了します.
@command{make}自身には非常に多くの制限があるので苦労します,ここではわ ずかですが紹介します.とにかく,シェルによってコマンドが実行されるので, その弱い部分の全てが継承されていくということを覚えておいてください @enddots{}
$<
$ 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
FOO = one \ BAR = two test: : FOO is "$(FOO)" : BAR is "$(BAR)"
FOO
はone BAR = two
と等価です.それ以外の@command{make}で
は,バックスラッシュは直後の行だけを含みます.
#
ではじまり,
エスケープされていない改行まで続きます.
% 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 -e
とSHELL
の注意も参照してください.)
サブ呼び出しの@command{make}に優先物を伝搬させるもう一つの方法は,
`Makefile'に手動で行なうことです.
foo = foo one: @echo $(foo) $(MAKE) foo=$(foo) two two: @echo $(foo)そうする場合,ユーザが優先したいと思われるすべてのマクロを予測する必要 があります.
SHELL
マクロ
$(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
% 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
VPATH
サポートを指定していません.多くの
@command{make}はVPATH
サポートの形式がありますが,その実装は,
@command{make}間で一貫していません.
VPATH
機能を必要としている人々への最高の提案は,@command{make}の
実装を選択しそれに固執するようにと言うことかもしれません.
`Makefile' の結果は常に移植性があるとは限らないので,移植性の高い
@command{make}を選択するのが良いでしょう(ヒント,ヒント).
VPATH
の実装の既知の問題には以下のものがあります.
VPATH
と二重のコロンのルール
VPATH
への代入で,Sunの@command{make}は最初の二重コロンのルールの
組だけを実行します.(このコメントは,1994年からで,現在は無くなってい
ます.SunOS 4では移植性があります.これが再生成された場合,それを説明
するテストケースを送ってください.)
$<
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
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.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最初の二つの@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
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'ディレクトリで実行されます.
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
$ 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.