改行文字を含まない入力ストリームに正規表現変換を適用するために「sed」または他の手段を使用できますか?

改行文字を含まない入力ストリームに正規表現変換を適用するために「sed」または他の手段を使用できますか?

sed式を含むシェルパイプラインがあります。

source | sed -e 's:xxxxx:yyyyy:g' | sink

動作しますが、sed行全体に適用されるという点で潜在的な欠陥があります。これはsink、改行文字が送信されるまで何も表示されないことを意味します。sourceソースが改行を送信しない場合、問題が発生します。

シェルはbashですが関係ないことを願っています。文字列の合計は正規表現にするxxxxxことができ、キャプチャグループを使用していくつかのコンテンツを 。yyyyyxy

sed改行文字を含まない入力ストリームに正規表現変換を使用または適用することは可能ですか?

Rubyでフィルタを書いてこの問題を解決しましたが、コードを書くのではなく既存のツールを使用できるかどうか疑問に思いました。

ベストアンサー1

実際に考えると、優先的に影響を与えることができるのはみんな可能:

source |
tr '\n<' '<\n'  |
paste -sd\\n - -|
sed  -e'/^[0-9]\{1,\}>/!{$!H;1h;$!d'\
     -e\} -e'x;y/\n</<\n/;s//<&/'   \
     -ew\ /dev/fd/1 |
filter ... | sink

これにより、まずインストリームコンテンツが中断されます。連合国<条件付きで for\nと after の両方を置き換えるホームフレーズ適切に交換してください。言及した区切り文字は次のとおりです。これは必須です。いいえ単一文字(改行文字で)したがって、単純な翻訳だけでは編集内容を保証するには不十分です。最初フローを定義します。つまり、言及した編集内容が必要な場合があります。たとえば、適用されると理解されるグループのキャプチャやその他の状況に合った一致があります。記録- エンドポイントを確認するまでは安定して完了できません。


バッファリングされていない


sed正規表現一致入力が初めて表示される場合にのみバッファリング<[0-9]+>まずすべて翻訳<ewlines\nまたはその逆にして、入力を前のスペースに1行ずつ積み重ねますsedH^[0-9]\{1,\}>一致します。

ただし、チャンクバッファ出力は4kbチャンク以上のパイプに書き込むときに行われますtrpaste

この問題を処理する2つのバージョンもあります。

sol1(){
    {   cat; printf '\n\4\n'; } |
    {   dd obs=1 cbs=512 conv=sync,unblock \
        <"$(pts;stty eol \";cat <&3 >&4&)" 
    }   3<&0 <&- <>/dev/ptmx 2>/dev/null 4>&0 |
    sed  -ne'/<[0-9]\{1,\}>/!{H;$!d' -e\} \
          -e'x;s/\n//g;w /dev/fd/1'
}

これにより、すべての入力がptyにプッシュされ、ddそれから読み取りが設定されます。 fdのロックptsを解除して割り当てるには、他の質問の小さなCプログラムを使用してくださいdd。上記の場合、区切りはカーネルによって行われます。 pty 行規則は次"のように構成されます。stty eolchar - 出力から削除されません。eofcharはそうです。ただし、ptyバッファをdd各発生にプッシュし、それを満たします。read()ddすべてread()Sまず、出力バッファの末尾を512文字のスペースで埋め、次のすべての末尾のスペースを単一の改行文字に圧縮します。

以下は、最後の行がブロックされる問題を解決する修正版です。

sol1_5(){
    {   cat; printf '\n\4\n'; } |
    {   dd ibs=16k obs=2 cbs=4k conv=sync,unblock <"$(pts
        stty raw isig quit \^- susp \^- min 1 time 2
        cat  <&3 >&4&)" 
    }   3<&0 <&- <>/dev/ptmx 2>/dev/null 4>&0 |
    sed -ne's/<[0-9]\{1,\}>/\n&/g;/./w /dev/fd/1'
}

trバッファリングを解放し、まったく異なる別のバージョンは次のとおりですpaste

sol2(){
    stdbuf -o0 tr '\n<' '<\n'  |
    stdbuf -o0 paste -sd\\n - -|
    sed  -ue'/^[0-9]\{1,\}>/!{$!H;1h;$!d'\
         -e\} -e'x;y/\n</<\n/;s//<&/'
}

サンプルデータを使用して両方の方法をテストした。

for sol in 1 2
do  printf '<37> Jul 28 10:40:47 127.0.0.1 time="2015-07-28 10:40:47" msg="LOGOUT User admin logged out on TELNET (10.0.200.1)"<37> Jul 28 10:45:58 127.0.0.1 time="2015-07-28 10:45:58" msg="LOGIN User admin logged in on TELNET (10.0.200.1)"<37> Jul 28 10:40:47 127.0.0.1 time="2015-07-28 10:40:47" msg="LOGOUT User admin logged out on TELNET (10.0.200.1)"<37> Jul 28 10:45:58 127.0.0.1 time="2015-07-28 10:45:58" msg="LOGIN User admin logged in on TELNET (10.0.200.1)"' |
   cat - /dev/tty | "sol$sol" | cat

どちらの場合も、最初の3行はすぐに印刷されますが、4行目はバッファに残ります。sedバッファは次の行の先頭を見つけるまで印刷されないため、EOFまで入力の後に1行を保持します。緊急CTRL+D印刷されました。


