sed:単一行の入力で失敗することなく、ファイル全体をパターンスペースに読み込みます


9

ファイル全体をパターンスペースに読み込むことは、改行を置き換える場合に便利です&c。そして、次のことを助言する多くのインスタンスがあります:

sed ':a;N;$!ba; [commands...]'

ただし、入力に1行しか含まれていない場合は失敗します。

例として、2行入力の場合、すべての行が置換コマンドの影響を受けます。

$ echo $'abc\ncat' | sed ':a;N;$!ba; s/a/xxx/g'
xxxbc
cxxxt

ただし、1行入力の場合置換は実行されません

$ echo 'abc' | sed ':a;N;$!ba; s/a/xxx/g'
abc

sed一度にすべての入力を読み取るコマンドを記述して、この問題を回避するにはどうすればよいですか?


実際の質問が含まれるように質問を編集しました。必要に応じて他の回答を待つこともできますが、最終的にはベストアンサーを承認済みとしてマークします(回答の左、上下矢印ボタンのすぐ下にあるパイプボタンを参照してください)。
John1024 2015年

@ John1024おかげで、例がありがたいです。このようなことを見つけると、「すべてが間違っている」ことを思い出しがちですが、あきらめない人がいるのは嬉しいです。:}
dicktyr 2015年

2
3番目のオプションがあります!GNUのsed -zオプションを使用します。ファイルにnullがない場合は、ファイルの終わりまで読み取られます。
これから

回答:


13

ファイル全体をパターンスペースに読み込むのが失敗する理由はさまざまです。最後の行を取り巻く問題の論理的な問題は一般的なものです。これはsedのラインサイクルに関連します-ラインがなくなり、sedEOFに遭遇すると、処理を終了します。そして、あなたが最後の行にいて、sed別のものを取得するように指示した場合、それはその場で停止し、それ以上何もしません。

つまり、ファイル全体をパターンスペースに読み込む必要がある場合は、とにかく別のツールを検討する価値があります。事実は、そのsed名のとおり、ストリームエディタです-一度に1行(または論理データブロック)を処理するように設計されています。

完全なファイルブロックを処理するためによりよく装備されている多くの類似したツールがあります。edそしてex、例えば、できることの多くsedと同様の構文で-そして他の多くで- ことができますが、入力ストリームのみを操作して、それを出力に変換しながら変換sedするのではなく、ファイルシステムに一時バックアップファイルを維持します。彼らの仕事は必要に応じてディスクにバッファリングされ、ファイルの終わりで突然終了することはありません(そして、バッファの緊張の下で破裂することはそれほど多くありません)。さらに、sedラインマーク、取り消し、名前付きバッファ、結合など、ストリームコンテキストでは意味をなさないような多くの便利な機能を提供します。

sedの主な長所は、データを読み取り次第、すばやく、効率的に、ストリームで処理できることです。ファイルを丸呑みすると、それを破棄し、最後に言及した最後の行の問題、バッファオーバーラン、ひどいパフォーマンスなどのエッジケースの問題に遭遇する傾向があります-解析するデータが長くなると、一致を列挙するときの正規表現エンジンの処理時間が長くなります指数関数的に増加します。

ちなみに、最後の点についてですが、例のs/a/A/gケースは単純な例であり、入力として収集したい実際のスクリプトではない可能性が高いと思いますが、慣れるのに時間がかかるかもしれません。y///g1つの文字を別の文字に置換することが多い場合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の動作を次のように定義しています。

  • N

    • \n埋め込まれた\newlineを使用して追加されたマテリアルを元のマテリアルから分離し、入力の次の行(終了ewlineを除く)をパターンスペースに追加します。現在の行番号が変更されることに注意してください。

    • 次の入力行がない場合、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
}

同じことが真のために保持しているrwtai、とc (そしておそらく私は、現時点では忘れてることをさらにいくつか)。ほとんどすべての場合、それらはまた書かれるかもしれません:

sed -e :arbitrary_label_name -e b\ to_arbitary_label_name -e \
    "//{ do arbitrary list of commands" -e \}

...ここで、新しい-executionステートメントは\newline区切り文字を表します。したがって、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

... testコマンドは、ほとんどのsedコマンドと同様に、リターンレジスタを更新するためにラインサイクルに依存しているため、ここでラインサイクルはほとんどの作業を実行できます。これは、ファイルを丸呑みするときに行うもう1つのトレードオフです。ラインサイクルは二度と更新されないため、多くのテストが異常に動作します。

