web-dev-qa-db-ja.com

sedを使用して複数行の文字列を置き換えるにはどうすればよいですか?

sedを使用して置換するパターンに\nを追加すると、パターンが一致しないことに気付きました。例:

$ cat > alpha.txt
This is
a test
Please do not
be alarmed

$ sed -i'.original' 's/a test\nPlease do not/not a test\nBe/' alpha.txt

$ diff alpha.txt{,.original}

$ # No differences printed out

これを機能させるにはどうすればよいですか?

274

sedの最も簡単な呼び出しでは、oneのテキスト行がありますパターンスペース、すなわち。入力からの\n区切りテキストの1行。パターンスペースの1行に\n...がありません。そのため、正規表現は何も検出しません。

パターンスペースに複数の行を読み込んで、驚くほどうまく操作できますが、通常よりも多くの労力が必要です。Sedには、このタイプのことを可能にするコマンドのセットがあります...ここに sedのコマンドの概要 へのリンクです。これは私が見つけた中で最高のものであり、私を転がしてもらいました。

ただし、sedのマイクロコマンドを使い始めたら、「ワンライナー」のアイデアを忘れてください。構造化されたプログラムのように、それを感じるまでレイアウトするのは便利です...驚くほどシンプルで、それと同様に珍しいものです。あなたはそれをテキスト編集の「アセンブラ言語」と考えることができます。

概要:sedを単純なものに使用しますが、それより少し多いかもしれませんが、一般的に、1行で作業するだけではなく、ほとんどの人は他のものを好みます...
他の誰かに他のことを提案させます..最良の選択が何であるか本当にわかりません(sedを使用しますが、これはPerlを十分に理解していないためです)。


sed '/^a test$/{
       $!{ N        # append the next line when not on the last line
         s/^a test\nPlease do not$/not a test\nBe/
                    # now test for a successful substitution, otherwise
                    #+  unpaired "a test" lines would be mis-handled
         t sub-yes  # branch_on_substitute (goto label :sub-yes)
         :sub-not   # a label (not essential; here to self document)
                    # if no substituion, print only the first line
         P          # pattern_first_line_print
         D          # pattern_ltrunc(line+nl)_top/cycle
         :sub-yes   # a label (the goto target of the 't' branch)
                    # fall through to final auto-pattern_print (2 lines)
       }    
     }' alpha.txt  

これは同じスクリプトですが、明らかに読みやすく、扱いにくいものに凝縮されていますが、一部のスクリプトはワンライナー

sed '/^a test$/{$!{N;s/^a test\nPlease do not$/not a test\nBe/;ty;P;D;:y}}' alpha.txt

これが私のコマンド「チートシート」です

:  # label
=  # line_number
a  # append_text_to_stdout_after_flush
b  # branch_unconditional             
c  # range_change                     
d  # pattern_delete_top/cycle          
D  # pattern_ltrunc(line+nl)_top/cycle 
g  # pattern=hold                      
G  # pattern+=nl+hold                  
h  # hold=pattern                      
H  # hold+=nl+pattern                  
i  # insert_text_to_stdout_now         
l  # pattern_list                       
n  # pattern_flush=nextline_continue   
N  # pattern+=nl+nextline              
p  # pattern_print                     
P  # pattern_first_line_print          
q  # flush_quit                        
r  # append_file_to_stdout_after_flush 
s  # substitute                                          
t  # branch_on_substitute              
w  # append_pattern_to_file_now         
x  # swap_pattern_and_hold             
y  # transform_chars                   
255
Peter.O

Perlの代わりにsedを使用:

$ Perl -0777 -i.original -pe 's/a test\nPlease do not/not a test\nBe/igs' alpha.txt
$ diff alpha.txt{,.original}
2,3c2,3
< not a test
< Be
---
> a test
> Please do not

-pi -eは、標準の「置換」コマンドラインシーケンスであり、-0777を指定すると、Perlはファイル全体を丸めます。詳細は perldoc perlrun を参照してください。

196
codehead

\n記号を他の記号に置き換えてから、通常どおりに機能する方がよいと思います。

例えば機能しないソースコード:

cat alpha.txt | sed -e 's/a test\nPlease do not/not a test\nBe/'

次のように変更できます。

cat alpha.txt | tr '\n' '\r' | sed -e 's/a test\rPlease do not/not a test\rBe/'  | tr '\r' '\n'

誰かが知らない場合、\nはUNIXの行末、\r\n-ウィンドウ、\r-クラシックMac OSです。通常のUNIXテキストは\r記号を使用しないため、この場合は安全に使用できます。

