Çok satırlı bir dizeyi değiştirmek için sed'i nasıl kullanabilirim?


243

\nKullanmak yerine bir kalıba eklersem sedeşleşmediğini fark ettim . Örnek:

$ 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

Bunun işe yaramasını nasıl sağlayabilirim?


Buradaki akıllı geçici çözüm: unix.stackexchange.com/a/445666/61742 . Tabii ki bu performans değil! İhtiyaçlarınıza göre bir değiştirme yapmak için diğer iyi seçenekler awk, perl ve python olabilir. Daha pek çokları var ama inanıyorum ki, çeşitli Linux dağıtımlarında en evrensel olanı awk. Teşekkürler!
Eduardo Lucio

Yanıtlar:


235

En basit çağrı olarak sed , sahip olduğu tek yani desen alanı, içinde metin satırı. \nGirişden 1 satır ayrılmış metin. Desen uzayındaki tek satırda yok \n... Bu yüzden regex'iniz bir şey bulamıyor.

Desen alanına birden çok satır okuyabilir ve işleri şaşırtıcı derecede iyi yönetebilirsiniz, ancak normalden daha fazla çaba sarf edersiniz. Sed, bu tür bir şeye izin veren bir dizi komut içerir ... İşte sed için bir Komut Özeti bağlantısı . Bulduğum en iyisiydi ve beni yuvarlattı.

Ancak sed'in mikro komutlarını kullanmaya başladığınızda "bir liner" fikrini unutun. Bunu hissedinceye kadar yapılandırılmış bir program gibi düzenlemek yararlıdır ... Şaşırtıcı derecede basit ve eşit derecede sıradışı. Bunu metin düzenlemenin "assembler dili" olarak düşünebilirsiniz.

