sed式を含むシェルパイプラインがあります。
source | sed -e 's:xxxxx:yyyyy:g' | sink
動作しますが、sed
行全体に適用されるという点で潜在的な欠陥があります。これはsink
、改行文字が送信されるまで何も表示されないことを意味します。source
ソースが改行を送信しない場合、問題が発生します。
シェルはbash
ですが関係ないことを願っています。文字列の合計は正規表現にするxxxxx
ことができ、キャプチャグループを使用していくつかのコンテンツを 。yyyyy
x
y
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行ずつ積み重ねますsed
。H
^[0-9]\{1,\}>
一致します。
ただし、チャンクバッファ出力は4kbチャンク以上のパイプに書き込むときに行われますtr
。paste
この問題を処理する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
eol
char - 出力から削除されません。eof
charはそうです。ただし、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
そしてtime
dd
ptyの値です。設定するとmin > 0
そして time > 0
非標準端末装置では、端末は少なくとも受信されるまで読み取りをブロックします。min
文字を入力してから次までブロックし続けます。time
10分の1秒が過ぎました。そのようにしてすべてに頼ることができればwrite()
端末にはバイトが多すぎ、完了するのに時間がかかりすぎます。個人的にログ書き込みに4kと0.2秒がかなり公正な仮定だと思います。その後、入力を読み取り、出力をフラッシュできます。同期的に。
したがって、sol1_5
4行すべてがすぐに印刷されます。
sedスクリプト
これは実際には非常に簡単な方法であり、複数の文字区切り文字sed
(デフォルトでは単一文字のレコードのみを区切る)改行を処理するためにかなり一般的に調整できます。
区切り文字パターンで最初に表示されるすべての文字を改行文字に変換し、すべての改行文字をその文字に変換します。
以下の複雑さの一部:ストリームの末尾に改行文字があることを確認してください。
tr '\n<' '<\n' | paste -sd\\n - -
区切り文字パターンの残りの部分に新しい改行で区切られた入力を取得します。ただし、行の先頭に発生した場合にのみ該当します。
これは簡単であるだけでなく、非常に効率的です。入力行の最初の数文字だけを確認してください。
sed
作業はほとんどまたはまったく必要ありません。/^[0-9]\{1,\}>/
H
一致しない行のコピーを古いスペースに追加して!
削除d
します。ただし、一致しない行の場合は、x
編集および保存バッファを変更して、現在のパターンスペースが完全に区切られた最後のレコードのすべての内容になるようにします。予約済みスペースには、最初に区切られたシーケンスの一部のみが含まれます。最も複雑なのは、最初の入力ラインと最後の入力ラインに注意を払う必要があることです。ここでの複雑さは
sed
基本的な効率に由来する。実際には、バッファごとに1つのレコードを処理できます。何の理由もなく最初の行に追加の行を挿入したくないので、
\n
この場合はh
古いスペースを追加する代わりに上書きする必要があります。H
皆さんは
!
いいえH
空または保持バッファがないため、最後の行を削除またはd
削除してください。$
これ以上スキャンする入力はありませんが、最後に保存した記録を処理する必要があります。/.../!{$!H;1h;$!d;};x
s///
これで、完全に区切られたコンテキストを復元するために高価な代替正規表現を適用するのではなく、独自の音訳機能を使用して、保存されているすべての挿入されたewline文字を区切り文字の最初の文字に一度に効率的に置き換えることができます。sed
y///
\n
y/\n</<\n/
<
最後に、パターン空間の先頭に新しい行を挿入するだけです。\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
小さなスクリプトの終わりに印刷される前にパターンスペースが表すのとまったく同じです(\n
ewlinesなど)。
前の例の修正バージョンを再利用して...
文字列>データ
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
答え2
プログラムが端末に書き込むと、バッファはすべての改行文字でフラッシュされますが、プログラムを使用できますバッファリング解除 (一部のディストリビューションでは、このコマンドがstdbufであることに注意してください。)
これを試してください。
unbuffer source | sed -e 's:xxxxx:yyyyy:g' | sink
答え3
少なくとも、GNU sedは最終改行なしで入力を処理できます(最後の不完全な行が渡されると、最終改行なしで出力が生成されます)。 Unixのテキストファイルは定義上新しい行で終わる必要があり(空でない場合)、sedはテキストユーティリティであるため、テキスト以外の入力に対するこれらの寛大さは保証されません。
sedはラインを変換するように設計されているため、ほとんどの実装では、変換を適用する前に、特にその入力ラインに対応する出力を生成する前に、入力ライン全体をメモリに読み込むことが期待されます。
sedを使用してこの入力を簡単に処理するには、パターンと一致しないか、代替テキストとして生成されていますが、入力で頻繁に発生する文字を選択しxxxxx
てくださいyyyyy
。 sedを呼び出して改行文字に変換するか、その逆に変換します。
source | tr ':\n' '\n:' | sed -e 's:foo:bar:g' | tr ':\n' '\n:' | sink
選択できる良いキャラクターがなければ、sedはおそらく役に立ちません、Rubyは合理的な選択です。
答え4
この問題を解決するために、Rubyで小さなスクリプトを実装しました。使用方法は次のとおりです。
source | myscript.rb | sink
これがソースです
$stdout.sync # no outbound buffering by Ruby
buf=''
$stdin.each_char do |c|
if buf.length>0 || c=='<' # buffering starts when '<' received
buf << c # and continues until flushed
buf.gsub!(/(<\d+>)/,"\n\\1") if (c == '>') # regex transform matching buffer
unless (buf =~ /<\d*$/) # flush buffer when regex fails
STDOUT << buf
buf.replace '' # empty buffer stops buffering
end
else
STDOUT << c; # unbuffered output
end
$stdout.flush # no buffering, please!
end
Rubyの専門家はこの問題を改善するかもしれませんが、ここに問題を解決した迅速な「汚いハッキング」があります。
デフォルトでは、一度に1文字ずつstdinを読み、一致する最初の文字があることを確認します。つまり、<
文字が見つからない場合はすぐにstdinに書き込みます。一致する場合は、バッファに書き込んでから、バッファの内容を正規表現で有効な区切り文字(後<
にゼロ以上の数字が続く)が一致しない場合を除き、バッファをフラッシュしてバッファリングを停止します。バッファリング時に a を取得すると、>
正規表現で変換を実行します。
修正する
上記のスクリプトは機能しますが、改行文字を待っている場合、ダウンストリームプロセスは入力をバッファリングできます。これは、入力の最後の行がダウンストリームパイプラインにかかる可能性があることを意味します。以下のバージョンは非ブロック読み取りを使用し、入力ブロックに改行を挿入します。
STDOUT.sync # no outbound buffering by Ruby
buf=''
def read_from_stdin()
last=''
while true
begin
c = STDIN.read_nonblock(1) # read 1 character; don't block
rescue Errno::EWOULDBLOCK # exception if nothing to read
yield "\n" unless last=="\n" # send a newline if prior character wasn't
IO.select([STDIN]) # block (wait for input)
retry # go back to 'begin' again
end
yield last=c # remember and send read character
end
end
read_from_stdin do |c|
if buf.length>0 || c=='<' # buffering starts when '<' received
buf << c # and continues until flushed
buf.gsub!(/(<\d+>)/,"\n\\1") if (c == '>') # regex transform matching buffer
unless (buf =~ /<\d*$/) # flush buffer when regex fails
STDOUT << buf
buf.replace '' # empty buffer stops buffering
end
else
STDOUT << c; # unbuffered output
end
STDOUT.flush # no buffering, please!
end