10 バグの報告

バグ報告はへメールで送ってください。また、可能であれば、報告の本文に「sed --version」の出力を含めてください。

次のようなバグ報告は送らないでください。

while building frobme-1.3.4
$ configure
error→ sed: file sedscr line 1: Unknown option to 's'
コード内の説明(日本語訳)

frobme-1.3.4のビルド中にconfigureを実行したところ、「sed: ファイルsedscrの1行目: sに対する不明なオプション」というエラーが出た、という報告例です。

GNU sedで使いたいパッケージをconfigureできない場合は、少し時間をかけて具体的な問題を特定し、単独で実行できるテストケースを作ってください。Cコンパイラーなどのプログラムとは異なり、sedのこうしたテストケースを作るのはとても簡単です。

単独で実行できるテストケースには、テストに必要なすべてのデータと、問題を起こす具体的なsedの呼び出し方を含めます。テストケースは小さいほどよいものです。「frobme-1.3.4をconfigureしてみてください」のように、sedから大きく離れたものを持ち込むべきではありません。確かに、原理的にはそれでもバグを探すための情報は足りますが、実際に調査するうえではあまり現実的ではありません。

以下は、よく報告されますがバグではないものです。

最終行のNコマンド

ほとんどの版のsedは、ファイルの最終行でNコマンドを実行すると、何も出力せずに終了します。GNU sedは、もちろん-nオプションが指定されていないかぎり、終了前にパターンスペースを出力します。これは意図した設計です。

既定の動作(GNU拡張で、POSIXには準拠しません):

$ seq 3 | sed N
1
2
3

POSIXに準拠した動作にするには、次のようにします。

$ seq 3 | sed --posix N
1
2

たとえば、次のコマンドの動作は、

sed N foo bar

fooの行数が偶数か奇数かによって変わります12。また、パターンにマッチした後の数行を読み込むスクリプトを書く場合、従来のsedの実装では、次のように書かなければなりません。

/foo/{ $!N; $!N; $!N; $!N; $!N; $!N; $!N; $!N; $!N }

次のように書くだけでは済みません。

/foo/{ N;N;N;N;N;N;N;N;N; }

いずれの場合も、最も簡単な回避策は、従来の動作に依存するスクリプトで$d;Nを使うか、POSIXLY_CORRECT変数に空でない値を設定することです。

正規表現の構文の衝突(バックスラッシュに関する問題)

sedはPOSIXの基本正規表現の構文を使います。規格によると、この構文では一部のエスケープシーケンスの意味は未定義です。sedで特に関係するのは、\|、\+、\?、\`、\'、\<、\>、\b、\B、\w、\Wです。

POSIXの基本正規表現を使う他のGNUプログラムと同様に、sedはこれらのエスケープシーケンスを特殊文字として解釈します。そのため、x\+は「x」の1回以上の出現にマッチします。abc\|defは「abc」または「def」にマッチします。

この構文は、他のsed向けに書かれたスクリプトを実行する際に問題となることがあります。sedのプログラムの中には、\|と\+が文字そのものの|と+にマッチすると仮定して書かれたものがあります。こうしたスクリプトをGNU sedなどの現代的なsedの実装で使うには、余分なバックスラッシュを取り除く修正が必要です。

一方、s|abc\|def||gを使って、abcとdefのどちらかの出現を取り除くスクリプトもあります。これはsed 4.0.xまでは動作しましたが、新しい版では文字列abc|defを取り除くものとして解釈します。これもPOSIXでは未定義の動作であり、この解釈の方が堅牢だと考える余地があります。たとえば、以前のsedでは、スラッシュをエスケープするという一般的な場合に、正規表現のマッチャーが\/を/として解析する必要がありましたが、これも未定義の動作です。新しい動作ではそれを避けられます。正規表現のマッチャーを私たちが制御できるのは一部だけなので、これはよいことです。

さらに、この版のsedは、スクリプトに表示できない文字を挿入するための複数のエスケープ文字をサポートしています(複数文字からなるものもあります)。\a、\c、\d、\o、\r、\t、\v、\xが該当します。これらは、他のsed向けに書かれたスクリプトで同様の問題を起こすことがあります。

-iが読み取り専用ファイルを上書きする

要するに、「sed -i」を使うと読み取り専用ファイルの内容を削除でき、一般に-iオプション(sedの実行を参照)では保護されたファイルを上書きできます。これはバグではなく、Unixファイルシステムの仕組みによるものです。

ファイルのアクセス許可は、そのファイルのデータに対してできる操作を定めます。一方、ディレクトリのアクセス許可は、その中にあるファイルの一覧に対してできる操作を定めます。「sed -i」は、ディスク上にある既存のファイルを書き込み用に開くことは決してありません。代わりに一時ファイルを操作し、最後に元の名前へ変更します。ファイルの名前を変更したり削除したりすることは、実際にはディレクトリの内容を変更することなので、その操作はファイルではなくディレクトリのアクセス許可に依存します。同じ理由で、sedは、読み取り専用ディレクトリにある書き込み可能なファイルに-iを使うことを認めず、そのようなファイルに-iを使うとハードリンクやシンボリックリンクが切れます。

0aが動作しない(エラーになる)

0行目は存在しません。0は特別なアドレスで、0,/RE/のようなアドレスをスクリプトの開始時から有効として扱うためにだけ使います。1,/abc/dと書いて最初の行に文字列「abc」が含まれている場合、そのマッチは無視されます。アドレス範囲は少なくとも2行にわたる必要があるためです(ファイルの終わりは例外です)。しかし、おそらく意図したのは、最初に「abc」を含む行までのすべての行を削除することでしょう。それには0,/abc/dを使います。

[a-z]が大文字と小文字を区別しない

これはロケールに関する問題です。POSIXは、[a-z]が現在のロケールの照合順序を使うことを要求しています。Cの言葉でいえば、strcmp(3)ではなくstrcoll(3)を使うということです。大文字と小文字を区別しない照合順序を持つロケールもあれば、そうでないものもあります。

別の問題として、[a-z]が照合要素を使おうとすることがあります。これはGNUシステム上で、GNU sedに付属するものをコンパイルする代わりに、GNU libcの正規表現マッチャーを使う場合にだけ起こります。たとえばデンマーク語ロケールでは、正規表現^[a-z]$は文字列「aa」にマッチします。これは「a」の後、「b」の前に位置する単一の照合要素だからです。スペイン語ロケールの「ll」やオランダ語ロケールの「ij」でも同様の動作になります。

シェルスクリプトのバグにつながることがあるこれらの問題を回避するには、環境変数LC_COLLATEとLC_CTYPEを「C」に設定してください。

s/.*//でパターンスペースが空にならない

これは、入力ストリームに不正なマルチバイトシーケンスが含まれる場合に起こります。POSIXは、そのようなシーケンスが「.」にマッチしないことを要求しています。そのため、「s/.*//」で期待どおりにパターンスペースが空になりません。実際、ほとんどのマルチバイトロケール(UTF-8ロケールを含みます)では、スクリプトの途中でsedのバッファーを空にする方法はありません。このため、GNU sedは拡張としてz(“zap”)コマンドを提供しています。

シェルスクリプトのバグにつながることがあるこれらの問題を回避するには、環境変数LC_COLLATEとLC_CTYPEを「C」に設定してください。


(12)

これが、動作の変更を促した実際の「バグ」です。