Sed kullanarak iki dizenin tüm oluşumlarını temiz bir şekilde değiştirin


13

StringA ve StringB'nin birden çok örneğini içeren bir dosyam olduğunu varsayalım. Tüm StringA oluşumları StringB ile değiştirmek (ve aynı anda) tüm StringB oluşumları StringA ile değiştirmek istiyorum.

Şu anda böyle bir şey yapıyorum

cat file.txt | sed 's/StringB/StringC/g' | sed 's/StringA/StringB/g' | sed 's/StringC/StringA/g'

Bu yaklaşımla ilgili sorun, dosyada StringC oluşmadığını varsayar. Bu pratikte bir sorun olmasa da, bu çözüm hala kirli hissediyor - yani, daha fazla unix sihri öğrenmek için bir fırsat gibi geliyor. :)

Yanıtlar:


11

Eğer StringBve StringAaynı giriş hattı üzerinde görünemez, o zaman yedek bir şekilde gerçekleştirmek ve orada ilk hiçbir oluşumları vardı dize aranır eğer sadece diğer yolu denemek sed söyleyebilir.

<file.txt sed -e 's/StringA/StringB/g' -e t -e 's/StringB/StringA/g'

Genel durumda, sed'de kolay bir yöntem olduğunu düşünmüyorum. Bu arada, not şartname eğer belirsiz olduğundan StringAve StringBçakışabilir. İşte her iki dizenin en soldaki yerine geçen ve yinelenen bir Perl çözümü.

<file.txt perl -pe 'BEGIN {%r = ("StringA" => "StringB", "StringB" => "StringA")}
                    s/(StringA|StringB)/$r{$1}/ge'

POSIX araçlarına sadık kalmak istiyorsanız, awk gitmenin yoludur. Awk, genel parametreli değiştirmeler için bir ilkeye sahip değildir, bu yüzden kendinizinkini yuvarlamanız gerekir.

<file.txt awk '{
    while (match($0, /StringA|StringB/)) {
        printf "%s", substr($0, 1, RSTART-1);
        $0 = substr($0, RSTART);
        printf "%s", /^StringA/ ? "StringB" : "StringA";
        $0 = substr($0, 1+RLENGTH)
    }
    print
}'

İlk komutu çalıştırdığımda sed bana söyler sed: can't read s/StringB/StringA/g: No such file or directory. -e t PATTERNİyi anlaşılmamış gibi görünüyor .
Gyscos

1
@Gyscos -eİkinci skomuttan önce bir eksik vardı . Cevabımı düzelttim.
Gilles 'SO- kötü olmayı bırak

8

Şu anda
.............. gibi bir şey yapıyorum.
Bu yaklaşımla ilgili sorun, StringC dosyada oluşmaz varsayar olmasıdır.

Bence yaklaşımınız iyi, sadece bir dize yerine başka bir şey kullanmalısınız, bir satırda (desen alanında) oluşamayan bir şey. En iyi aday \newline.
Normalde, desen alanındaki hiçbir giriş satırı bu karakteri içermez, böylece bir dosyanın tüm oluşumlarını THISve THATbir dosyada yer değiştirmek için şunları çalıştırabilirsiniz:

sed 's/THIS/\
/g
s/THAT/THIS/g
s/\n/THAT/g' infile

veya sediniz \nRHS'de de destekliyorsa :

sed 's/THIS/\n/g;s/THAT/THIS/g;s/\n/THAT/g' infile

1
Bu güzel. Biraz ağladım. RHS yeni satırlarını yapmanın başka bir yolu da kabuk değişkenleridir - sedbelirli kaçışları destekleyip desteklememek, önceden birkaç makro hazırlarsanız çok daha az önemli hale gelir. Gibi set /THIS /THAT "$(printf \\n/)"; sed "s/$2/\\$4g;s/$3$2/g;s/\\n$3/g"- burada aptalca, kuşkusuz, ama diğer bazı zamanlarda çok daha mantıklı - özellikle char sınıfları ve benzeri için.
mikeserv

Buna ne dersin dostum. Orada bir cevap bile var. Yorum yaptığımda orada mıydı? Ben sadece yakın zamanda düzenlenen listede (belki) pop up gördüm ve üst cevabın üst satırı ( sadece gömülü olmayan linux umursanız, sanırım) biraz kapalı idi . Orada Gilles'in önerisini tercih ediyorum - uzun süredir devam etmedikçe sed, sürekli sabit çatal çatalı ekinduva kabusu. Farklı bir notta - pasteBir gün boyunca oynuyorum . Ayrıştırıcı benzeri bir seçenek yaptım column. Sadece giriş dizeleri ve dizeleri şeyler için tire çizer.
mikeserv

3

Ben iki kelime takas için bir "nonce" dize kullanmak için mükemmel olduğunu düşünüyorum. Daha genel bir çözüm istiyorsanız, aşağıdakileri yapabilirsiniz:

sed 's/_/__/g; s/you/x_x/g; s/me/you/g; s/x_x/me/g; s/__/_/g' <<<"say you say me"

Bu,

say me say you

x_x"X_x" dizeleriniz varsa değiştirmekten kaçınmak için iki ek değiştirmeye ihtiyacınız olduğunu unutmayın . Ama bu bile awkbenim için çözümden daha basit görünüyor .


Asker'in zaten yaptıklarını söylediği şey bu gibi görünüyor.
roaima

1
Evet, ilk bakışta gözden kaçırdım (düzenleme geçmişine bakın), ancak verilen çözüm farklıdır çünkü yedek dize (burada "x_x") orijinal dizede gerçekleştiğinde bile çalışır, dolayısıyla daha geneldir.
David Ongaro

Akıllı, ama bir yakalama var. StringA veya StringB içeriyorsa _, kişinin _kendisini (başka bir karakter seçin) veya zahmetli dizeyi ( s/_/__/gönceden gerçekleştirin , daha iyi görünüyor) ayarlaması gerekir. Çözümünüz, rasgele dizeleri takas etmek için körü körüne uygulanamaz.
Kamil Maciorowski

@KamilMaciorowski Ne demek istediğini anlamıyorum? Aslında s/_/__/gönceden bir uygulama yapıyorum . Belki de başarısız olan bir testcase göster.
David Ongaro

@KamilMaciorowski ah sanırım şimdi anlıyorum. Yedek dizelerin kendisinde a varsa _, yani y_ouile değiştirmeyi söyleyin me. Evet, bunun farkında olmalı y__ouve ifadeye konmalıdır. Değiştirmeyi giriş parametreleri olarak alan bir komut dosyasının da bunu dikkate alması gerekir.
David Ongaro
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.