ファイル全体をパターンスペースに読み込むのが失敗する理由はさまざまです。最後の行を取り巻く問題の論理的な問題は一般的なものです。これはsed
のラインサイクルに関連します-ラインがなくなり、sed
EOFに遭遇すると、処理を終了します。そして、あなたが最後の行にいて、sed
別のものを取得するように指示した場合、それはその場で停止し、それ以上何もしません。
つまり、ファイル全体をパターンスペースに読み込む必要がある場合は、とにかく別のツールを検討する価値があります。事実は、そのsed
名のとおり、ストリームエディタです-一度に1行(または論理データブロック)を処理するように設計されています。
完全なファイルブロックを処理するためによりよく装備されている多くの類似したツールがあります。ed
そしてex
、例えば、できることの多くsed
と同様の構文で-そして他の多くで- ことができますが、入力ストリームのみを操作して、それを出力に変換しながら変換sed
するのではなく、ファイルシステムに一時バックアップファイルを維持します。彼らの仕事は必要に応じてディスクにバッファリングされ、ファイルの終わりで突然終了することはありません(そして、バッファの緊張の下で破裂することはそれほど多くありません)。さらに、sed
ラインマーク、取り消し、名前付きバッファ、結合など、ストリームコンテキストでは意味をなさないような多くの便利な機能を提供します。
sed
の主な長所は、データを読み取り次第、すばやく、効率的に、ストリームで処理できることです。ファイルを丸呑みすると、それを破棄し、最後に言及した最後の行の問題、バッファオーバーラン、ひどいパフォーマンスなどのエッジケースの問題に遭遇する傾向があります-解析するデータが長くなると、一致を列挙するときの正規表現エンジンの処理時間が長くなります指数関数的に増加します。
ちなみに、最後の点についてですが、例のs/a/A/g
ケースは単純な例であり、入力として収集したい実際のスクリプトではない可能性が高いと思いますが、慣れるのに時間がかかるかもしれません。y///
。g
1つの文字を別の文字に置換することが多い場合y
は、非常に便利です。これは置換ではなく変換であり、正規表現を意味しないため、はるかに高速です。この後者の点は、空の//
アドレスを保持および繰り返ししようとするときにも影響を与えませんが、それらによって影響を受ける可能性があるため、便利です。いずれにせよ、y/a/A/
同じことを達成するためのより簡単な方法です-と同様にスワップが可能です:y/aA/Aa/
これは、すべての大文字と小文字を相互に線のように入れ替えます。
また、記述した動作は実際に発生するはずの動作ではないことにも注意してください。
GNUのからinfo sed
で、一般的に報告されたバグのセクション:
N
最終行のコマンド
ほとんどのバージョンのコマンドは、ファイルの最終行にコマンドが発行されるとsed
何も出力せずに終了N
します。sed
もちろん-n
コマンドスイッチが指定されていない限り、GNU は終了する前にパターンスペースを出力します。この選択は仕様によるものです。
たとえば、の動作sed N foo bar
は、fooの行数が偶数か奇数かによって異なります。または、パターンマッチに続く次の数行を読み取るスクリプトを書く場合、の従来の実装で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
変数を空でない値に設定することです。
POSIXLY_CORRECT
環境変数は言及されているPOSIXの場合は、その指定しているのでsed
しようとしたときに出会いがEOF N
それは出力せずに終了すべきであるが、この場合、標準でGNUバージョンが意図的に休憩。また、動作が正当化されているとしても、エラーのケースはストリーム編集の1つであり、ファイル全体をメモリに丸呑みしないことが前提です。
この規格では、N
の動作を次のように定義しています。
そのノートでは、質問で示された他のいくつかのGNU-ismsがあります-特に、:
ラベル、b
牧場、および{
関数コンテキストの括弧の使用}
。経験則として、sed
任意のパラメーターを受け入れるコマンドはすべて\n
、スクリプトのewlineで区切られていると理解されています。だからコマンド...
:arbitrary_label_name; ...
b to_arbitrary_label_name; ...
//{ do arbitrary list of commands } ...
... sed
それらを読み取る実装によっては、すべてが不規則に実行される可能性が非常に高いです。移植可能にそれらを書く必要があります:
...;:arbitrary_label_name
...;b to_arbitrary_label_name
//{ do arbitrary list of commands
}
同じことが真のために保持しているr
、w
、t
、a
、i
、とc
(そしておそらく私は、現時点では忘れてることをさらにいくつか)。ほとんどすべての場合、それらはまた書かれるかもしれません:
sed -e :arbitrary_label_name -e b\ to_arbitary_label_name -e \
"//{ do arbitrary list of commands" -e \}
...ここで、新しい-e
xecutionステートメントは\n
ewline区切り文字を表します。したがって、GNU info
テキストで従来のsed
実装があなたに強制することを示唆している場合:
/foo/{ $!N; $!N; $!N; $!N; $!N; $!N; $!N; $!N; $!N }
...それはむしろ...
/foo/{ $!N; $!N; $!N; $!N; $!N; $!N; $!N; $!N; $!N
}
...もちろん、それも正しくありません。この方法でスクリプトを作成するのは少しばかげています。同じことを行うには、次のようなもっと簡単な方法があります。
printf %s\\n foo . . . . . . |
sed -ne 'H;/foo/h;x;//s/\n/&/3p;tnd
//!g;x;$!d;:nd' -e 'l;$a\' \
-e 'this is the last line'
...印刷:
foo
.
.
.
foo\n.\n.\n.$
.$
this is the last line
... t
estコマンドは、ほとんどのsed
コマンドと同様に、リターンレジスタを更新するためにラインサイクルに依存しているため、ここでラインサイクルはほとんどの作業を実行できます。これは、ファイルを丸呑みするときに行うもう1つのトレードオフです。ラインサイクルは二度と更新されないため、多くのテストが異常に動作します。
上記のコマンドは、単純なテストを行って、読み取った内容を確認するだけなので、入力に到達するリスクがありません。H
古いすべての行は、ホールドスペースに追加されますが、ラインが一致した場合/foo/
、それは上書きされh
、古い空間に。次にバッファーがx
変更され、s///
バッファーの内容が//
最後にアドレス指定されたパターンと一致する場合は、条件付き更新が試行されます。つまり//s/\n/&/3p
、ホールドスペースの3番目の改行をそれ自体で置き換え、ホールドスペースが現在一致している場合に結果を出力しようとします/foo/
。これがt
成功した場合、スクリプトはn
ot d
eleteラベルに分岐しl
ます。これにより、フックが実行され、スクリプトが終了します。
/foo/
ただし、ホールドスペースで両方と3番目の改行を一致させることができない場合、一致しない//!g
場合/foo/
はバッファーを上書きします。一致する場合は、\n
ewlineが一致しない場合にバッファーを上書きします(これにより/foo/
、自体)。このわずかな微妙なテストにより、バッファーがnoの長いストレッチで不必要にいっぱいになるのを防ぎ/foo/
、入力が積み重ならないため、プロセスがスムーズに保たれるようにします。no /foo/
または//s/\n/&/3p
failの場合に続いて、バッファーが再びスワップされ、最後を除くすべての行が削除されます。
最後の最後の行$!d
は、トップダウンsed
スクリプトを作成して複数のケースを簡単に処理する方法の簡単なデモです。一般的な方法で、最も一般的なものから始めて最も具体的なものに向かって不要なケースを削除する場合、エッジケースは、他の必要なデータを使用してスクリプトの最後まで単純に通過できるため、より簡単に処理できます。必要なデータだけを残して、すべてをラップします。ただし、このようなエッジケースをクローズドループからフェッチする必要がある場合は、はるかに困難です。
そして、これが私が言わなければならない最後のことです:あなたが本当にファイル全体をプルしなければならないなら、あなたはそれを行うためにラインサイクルに依存することによって少し少ない仕事をするために立つことができます。通常、N
extとn
extを先読みに使用します- ラインサイクルの前に進むためです。ループ内に閉じたループを冗長的に実装するのでsed
はなく-とにかくラインサイクルは単純な読み取りループであるため-目的が無差別に入力を収集することだけである場合は、おそらく簡単です。
sed 'H;1h;$!d;x;...'
...ファイル全体を収集するか、バストしようとします。
N
最後の行の動作に関する注意事項...
テストできるツールがないので、編集したファイルが次のリードスルーのスクリプトファイルであるN
場合、読み取りとインプレース編集の動作が異なることを考慮してください。