エキゾチックなシンボルを使用して、一時的に\ nを置き換えることもできます。例として-\ f(フォームフィードシンボル)。さらに多くの記号 ここ を見つけることができます。

cat alpha.txt | tr '\n' '\f' | sed -e 's/a test\fPlease do not/not a test\fBe/'  | tr '\f' '\n'
108
xara

すべてのことを考慮して、ファイル全体を取得するが最も速い方法です。

基本的な構文は次のとおりです:

sed -e '1h;2,$H;$!d;g' -e 's/__YOUR_REGEX_GOES_HERE__...'

ファイルが途方もなく大きい場合、ファイル全体をむさぼり食うことはオプションではないかもしれません。そのような場合、ここで提供される他の回答は、小さなメモリフットプリントで動作することが保証されているカスタマイズされたソリューションを提供します。

他のすべてのハックとスラッシュの状況では、単に-e '1h;2,$H;$!d;g'の後に元のsed正規表現引数を追加するだけで、ほとんどの作業が完了します。

例えば.

$ echo -e "Dog\nFox\nCat\nSnake\n" | sed -e '1h;2,$H;$!d;g' -re 's/([^\n]*)\n([^\n]*)\n/Quick \2\nLazy \1\n/g'
Quick Fox
Lazy Dog
Quick Snake
Lazy Cat

-e '1h;2,$H;$!d;g'は何をしますか?

12,$$!の部分は、直接続くコマンドが実行される行を制限する行指定子です。

  • 1:最初の行のみ
  • 2,$:2番目から始まるすべての行
  • $!:最後以外のすべての行

展開すると、これはN行入力の各行で発生することです。

  1: h, d
  2: H, d
  3: H, d
  .
  .
N-2: H, d
N-1: H, d
  N: H, g

gコマンドには行指定子が指定されていませんが、先行するdコマンドには特別な句 "Start next cycle。 "、そしてこれはgが最後を除くすべての行で実行されるのを防ぎます。

各コマンドの意味については:

  • 各行で最初のhの後にHsが続くと、入力された行がsedhold space。 (任意のテキストバッファを考えてください。)
  • その後、dは各行を破棄して、これらの行が出力に書き込まれないようにします。ただし、hold spaceは保持されます。
  • 最後に、最後の行で、ghold spaceからすべての行の累積を復元し、sed (一度に1行ずつではなく)入力全体で正規表現を実行できるため、\nsで照合できます。
52
antak

sedには、複数行の操作を管理するための3つのコマンドがあります: NDおよびP(これらをnormalndおよびp)。

この場合、パターンの1行目を照合し、Nを使用して2行目をpattern spaceに追加してから、 s置換を実行します。

何かのようなもの:

/a test$/{
  N
  s/a test\nPlease do not/not a test\nBe/
}
42
andcoz

できますが難しいです 。別のツールに切り替えることをお勧めします。置換するテキストのどの部分にも一致しない正規表現がある場合は、GNU awkのawkレコードセパレータとして使用できます。

awk -v RS='a' '{gsub(/hello/, "world"); print}'

検索文字列に連続する改行が2つない場合は、awkの「段落モード」を使用できます(1つ以上の空白行でレコードを区切ります)。

awk -v RS='' '{gsub(/hello/, "world"); print}'

簡単な解決策は、Perlを使用してファイルをメモリに完全にロードすることです。

Perl -0777 -pe 's/hello/world/g'

これは2行のマッチングのsedソリューションだと思います。

sed -n '$!N;s@a test\nPlease do not@not a test\nBe@;P;D' alpha.txt

3行の一致が必要な場合は...

sed -n '1{$!N};$!N;s@aaa\nbbb\nccc@xxx\nyyy\nzzz@;P;D'

4行の一致が必要な場合は...

sed -n '1{$!N;$!N};$!N;s@ ... @ ... @;P;D'

「s」コマンドの交換部品が行を縮小する場合、このように少し複雑になります

# aaa\nbbb\nccc shrink to one line "xxx"

sed -n '1{$!N};$!N;/aaa\nbbb\nccc/{s@@xxx@;$!N;$!N};P;D'

補充部分が線を伸ばすと、このように少し複雑になります

# aaa\nbbb\nccc grow to five lines vvv\nwww\nxxx\nyyy\nzzz

sed -n '1{$!N};$!N;/aaa\nbbb\nccc/{s@@vvv\nwww\nxxx\nyyy\nzzz@;P;s/.*\n//M;P;s/.*\n//M};P;D'

この2番目の方法は、通常の小さなサイズのテキストファイルを単純にコピーアンドペーストしたものです(シェルスクリプトファイルが必要です)。

#!/bin/bash

