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


243

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

$ 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

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


ここでスマートな回避策:unix.stackexchange.com/a/445666/61742。もちろん、パフォーマティックではありません!ニーズに応じて置換を実行するためのその他の適切なオプションは、awk、perl、pythonです。他にもたくさんありますが、awkはさまざまなLinuxディストリビューションで最も普遍的だと思います(たとえば)。ありがとう!
エドゥアルドルチオ

回答:


235

最も簡単な呼び出しでのsed、それが持っている1つのすなわちパターン・スペース内のテキストの行を。\n入力からの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                   

167
今私を撃ちます。最悪の構文!
ギリ

53
これは素晴らしい説明ですが、@ Giliに同意する傾向があります。
gatoatigrado

11
あなたのチートシートはそれをすべて持っています。
konsolebox 14

3
tここでコマンドを使用するためにラベルは必要ありません。ラベルが指定されていない場合、デフォルトでスクリプトの最後に分岐します。したがってsed '/^a test$/{$!{N;s/^a test\nPlease do not$/not a test\nBe/;t;P;D}}' alpha.txt、すべての状況でコマンドとまったく同じです。もちろん、この特定のファイルについてsed '/test/{N;s/.*/not a test\nBe/}' alpha.txtも同じことを行いますが、私の最初の例はすべての可能なファイルについて論理的に同等です。また\n、置換文字列では改行が生成されないことに注意してください。バックスラッシュ `\`の後に実際の改行が必要です。
ワイルドカード

9
構文はGNU固有であることに注意してください(RHSの#コマンドは前のコマンドと分離されていません)。GNU を使用すると、NUL区切りのレコードを使用することもできます(テキスト(定義上NULを含まない)の場合は、入力全体を丸lurみします)。\nssed-z
ステファンシャゼル

181

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はファイル全体を丸lurみします。詳細については、perldoc perlrunを参照してください


3
ありがとう!複数行の作業では、perlが勝ちます!ファイルをその場で変更するには、 `$ perl -pi -e 's / bar / baz /' fileA`を使用しました。
ニコラス・トーリーコットレル

3
元のポスターがsedawkまたはperlを使用して返信を求めて表示することは非常に一般的です。私はそれが話題になっていないと思うので、申し訳ありませんが、私はマイナス1を解雇しました。
ローピ

68
+1&ロベルトに反対。多くの場合、より良い方法の無知のために具体的に表現された質問。実質的なコンテキストの違いがない場合(ここのように)、最適なソリューションは、少なくとも質問固有のものと同じくらいのプロファイルを取得する必要があります。
地理理論

56
sed上記の答えは、Perlの答えが話題になっていることを証明していると思います。
reinierpost

7
少し簡単:「-p0e」を使用すると、「-0777」は不要です。unix.stackexchange.com/a/181215/197502
ワイデンリンデ

96

私は、\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'

誰もが知っていない場合は、\nUNIXの行末、ある\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'

11
この巧妙なハックのために+1!特に有用なのは、編集中のファイルの内容について完全に確信がない限り、一時的に改行を置き換えるためにエキゾチックなシンボルを使用することに関するアドバイスです。
L0j1k

これは、代わりにOS X上で書かれたとして、1はすべてのインスタンスを置き換える必要が動作しない\rの引数にsed持ちます$(printf '\r')
アベボパレボップ

@abeboparebop:素晴らしい発見!👍または、homebrewを使用してGNU sedをインストールします
。stackoverflow.com/a

@abeboparebop、OSX上で、あなただけ追加する必要がある$変換からそれを防ぐために、sedの文字列の前\rr。短い例:sed $'s/\r/~/'。完全な例:cat alpha.txt | tr '\n' '\r' | sed $'s/a test\rPlease do not/not a test\rBe/' | tr '\r' '\n'
wisbucky

40

すべてを考慮すると、ファイル全体をゴブリングするのが最速の方法かもしれません。

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

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コマンドは、特別な条項を持っている「スタート次のサイクルを。」、これは防ぎg最後を除くすべての行で実行されているから。

各コマンドの意味に関して:

  • 最初hに続いてH、各ラインのコピーのSは、に入力線前記sed「sは空間を保持します。(任意のテキストバッファを考えてください。)
  • その後、d各行を破棄して、これらの行が出力に書き込まれないようにします。ただし、ホールドスペースは保持されます。
  • 最後に、最後の行でgホールドスペースからすべての行の累積を復元し、sed入力全体に対して正規表現を実行できるようにします(一度に1行ずつではなく)。に一致し\nます。

38

sed複数行の操作を管理するために3つのコマンドがありますNDP(にそれらを比較ノーマル ndおよびp)。

この場合、パターンの最初の行を照合しN、2番目の行をパターンスペースに追加してsから、置換に使用できます。

何かのようなもの:

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

2
これはすごい!受け入れられた答えよりも単純であり、依然として効果的です。
jeyk

そして、ホールドスペース(関連するすべてのものGHx...)を。sコマンドを使用して、パターンスペースにさらに行を追加することもできます。
ステファンシャゼル


このソリューションは、次のケースでは機能しません。「これは\ nテスト\ naテスト\ n
心配

@ mug896複数のNコマンドが必要になる可能性が最も高い
-loa_in_

15

できますが、難しいです。別のツールに切り替えることをお勧めします。置換したいテキストのどの部分にも一致しない正規表現がある場合、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'

1
perlコマンドをファイルに適用する方法は?
sebix 16

2
@sebix perl -0777 -pe '…' <input-file >output-file。所定の場所でファイルを変更するには、perl -0777 -i -pe '…' filename
Gilles

3
GNU sed-zオプションも参照してください(2012年にその回答が投稿された後に追加されました)seq 10 | sed -z 's/4\n5/a\nb/'
ステファンシャゼル

7

これは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行の置換に「-n」の代わりに「-i」を使用しました。これが必要なことであり、偶然にも、それはaskerの例にもあるからです。
ナゲブ

5
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構文をお勧めします。


問題は、sedが/ a test /と/ Please do not /の間のすべての最終テキストを置き換えることです...:(
noonex

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

入力時にウィンドウを少し広げてください。

とても簡単です。標準の代替に加えて; あなただけの必要$!NPDここに。


4

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行区切り文字をあなたのものに置き換え、その逆も同様です。)


2

これは、OS Xで動作するように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')


1
一方でprintf '\r'(またはecho -e '\r')正常に動作しない、あなただけのシェルの構文を使用できることに注意してください$'\r'エスケープリテラルを参照すること。例えば、echo hi$'\n'there間に改行をエコーしますhithere。各バックスラッシュように同様に、あなたは文字列全体をラップすることができます\ :それ以降の文字をエスケープしますecho $'hi\nthere'
Dejayクレイトン

1

私は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

GNUにsed-z、OPが適用しようとした構文を使用できるオプションがあります。(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-separated)行の一致を確実に置換するにはg、グローバル置換(たとえばs/.../.../g)にフラグを使用することを忘れないでください。


クレジット: @stéphane-chazelas は、上記のコメントで最初に-zについて言及しました。


0

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

ただし、ループを使用して、sed がパターン空間に2つの連続した行を保持するようにする方法があります。

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

同じ量の行が置き換えられると仮定しています。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.