上記のコマンドは、単純なテストを行って、読み取った内容を確認するだけなので、入力に到達するリスクがありません。H古いすべての行は、ホールドスペースに追加されますが、ラインが一致した場合/foo/、それは上書きされh、古い空間に。次にバッファーがx変更され、s///バッファーの内容が//最後にアドレス指定されたパターンと一致する場合は、条件付き更新が試行されます。つまり//s/\n/&/3p、ホールドスペースの3番目の改行をそれ自体で置き換え、ホールドスペースが現在一致している場合に結果を出力しようとします/foo/。これがt成功した場合、スクリプトはnot deleteラベルに分岐しlます。これにより、フックが実行され、スクリプトが終了します。

/foo/ただし、ホールドスペースで両方と3番目の改行を一致させることができない場合、一致しない//!g場合/foo/はバッファーを上書きします。一致する場合は、\newlineが一致しない場合にバッファーを上書きします(これにより/foo/、自体)。このわずかな微妙なテストにより、バッファーがnoの長いストレッチで不必要にいっぱいになるのを防ぎ/foo/、入力が積み重ならないため、プロセスがスムーズに保たれるようにします。no /foo/または//s/\n/&/3pfailの場合に続いて、バッファーが再びスワップされ、最後を除くすべての行が削除されます。

最後の最後の行$!dは、トップダウンsedスクリプトを作成して複数のケースを簡単に処理する方法の簡単なデモです。一般的な方法で、最も一般的なものから始めて最も具体的なものに向かって不要なケースを削除する場合、エッジケースは、他の必要なデータを使用してスクリプトの最後まで単純に通過できるため、より簡単に処理できます。必要なデータだけを残して、すべてをラップします。ただし、このようなエッジケースをクローズドループからフェッチする必要がある場合は、はるかに困難です。

そして、これが私が言わなければならない最後のことです:あなたが本当にファイル全体をプルしなければならないなら、あなたはそれを行うためにラインサイクルに依存することによって少し少ない仕事をするために立つことができます。通常、Nextとnextを先読みに使用します- ラインサイクルのに進むためです。ループ内に閉じたループを冗長的に実装するのでsedはなく-とにかくラインサイクルは単純な読み取りループであるため-目的が無差別に入力を収集することだけである場合は、おそらく簡単です。

sed 'H;1h;$!d;x;...'

...ファイル全体を収集するか、バストしようとします。


N最後の行の動作に関する注意事項...

テストできるツールがないので、編集したファイルが次のリードスルーのスクリプトファイルであるN場合、読み取りとインプレース編集の動作が異なることを考慮してください。


1
無条件をH最初に置くのは素敵です。
jthill、2015年

@mikeservご協力ありがとうございます。ラインサイクルを維持することで潜在的なメリットを確認できますが、どのように作業を減らすことができますか?
dicktyr 2015年

@dicktyrよく、:a;$!{N;ba}上で述べたように構文にはいくつかのショートカットがあります-慣れていないシステムで正規表現を実行しようとすると、長期的には標準形式を使用する方が簡単です。しかし、それは私が意図したものではありませんでした:閉じたループを実装します-必要に応じて、分岐して(不要なデータをプルーニングして)サイクルを発生させるだけでは、途中で簡単にループに入ることができません。それはトップダウンのようなものです-すべてsedが行うことはそれがやったことの直接の結果です。見方が違うかもしれませんが、試してみると、スクリプトの方が簡単な場合があります。
mikeserv、2015

11

Nコマンドはパターンマッチの前$!(最後の行ではない)に来るため失敗し、sedは作業を行う前に終了します。

N

パターンスペースに改行を追加してから、次の入力行をパターンスペースに追加します。入力がない場合、sedはコマンドを処理せずに終了します

これは、パターンの後にNbコマンドをグループ化するだけで、簡単に修正して、単一行入力でも機能するように(そして、いずれにせよ、より明確にすることができます)できます。

sed ':a;$!{N;ba}; [commands...]'

次のように機能します。

  1. :a 「a」という名前のラベルを作成します
  2. $! 最終行でない場合は、
  3. Nパターンスペースに次の行を追加し(または次の行がない場合は終了baし)、ラベル 'a'を分岐(移動)します

残念ながら、(GNU拡張に依存しているため)移植性はありませんが、次の代替(@mikeservが推奨)は移植性があります。

sed 'H;1h;$!d;x; [commands...]'

他の場所で情報が見つからなかったため、ここに投稿しました:a;N;$!ba;。他の人が広範囲にわたる問題を回避できるように、それを利用可能にしたいと考えました。
dicktyr 2015年

投稿ありがとうございます!自分の答えを受け入れることも問題ないことを覚えておいてください。システムが許可するまで、しばらく待つ必要があります。
terdon
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.