Özet: basit şeyler için sed'i kullanın ve belki biraz daha fazla, ama genel olarak, tek bir çizgiyle çalışmanın ötesine geçtiğinde, çoğu insan başka bir şeyi tercih eder ...
Bir başkasının başka bir şeyi önermesine izin vereceğim .. Ben gerçekten en iyi seçimin ne olacağından emin değilim (sed kullanırdım, ama bunun sebebi perl'i yeterince iyi tanımıyorum.)


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  

İşte aynı senaryo, okunması ve üzerinde çalışılması zor olan şeylere yoğunlaşmış, ancak bazıları şüpheli bir şekilde bir liner olarak adlandırılacaktı.

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

İşte benim komutum "cheat sheet"

:  # 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
Vur beni şimdi. Şimdiye kadar en kötü sözdizimi!
Gili

53
Bu harika bir açıklama, ama @Gili ile aynı fikirdeyim.
gatoatigrado

11
Hile sayfanızda hepsi var.
konsolebox

3
tBuradaki komutu kullanmak için bir etikete ihtiyacınız yoktur; bir etiket verilmediğinde betiğin sonuna kadar dallanma varsayılandır. Yani sed '/^a test$/{$!{N;s/^a test\nPlease do not$/not a test\nBe/;t;P;D}}' alpha.txther koşulda emrinizle tamamen aynı. Elbette bu özel dosya için sed '/test/{N;s/.*/not a test\nBe/}' alpha.txtde aynı şeyi yapar, ancak ilk örneğim tüm olası dosyalar için mantıksal olarak eşdeğerdir . Ayrıca \n, yeni bir dizgede yeni satır oluşturmadığını unutmayın ; Bunu yapmak için bir ters eğik çizgi \ \ `yi izlemeniz gerekir.
Wildcard

9
Sözdiziminin GNU'ya özgü olduğuna dikkat edin ( #komut bir öncekinden \nRHS'de ayrılmaz s). GNU ile NUL ayrılmış kayıtları sedkullanmak -ziçin de kullanabilirsiniz (ve eğer metinse (girişte NUL içermiyorsa) bütün girdiyi karıştırırsınız).
Stéphane Chazelas

181

Yerine perlkullanın 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 -estandart "yerinde değiştir" komut satırı dizinizdir ve -0777 perl'nin tüm dosyaları susturmasına neden olur. Daha fazla bilgi edinmek için perldoc perlrun'a bakın .


3
Teşekkürler! Çok satırlı işler için perl kazanır! Dosyayı yerinde değiştirmek için '$ perl -pi -e' / s / bar / baz / 'fileA` kullandım.
Nicholas Tolley Cottrell

3
Orijinal posterin sorması sedve awk veya perl kullanarak cevap yazması çok yaygındır . Sanırım konuyla ilgili değil, peki, üzgünüm ama eksi bir ateşledim.
Rho Phi,

68
+1 ve Roberto'ya katılmıyorum. Genelde sorular daha iyi yöntemlerin cehaletinden dolayı özellikle ifade edilmiştir. Önemli bir bağlamsal farklılık olmadığı zaman (burada olduğu gibi), optimal çözümler en azından soruya özel olanlar kadar profil almalıdır.
geotheory

56
Bence sedyukarıdaki cevap Perl cevabının konuyla ilgili olduğunu kanıtlıyor.
reinierpost

7
Biraz daha kolay: "-p0e" ile "-0777" gerekli değildir. unix.stackexchange.com/a/181215/197502
Weidenrinde

96

Bence, \nsembolün yerine başka bir sembol koymak ve daha sonra her zamanki gibi çalışmak daha iyidir :

örneğin çalışmayan kaynak kodu:

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

değiştirilebilir:

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

Hiç kimse bilmiyorsa, \nUNIX satır biter, \r\n- windows, \r- klasik Mac OS. Normal UNIX metni \rsembol kullanmaz , bu nedenle bu durumda kullanmak güvenlidir.

Geçici olarak değiştirmek için bazı egzotik semboller de kullanabilirsiniz \ n. Örnek olarak - \ f (form besleme simgesi). Burada daha fazla sembol bulabilirsiniz .

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

11
Bu akıllı kesmek için +1! Özellikle yararlı olan, düzenlediğiniz dosyanın içeriğinden kesinlikle emin olmadığınız sürece, yeni çizgiyi geçici olarak değiştirmek için egzotik bir sembol kullanma önerisidir.
L0j1k

Bu Yerine OS X'te yazıldığı gibi, bir tüm örneklerini değiştirmek gerekiyor çalışmıyor \riçin argüman sedile $(printf '\r').
abeboparebop

@ abeboparebop: büyük bulmak! 👍 alternatif olarak, homebrew kullanarak GNU sed'i kurun: stackoverflow.com/a/30005262
ssc

@abeboparebop, OSX üzerinde, sadece eklemeniz gerekir $dönüştürme engellemek için sed dize önce \rbir etmek r. Kısa bir örnek: sed $'s/\r/~/'. Tam örnek:cat alpha.txt | tr '\n' '\r' | sed $'s/a test\rPlease do not/not a test\rBe/' | tr '\r' '\n'
Wisbucky

40

Her şey göz önünde bulundurulursa, dosyanın tamamını yuvarlayarak gitmek en hızlı yol olabilir.

Temel sözdizimi aşağıdaki gibidir:

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

Unutmayın, dosya çok büyükse, dosyanın tamamını yuvarlayarak bir seçenek olmayabilir. Bu gibi durumlarda, burada verilen diğer yanıtlar, küçük bir bellek ayak izi üzerinde çalışması garanti edilen özelleştirilmiş çözümler sunar.

Diğer tüm kesmek ve eğik çizgi durumları için, yalnızca -e '1h;2,$H;$!d;g'orijinal sedregex argümanınız tarafından takip edilen hazırlıkları yapmak işi hemen hemen alır.

Örneğin

$ 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

Ne yapar -e '1h;2,$H;$!d;g'?

1, 2,$, $!Parça hattı doğrudan aşağıdaki komutu çalışır çizgiler bu sınırı şartnamecilere vardır.

  • 1: Sadece ilk satır
  • 2,$: İkinci satırdan başlayan tüm satırlar
  • $!: Sonuncusu dışındaki her satır

Böylece genişledi, N satır girişinin her satırında olan şey bu.

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

gKomut Satır belirtici verilmez, ancak önceki dkomut özel bir hüküm yok " Başlat sonraki döngüsü. " Ve bu engeller gson dışındaki tüm hatlarda çalışan.

Her komutun anlamı gelince:

  • Birinci hve ardından H, her bir satır kopyalarda s girdi hatları bahsedilen sedsitesindeki tutma alanı . (İsteğe bağlı metin arabelleğini düşünün.)
  • Daha sonra, dbu satırların çıktılara yazılmasını önlemek için her satırı atar. Ancak tutma alanı korunur.
  • Son olarak, son satırda, gher satırın tutma alanından birikmesini geri yükler, böylece sedregex'ini tüm girdi üzerinde çalıştırabilir (her seferinde bir satırdan ziyade), ve böylece \ns üzerinde maç .

38

sed: Üç komutları çok hatlı operasyonlarını yönetmek zorundadır N, Dve P(onları karşılaştırmak , normal n , dve p).

Bu durumda, kalıbınızın ilk çizgisini eşleştirebilir, Nikinci çizgiyi kalıp boşluğuna eklemek için kullanabilirsiniz ve sonra syerine koymak için kullanabilirsiniz .

Gibi bir şey:

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

2
Bu harika! Kabul edilen cevaptan daha basit ve hala etkili.
20'de jeyk

Ve yer tutmaya (ilgili tüm olanları G, H, x...). sKomut ile desen alanına daha fazla satır eklenebilir .
Stéphane Chazelas


bu çözüm şu durumda çalışmıyor: "Bu \ na test \ na test \ n Lütfen alarma
geçmeyin

@ mug896 muhtemelen birden fazla Nkomuta ihtiyacınız var
loa_in_

15

Yapabilirsin ama bu zor . Farklı bir araca geçmenizi öneririm. Değiştirmek istediğiniz metnin hiçbir bölümüne uymayan normal bir ifade varsa, bunu GNU awk'de bir awk kayıt ayırıcısı olarak kullanabilirsiniz.

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

Arama dizginizde hiç iki ardışık yeni satır yoksa, awk'in "paragraf modunu" (kayıtları ayrı bir veya daha fazla boş satır) kullanabilirsiniz.

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

Perl kullanmak ve dosyayı tamamen belleğe yüklemek kolay bir çözümdür.

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

1
Perl komutu bir dosyaya nasıl uygulanır?
sebix

2
@sebix perl -0777 -pe '…' <input-file >output-file. Bir dosyayı yerinde değiştirmek için,perl -0777 -i -pe '…' filename
Gilles

3
GNU bakınız sed'ın -z(yani cevap yayınlanmıştır sonra 2012 yılında eklendi) seçeneğini: seq 10 | sed -z 's/4\n5/a\nb/'.
Stéphane Chazelas

7

Bence bu 2 satırın eşleşmesi için sed çözüm.

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

Eşleşen 3 satır istiyorsanız o zaman ...

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

Eşleşen 4 satır istiyorsanız o zaman ...

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

"S" komutundaki yedek parça satırları daraltırsa, bunun gibi biraz daha karmaşık

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

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

Eğer çözülme kısmı çizgileri büyürse, bunun gibi biraz daha karmaşık

# 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'

Bu zirveye çıkmalı! İki satırlı ikame için sadece "-n" yerine "-i" kullandım, çünkü ihtiyacım olan şey buydu ve bu arada, bu da istemcinin örneğinde.
Nagev

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

İşte /a test/,/Please do not/(çok satırlı) bir metin bloğu olarak kabul edilir c, change komutunu takip eden yeni metinnot a test \nBe

Değiştirilecek metnin çok uzun olması durumunda, eski sözdizimi öneririm .


oops problem şu ki, sed a / a test / ve / Lütfen arasındakileri değiştirmeyecek tüm metni değiştirecek ... :(
noonex 24:16

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

Sadece penceredeki girişi biraz genişlet.

Bu oldukça kolay. Standart ikamenin yanı sıra; Yalnızca ihtiyaç $!N, Pve Dburada.


4

Perl dışında, akışlar (ve dosyalar da) için çok satırlı düzenleme için genel ve kullanışlı bir yaklaşım şudur:

İlk önce, örneğin, istediğiniz gibi bazı yeni UNIQUE çizgi ayırıcı oluşturun.

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

Sonra sed komutunda (veya başka bir araçta) \ n yerine $ {S} yazsın.

$ 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 satır ayırıcıyı sizinkiyle değiştirir ve bunun tersi de geçerlidir.)


2

Bu, OS X üzerinde çalışması için xara'nın akıllı cevabının küçük bir modifikasyonudur (10.10 kullanıyorum):

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'

Açıkça kullanmak yerine kullanmak \rzorundasınız $(printf '\r').


1
İken printf '\r'(veya echo -e '\r') düzgün çalışması gerekmez, sadece kabuk sözdizimini kullanabilirsiniz unutmayın $'\r'kaçan değişmezleri başvurmak için. Örneğin, ve echo hi$'\n'therearasında yeni bir satır ekleyecektir . Benzer şekilde, tüm dizgiyi, her ters eğik çizginin sonraki karakterinden hithere\ echo $'hi\nthere'
çıkmasını sağlayacak şekilde sarabilirsiniz

1

Sed kullanarak bir dosyaya birkaç satır HTML eklemek istedim (ve burada sona erdi). Normalde sadece perl kullanırdım, ama başım ağrıyordu, başka bir şey yoktu. Eğer dizeyi tek bir satıra çevirirsem ve bash / sed \ t \ n enterpolasyonu yaptıysam her şeyin işe yaradığını gördüm:

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

Çift tırnaktan ve öne eğik çizgilerden kaçmak için bir işleve sahip olmak daha temiz olacaktır, ancak bazen soyutlama zamanın hırsızıdır.


1

GNU sed, -zOP'nin uygulamaya çalıştığı sözdizimini kullanmaya izin veren bir seçeneğe sahiptir . ( man sayfa )

Örnek:

$ 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

Dikkat: Kullanırsanız ^ve $şimdi NUL karakteriyle sınırlandırılmış satırların başına ve sonuna denk gelirlerse (değil \n). Ve tüm (- \nayrılmış) hatlarınızdaki eşleşmelerin yerine koyulmasını sağlamak için gbayrağı genel yer değiştirmelerde (örn. s/.../.../g) Kullanmayı unutmayın .


Kredi: @ stéphane-chazelas yukarıda belirtilen bir yorumda -z'den önce bahsetti.


0

Sed yeni hatlarda girişi keser. Her döngü için sadece bir satır tutar.
Bu nedenle \n, desen alanı içermiyorsa (yeni bir çizgiyle) eşleşmenin bir yolu yoktur .

Yine de , döngü kullanarak art arda iki çizgiyi desen uzayında ardışık iki çizgiyi tutturmanın bir yolu vardır :

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

N ve P arasında gereken herhangi bir işlemi ekleyin (yerine l).

Bu durumda (2 satır):

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

Veya üç satır için:

$ 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

Bu, aynı miktarda çizginin değiştirileceğini varsayar.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.