<37> Jul 28 10:40:47 127.0.0.1 time="2015-07-28 10:40:47" msg="LOGOUT User admin logged out on TELNET (10.0.200.1)"
<37> Jul 28 10:45:58 127.0.0.1 time="2015-07-28 10:45:58" msg="LOGIN User admin logged in on TELNET (10.0.200.1)"
<37> Jul 28 10:40:47 127.0.0.1 time="2015-07-28 10:40:47" msg="LOGOUT User admin logged out on TELNET (10.0.200.1)"
<37> Jul 28 10:45:58 127.0.0.1 time="2015-07-28 10:45:58" msg="LOGIN User admin logged in on TELNET (10.0.200.1)"

しかし、sol1_5まったく異なるアプローチを使用します。入力を分離するために文字コンテキストに依存するのではなく、各write()4k以下のバイトが少なくとも1つの完全なコンテキストを表す必要があると思うので、適切な改行だと思うものを各バイトに追加し、出力をすぐにフラッシュします。

仕組みは設定することです。stty minそしてtimeddptyの値です。設定するとmin > 0 そして time > 0非標準端末装置では、端末は少なくとも受信されるまで読み取りをブロックします。min文字を入力してから次までブロックし続けます。time10分の1秒が過ぎました。そのようにしてすべてに頼ることができればwrite()端末にはバイトが多すぎ、完了するのに時間がかかりすぎます。個人的にログ書き込みに4kと0.2秒がかなり公正な仮定だと思います。その後、入力を読み取り、出力をフラッシュできます。同期的に

したがって、sol1_54行すべてがすぐに印刷されます。


sedスクリプト


これは実際には非常に簡単な方法であり、複数の文字区切り文字sed(デフォルトでは単一文字のレコードのみを区切る)改行を処理するためにかなり一般的に調整できます。

  1. 区切り文字パターンで最初に表示されるすべての文字を改行文字に変換し、すべての改行文字をその文字に変換します。

    • 以下の複雑さの一部:ストリームの末尾に改行文字があることを確認してください。

    • tr '\n<' '<\n' | paste -sd\\n - -

  2. 区切り文字パターンの残りの部分に新しい改行で区切られた入力を取得します。ただし、行の先頭に発生した場合にのみ該当します。

    • これは簡単であるだけでなく、非常に効率的です。入力行の最初の数文字だけを確認してください。sed作業はほとんどまたはまったく必要ありません。

    • /^[0-9]\{1,\}>/

  3. H一致しない行のコピーを古いスペースに追加して!削除dします。ただし、一致しない行の場合は、x編集および保存バッファを変更して、現在のパターンスペースが完全に区切られた最後のレコードのすべての内容になるようにします。予約済みスペースには、最初に区切られたシーケンスの一部のみが含まれます。

    • 最も複雑なのは、最初の入力ラインと最後の入力ラインに注意を払う必要があることです。ここでの複雑さはsed基本的な効率に由来する。実際には、バッファごとに1つのレコードを処理できます。

    • 何の理由もなく最初の行に追加の行を挿入したくないので、\nこの場合はh古いスペースを追加する代わりに上書きする必要があります。H

    • 皆さんは! いいえ H空または保持バッファがないため、最後の行を削除またはd削除してください。$これ以上スキャンする入力はありませんが、最後に保存した記録を処理する必要があります。

    • /.../!{$!H;1h;$!d;};x

  4. s///これで、完全に区切られたコンテキストを復元するために高価な代替正規表現を適用するのではなく、独自の音訳機能を使用して、保存されているすべての挿入されたewline文字を区切り文字の最初の文字に一度に効率的に置き換えることができます。sedy///\n

    • y/\n</<\n/
  5. <最後に、パターン空間の先頭に新しい行を挿入するだけです。\n挿入する必要がある行は、印刷時に最後のバッファサイクルの最後にすでに追加されているためです。

    • これを行う最も効率的な方法は、//入力行でテストしたのと同じ正規表現を再利用することです。これはsedあなたがする必要があることから逃れるかもしれません。regcomp()単一の正規表現をコンパイルして繰り返しregexec()全体の流れを安定して描写するために、同じ自動装置が繰り返し実行される。

    • s//<&/

これで、この出力ストリームを通常の行で区切られたテキストファイルとして\n扱うことができます。

テスト

printf '%s\n' the string \
              "<9>more $(printf %050s|tr ' ' '<') string" \
              and \<9\> "<more<string and <9> more<string" |
tr '<\n' '\n<'   |
paste -sd\\n - - |
sed  -e'/^[0-9]\{1,\}>/!{$!H;1h;$!d' \
     -e\} -e'x;y/\n</<\n/;s//<&/'

the
string

<9>more <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< string
and

<9>
<more<string and 
<9> more<string

これで、文字列に編集内容を適用することが目標である場合は、次のように説明できます。^<[0-9]+>(^(<[0-9]+>))*まあ、この時点ではおそらく2番目のフィルタは必要ありません。なぜなら、それはsed小さなスクリプトの終わりに印刷される前にパターンスペースが表すのとまったく同じです(\newlinesなど)。

前の例の修正バージョンを再利用して...

文字列>データ

printf '%s\n' the string \
              "<1>more $(printf %050s|tr ' ' '<') string" \
              and \<2\> "<more<string and <3> more<string" |
tr '<\n' '\n<'   |
paste -sd\\n - - |
sed  -e'/^[0-9]\{1,\}>/!{$!H;1h;$!d' \
     -e\} -e'x;y/\n</<\n/;s//<&/'  \
     -e'/^<[2-9][0-9]*>/s/string/data/g'

the
string

<1>more <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< string
and

<2>
<more<data and 
<3> more<data

おすすめ記事