あらゆるマシンで見つかることが期待できる小さなツールセットには,知ってお くべき制限がいくつか含まれているはずです.
$ 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を拒絶する単純なテストを使用したりしないでください.
HP-UX ccは,プリプロセスとアセンブラを行なう.Sファイル を受け入れません.‘cc -c foo.S’は成功したように見えますが,実際には 何もしません.
‘cc foo.c’で生成されるデフォルトの実行形式は,以下のようになるはず です.
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できなくす
ることが可能です.それは管理者が設定可能なパラメータなので,動作を示すた
めにカーネル名を使用することは不可能です.
$ uname -a OSF1 medusa.sis.pasteur.fr V5.1 732 alpha $ date "+%s" %s
Tru64のように,実装によっては/dev/nullの比較で失敗するものもあり
ます.その代わりに空のファイルを使用してください.
AS_DIRNAME
を使用すべきです(see Programming in 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ライクのものでも動作す るように‘//’を返した方が移植性が高いでしょう.
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の制限で苦しむことになります.
length
,substr
,match
,そしてindex
は使用し
ないでください.
expr '' \| ''
GNU/LinuxとPOSIX.2-1992では,この場合は空の文字列を 返しますが,伝統的なunixでは‘0’を返します(Solarisはそのような 例の一つです).最近のPOSIX.1-2001ドラフトでは,その指定は伝統 的なunixの動作に一致するよう変更されています(信じられないことですが, これを修正するには時すでに遅しです).同じ問題が,計算結果が空の文字列に なるときにも,以下の状態では発生します.
expr bar : foo \| foo : bar
空の文字列を避けることで,この移植性の問題を避けてください.
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で変数を設定する方法を
使用します.
grep -F
での置換を
サポートしていません.この問題を回避するため,AC_PROG_FGREP
を呼
び出し,$FGREP
を使用してください.
‘{}’の置換は,引数が正確に{}の場合のみ保証され,それが引 数の一部の場合は保証されません.例えば,DUとHP-UX 10.20とHP-UX 11では保 証されません.
$ touch foo $ find . -name foo -exec echo "{}-{}" \; {}-{}
一方,GNU findは‘./foo-./foo’を報告します.
grep
の標準出力と標準エラー出力を
/dev/nullへリダイレクトしてください.一致が見つかったかどうかを決
定するために,grep
の終了ステータスを調査してください.
最後のパターンのみ尊重するgrep
(例えば,AIX 6.5とSolaris
2.5.1)もあるので,-eで複数の正規表現を使用しないでください.ど
ちらにしろ,Stardent Vistra SVR4のgrep
には-e がありませ
ん... その代わりに拡張した正規表現と代入を使用してください.
Irix 6.5.16mのgrepは,それをサポートしていないので,
-wに依存しないようにしてください.
2.04以前のバージョンのDJGPPに対して,lnは実行形式へのソフトリ
ンクを,実際のプログラムを呼び出すスタブを生成することでエミュレートしま
す.この機能は,Unix独自の実行形式以外のファイルでも動作します.そのため,
‘ln -s file link’はlink.exeを生成し,それは実行された場合に
file.exeの呼び出しを試みます.しかしこの機能は実行形式に対しての
み動作するので,このシステムでは‘cp -p’が代わりに使用されます.
DJGPPの2.04とそれ以降では完全なシンボリックリンクがサポートされています.
最近では,すべての診断結果は標準エラー出力に出てきますが,伝統的な
‘ls foo’は,fooが存在しない場合,メッセージ‘foo not
found’を標準出力に出力します.伝統的なlsでは,‘.c’ファイ
ルが無い場合,‘sources=`ls *.c 2>/dev/null`’は‘sources="*.c
not found"’と等価なので,そのようなシェルコマンドを書くときに注意してく
ださい.
AS_MKDIR_P(
filename)
を使用すべきで
す(see Programming in M4sh).
ファイルシステム間で個別にファイルを移動することは(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のスクリプトは,八文字以上の分岐ラベルを使用すべきではなく,コメント を含めるべきでもありません.
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
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
utime
やutimes
システムコールを使用し,
結果として,‘cp -p’に存在するタイムスタンプの切り詰めの問題と同様の
結果になるはずです.
古いBSDシステムには,空のファイルに対するtouchのよう
なコマンドで,タイムスタンプを更新しない結果となるものもあるので,回避す
るために,echo
のようなコマンドを使用してください.
GNU touch 3.16r(とそれ以前の全て)は,空のファイルが NFSでマウントされている4.2のボリュームのとき,SunOS 4.1.3 での 動作で異常終了します.