# copy & paste content that you want to substitute

AA=$( cat <<\EOF | sed -z -e 's#\([][^$*\.#]\)#\\\1#g' -e 's#\n#\\n#g'
a test
Please do not
EOF
)

BB=$( cat <<\EOF | sed -z -e 's#\([&\#]\)#\\\1#g' -e 's#\n#\\n#g'
not a test
Be
EOF
)

sed -z -i 's#'"${AA}"'#'"${BB}"'#g' *.txt   # apply to all *.txt files
10
mug896

GNU sedには、OPが適用しようとした構文を使用できる-zオプションがあります。 ( manページ

例:

$ cat alpha.txt
This is
a test
Please do not
be alarmed
$ sed -z 's/a test\nPlease do not\nbe/not a test\nBe/' -i alpha.txt
$ cat alpha.txt
This is
not a test
Be alarmed

注意:^および$を使用すると、NUL文字で区切られた行の最初と最後に一致します( \nではありません)。また、すべての(\nで区切られた)行が一致するように置換するには、グローバル置換(s/.../.../gなど)にgフラグを使用することを忘れないでください。


クレジット:@stéphane-chazelas上記のコメントで最初に言及された-z

7
Peterino
sed -i'.original' '/a test/,/Please do not/c not a test \nBe' alpha.txt

ここで/a test/,/Please do not/は(複数行の)テキストのブロックと見なされ、c変更コマンド の後に新しいテキストnot a test \nBeが続きます

置き換えるテキストが非常に長い場合は、 ex 構文をお勧めします。

5
gibies
sed -e'$!N;s/^\(a test\n\)Please do not be$/not \1Be/;P;D' <in >out

入力時にウィンドウを少し広げるだけです。

とても簡単です。標準的な置換に加えて; $!NPDはここにあります。

4
mikeserv

Perlとは別に、ストリーム(およびファイルも)の複数行編集のための一般的で便利なアプローチは次のとおりです。

たとえば、好きなように新しいUNIQUE行区切りを作成します。

$ S=__ABC__                     # simple
$ S=__$RANDOM$RANDOM$RANDOM__   # better
$ S=$(openssl Rand -hex 16)     # ultimate

次に、sedコマンド(またはその他のツール)で、次のように\ nを$ {S}に置き換えます

$ cat file.txt | awk 1 ORS=$S |  sed -e "s/a test${S}Please do not/not a test\nBe/" | awk 1 RS=$S > file_new.txt

(awkは、ASCII行区切り文字を自分のものに、またはその逆に置き換えます。)

4
guest

これは、OSAで動作するようにxaraの賢い回答を少し変更したものです(私は10.10を使用しています)。

_cat alpha.txt | tr '\n' '\r' | sed -e 's/a test$(printf '\r')Please do not/not a test$(printf '\r')Be/'  | tr '\r' '\n'
_

_\r_を明示的に使用する代わりに、$(printf '\r')を使用する必要があります。

2
abeboparebop

Sedを使用してファイルに数行のHTMLを追加したかった(そして、ここで終了した)。通常、私はPerlを使用するだけですが、sed、bash、その他はほとんどないボックスを使用していました。文字列を1行に変更し、bash/sedに\ t\nを補間させると、すべてがうまくいったことがわかりました。

HTML_FILE='a.html' #contains an anchor in the form <a name="nchor" />
BASH_STRING_A='apples'
BASH_STRING_B='bananas'
INSERT="\t<li>$BASH_STRING_A<\/li>\n\t<li>$BASH_STRING_B<\/li>\n<a name=\"nchor\"\/>"
sed -i "s/<a name=\"nchor"\/>/$INSERT/" $HTML_FILE

二重引用符とスラッシュをエスケープする関数があればよりクリーンになりますが、時々抽象化が時間の泥棒です。

1
Alexx Roche

Sedは改行で入力を中断します。ループごとに1行のみを保持します。
したがって、パターンスペースに含まれていない場合、\n(改行)を照合する方法はありません。

ただし、ループを使用してパターンスペースにsed keep two連続する行を作成する方法があります。

sed 'N;l;P;D' alpha.txt

NとPの間に必要な処理を追加します(lを置き換えます)。

この場合(2行):

$ sed 'N;s/a test\nPlease do not/not a test\nBe/;P;D' alpha.txt
This is
not a test
Be
be alarmed

または、3行の場合:

$ sed -n '1{$!N};$!N;s@a test\nPlease do not\nbe@not a test\nDo\nBe@;P;D' alpha.txt 
This is
not a test
Do
Be alarmed

同じ数の行が置き換えられることを前提としています。

0
Isaac