SED normal ifadesiyle açgözlü olmayan eşleşme (perl'leri taklit edin. *?)


22

Ben sedilk ABve (dahil) ilk oluşumu arasında bir dizede bir şey yerine kullanmak istiyorum .ACXXX

İçin örneğin , bu dizesi (Bu dize sadece bir test içindir):

ssABteAstACABnnACss

ve ben buna benzer çıktı istiyoruz: ssXXXABnnACss.


Bunu şu şekilde yaptım perl:

$ echo 'ssABteAstACABnnACss' | perl -pe 's/AB.*?AC/XXX/'
ssXXXABnnACss

ama bunu uygulamak istiyorum sed. Aşağıdakiler (Perl uyumlu normal ifadeyi kullanarak) çalışmaz:

$ echo 'ssABteAstACABnnACss' | sed -re 's/AB.*?AC/XXX/'
ssXXXss

2
Bu mantıklı değil. Perl'de çalışan bir çözümünüz var, ancak Sed'i kullanmak istiyorsunuz, neden?
Kusalananda

Yanıtlar:


16

Sed regexes en uzun maçla eşleşir. Sed'in açgözlü olmayan eşdeğeri yoktur.

Açıkçası yapmak istediğimiz şey

  1. AB,
    ardından
  2. başka bir şey herhangi bir miktar AC,
    ardından
  3. AC

Ne yazık ki, sed# 2 yapamazsınız - en azından çok karakterli normal bir ifade için değil. Tabii ki, tek karakterlik düzenli ifade gibi için @(hatta [123]), yapabileceğimiz [^@]*ya [^123]*. Ve biz tüm oluşumlarını değiştirerek en sed sınırlamaları aşmanın böylece ACetmek @ve daha sonra aramaya

  1. AB,
    ardından
  2. başka bir şey herhangi bir sayı @,
    ardından
  3. @

bunun gibi:

sed 's/AC/@/g; s/AB[^@]*@/XXX/; s/@/AC/g'

Son bölüm, eşleşmeyen eşsiz örneklerini olarak @değiştirir AC.

Ancak, elbette, bu pervasız bir yaklaşımdır, çünkü giriş zaten @karakter içerebilir , bu yüzden onları eşleştirerek yanlış pozitifler alabiliriz. Bununla birlikte, hiçbir kabuk değişkeninin içinde bir NUL ( \x00) karakteri bulunmayacağından , NUL yukarıdaki çözüm yerine kullanmak için muhtemelen iyi bir karakterdir @:

$ echo 'ssABteAstACABnnACss' | sed 's/AC/\x00/g; s/AB[^\x00]*\x00/XXX/; s/\x00/AC/g'
ssXXXABnnACss

NUL kullanımı GNU sed gerektirir. (GNU özelliklerinin etkinleştirildiğinden emin olmak için kullanıcının kabuk değişkenini POSIXLY_CORRECT ayarlamamış olması gerekir.)

-zÇıktı gibi NUL ile ayrılmış girdiyi işlemek için GNU bayrağını kullanarak sed kullanıyorsanız find ... -print0, NUL desen boşluğunda olmayacak ve NUL burada ikame için iyi bir seçim olacaktır.

NUL bir bash değişkeninde olamazsa da, bunu bir printfkomuta dahil etmek mümkündür . Giriş dizeniz NUL dahil herhangi bir karakter içeriyorsa, akıllı bir kaçış yöntemi ekleyen Stéphane Chazelas'ın cevabına bakın .


Uzun bir açıklama eklemek için cevabınızı düzenledim; kırpmaktan veya geri almaktan çekinmeyin.
G-Man, 'Monica'yı Yeniden Başlat' diyor

@ G-Man Bu mükemmel bir açıklama! Çok iyi yapmışsın. Teşekkür ederim.
John1024

Şunları yapabilirsiniz echoveya printfbash gayet bir `\ 000' (ya da giriş bir dosyadan gelebilir). Ancak genel olarak, bir metin dizisinin elbette NUL'ları yoktur.
ilkkachu

@ilkkachu Bu konuda haklısın. Ne yazmalıydım hiçbir kabuk değişkeni veya parametre NUL içerebilir. Yanıt güncellendi.
John1024

Eğer değişirse bu bir sürü daha güvenli olmaz ACiçin AC@tekrar geri ve?
Michael Vehrs

7

Bazı seduygulamaların buna desteği vardır. ssedPCRE modu vardır:

ssed -R 's/AB.*?AC/XXX/g'

AT&T ast sed , artırılmış normal ifadeleri kullanırken birleşme ve olumsuzlamaya sahiptir :

sed -A 's/AB(.*&(.*AC.*)!)AC/XXX/g'

Portably, bu tekniği kullanabilirsiniz: bitiş dizesini (burada AC) başlangıç ​​veya bitiş dizesinde ( :burada olduğu gibi ) oluşmayan tek bir karakterle değiştirin, böylece yapabilirsiniz s/AB[^:]*://ve bu karakter girişte görünebilirse , başlangıç ​​ve bitiş dizeleriyle çakışmayan bir kaçış mekanizması kullanın.

Bir örnek:

sed 's/_/_u/g; # use _ as the escape character, escape it
     s/:/_c/g; # escape our replacement character
     s/AC/:/g; # replace the end string
     s/AB[^:]*:/XXX/g; # actual replacement
     s/:/AC/g; # restore the remaining end strings
     s/_c/:/g; # revert escaping
     s/_u/_/g'

GNU sedile bir yaklaşım, satırsonu yerine yeni karakter kullanmaktır. Çünkü sedher seferinde süreçler bir satır, satır asla desen uzayda oluşur, bu nedenle tek yapabilirsiniz:

sed 's/AC/\n/g;s/AB[^\n]*\n/XXX/g;s/\n/AC/g'

Bu genellikle diğer seduygulamalarla çalışmaz çünkü desteklemezler [^\n]. GNU ile sedPOSIX uyumluluğunun etkinleştirilmediğinden emin olmalısınız (POSIXLY_CORRECT ortam değişkeninde olduğu gibi).


6

Hayır, sed regex'lerinin açgözlü olmayan eşleşmesi yoktur.

Sen ilk geçtiği tüm metni eşleşebilir AC“içermeyen bir şey kullanarak ACtakip” ACPerl en aynıdır yapar .*?AC. Mesele şu ki, “içermeyen herhangi bir şey AC” düzenli bir ifade olarak kolayca ifade edilemez: her zaman düzenli bir ifadenin olumsuzlanmasını tanıyan düzenli bir ifade vardır, fakat olumsuzluk düzenli ifadesi hızla karmaşıklaşır. Ve taşınabilir sed'de bu mümkün değildir, çünkü olumsuzluk normal ifadesi genişletilmiş düzenli ifadelerde (örn. Awk) mevcut olan ancak taşınabilir temel düzenli ifadelerde olmayan bir değişimin gruplandırılmasını gerektirir. GNU sed gibi bazı sed sürümlerinde, BRE'nin mümkün olan tüm düzenli ifadeleri ifade etmesini sağlayan uzantılar vardır.

sed 's/AB\([^A]*\|A[^C]\)*A*AC/XXX/'

Normal ifadeyi reddetmenin zorluğu nedeniyle, bu iyi bir genelleme değildir. Bunun yerine çizgiyi geçici olarak dönüştürmektir. Bazı sed uygulamalarında, yeni satırları işaretleyici olarak kullanabilirsiniz, çünkü bunlar bir giriş satırında görünemezler (ve birden çok işaretçiye ihtiyacınız varsa, yeni satırı ve ardından değişen bir karakteri kullanın).

sed -e 's/AC/\
&/g' -e 's/AB[^\
]*\nAC/XXX/' -e 's/\n//g'

Ancak, ters eğik çizgi yeni satırının bazı sed sürümleriyle karakter setinde çalışmadığına dikkat edin. Özellikle bu, gömülü olmayan Linux üzerinde sed uygulaması olan GNU sed'de çalışmaz; GNU sed'de \nbunun yerine şunları kullanabilirsiniz :

sed -e 's/AC/\
&/g' -e 's/AB[^\n]*\nAC/XXX/' -e 's/\n//g'

Bu özel durumda, ACbirinciyi bir satırsonu ile değiştirmek yeterlidir . Yukarıda sunduğum yaklaşım daha geneldir.

Sed'deki daha güçlü bir yaklaşım, çizgiyi tutma alanına kaydetmek, çizginin ilk “ilginç” kısmı hariç tümünü kaldırmak, tutma alanını ve desen alanını değiştirmek veya desen alanını tutma alanına eklemek ve tekrarlamaktır. Ancak, bu kadar karmaşık şeyler yapmaya başlarsanız, gerçekten awk'a geçmeyi düşünmelisiniz. Awk'ın da açgözlü olmayan eşleşmesi yoktur, ancak bir dizeyi bölebilir ve parçaları değişkenlere kaydedebilirsiniz.


@ilkkachu Hayır, değil. s/\n//gtüm yeni satırları kaldırır.
Gilles 'SO- kötü olmayı kes'

asdf. Doğru, benim hatam.
ilkkachu

3

sed - Christoph Sieghart tarafından açgözlü olmayan eşleştirme

Sed'de açgözlü olmayan eşleşme elde etmenin hilesi, maçı sonlandıran karakter hariç tüm karakterlerle eşleşmektir. Biliyorum, beyinsiz, ama değerli dakikaları boşa harcadım ve kabuk komut dosyaları sonuçta hızlı ve kolay olmalı. Yani başka birinin buna ihtiyacı olabilirse:

Açgözlü eşleme

% echo "<b>foo</b>bar" | sed 's/<.*>//g'
bar

Açgözlü olmayan eşleme

% echo "<b>foo</b>bar" | sed 's/<[^>]*>//g'
foobar


3
“Beyinsiz” terimi belirsizdir. Bu durumda, sizin (veya Christoph Sieghart) bunu düşündüğünüz açık değildir. Eğer (sıfır-of-the daha-of ifadesi takip eder söz konusu sorunu çözmek için nasıl gösterdi olsaydı Özellikle, güzel olurdu tarafından birden fazla karakteri ) . Bu cevabın bu durumda iyi çalışmadığını görebilirsiniz.
Scott

Tavşan deliği, ilk bakışta bana göründüğünden çok daha derin. Haklısınız, bu geçici çözüm çok karakterli normal ifade için iyi çalışmıyor.
gresolio

0

Sizin durumunuzda bu şekilde kapanış karakterini reddedebilirsiniz:

echo 'ssABteAstACABnnACss' | sed 's/AB[^C]*AC/XXX/'

2
Soru “İlk arasında bir şey değiştirmek istiyorum, diyor ABve ilk geçtiği ACile XXX...” ve veren ssABteAstACABnnACssbir şekilde örnek girişi. Bu cevap bu örnek için geçerlidir , ancak genel olarak soruyu cevaplamaz. Örneğin ssABteCstACABnnACss, çıktıyı da vermelidir aaXXXABnnACss, ancak komutunuz bu satırı değişmeden geçirir.
G-Man, 'Monica'yı Yeniden Başlat' diyor

0

Çözüm oldukça basit. .*açgözlüdür, ama kesinlikle açgözlü değildir. ssABteAstACABnnACssNormal ifadeyle eşleştirmeyi düşünün AB.*AC. ACİzler .*zorunluluk aslında bir maçımız var. Sorun şu ki .*, açgözlü olduğu için, bir sonraki ilkinden ziyade sonuncuylaAC eşleşecektir . İlk yukarı yediği değişmezi ise regexp içinde ssABteAstACABnn içinde son uyan AC ss. Bunu engellemek için, sadece ilk yerine bir şeyle saçma ikinci birinden ve her şeyden ayırmak için. AC.*ACACAC

echo ssABteAstACABnnACss | sed 's/AC/-foobar-/; s/AB.*-foobar-/XXX/'
ssXXXABnnACss

Açgözlü .*şimdi dibinde durur -foobar-içinde ssABteAst-foobar-ABnnACssbaşkası yok çünkü -foobar-bundan daha -foobar-ve regexp'in -foobar- GEREKİR bir maç var. Önceki sorun, normal ifadenin ACiki eşleşmesinin .*olmasıydı , ancak açgözlü olduğu için son eşleşme ACseçildi. Bununla birlikte -foobar-, sadece bir maç mümkündür ve bu maç .*bunun kesinlikle açgözlü olmadığını kanıtlar . Otobüs durağı, aşağıdaki normal ifadenin geri kalanı için .*yalnızca bir eşleşme kaldığında gerçekleşir .*.

Yanlış ile değiştirileceği için ACbirinciden önce görünürse bu çözümün başarısız olacağını unutmayın . Örneğin, birinci sonra yerine, olur ; bu nedenle, bir eşleşme bulunamaz . Ancak, dizi her zaman ... AB ... AC ... AB ... AC ... ise, bu çözüm başarılı olacaktır.ABAC-foobar-sedACssABteAstACABnnACss-foobar-ssABteAstACABnnACssAB.*-foobar-


0

Bir alternatif, dizeyi değiştirerek açgözlü eşleşmeyi istemektir

echo "ssABtCeCAstACABnnACss" | rev | sed -E "s/(.*)CA.*BA(.*)/\1CA+-+-+-+-BA\2/" | rev

Dizeyi revtersine çevirmek, eşleşme kriterlerinizi tersine çevirmek sed, normal şekilde kullanmak ve daha sonra sonucu tersine çevirmek için kullanın ....

ssAB-+-+-+-+ACABnnACss
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.