Tek bir geçişte birden fazla dizeyi değiştirme


12

Ortak Unix araçlarıyla (bash, sed, awk, belki perl) bir şablon dosyasında yer tutucu dizeleri somut değerlerle değiştirmenin bir yolunu arıyorum. Değişimin tek bir geçişte yapılması önemlidir, yani zaten taranan / değiştirilen şey başka bir değişim için dikkate alınmamalıdır. Örneğin, bu iki deneme başarısız olur:

echo "AB" | awk '{gsub("A","B");gsub("B","A");print}'
>> AA

echo "AB" | sed 's/A/B/g;s/B/A/g'
>> AA

Bu durumda doğru sonuç elbette BA.

Genel olarak, çözüm, verilen değiştirme dizelerinden biriyle en uzun eşleşme için girişi soldan sağa taramaya eşdeğer olmalı ve her eşleme için bir değiştirme gerçekleştirmeli ve girişte bu noktadan devam etmelidir (hiçbiri zaten okunmuş girdi veya yapılan yedeklemeler eşleşmeler için dikkate alınmamalıdır). Aslında, ayrıntılar önemli değil, sadece değiştirmenin sonuçları tamamen veya kısmen başka bir değiştirme için dikkate alınmaz.

NOT Sadece doğru jenerik çözümleri arıyorum. Lütfen belirli girişler (giriş dosyaları, arama ve çiftleri değiştirme) için başarısız olan çözümler önermeyin, ancak olası görünmüyorlar.


Sanırım bir karakterden daha uzunlar? Bunun için kullanabilirsiniz tr AB BA.
Kevin

3
Ve açıkçası, eğer birisi notunuzu biraz kaba düşünürse şaşırmazdım.
peterph

1
Örnek girdi veya çıktı sağlamadığınızda "yalnızca doğru çözümler elde etmeyi" nasıl beklersiniz?
jasonwryan

1
Korkarım ki tam olarak tanımladığınız gibi yapmanız gerekecek - baştan ayrıştırın ve gittiğiniz gibi değiştirin - yani normal ifadelerle değil.
peterph

2
Bu adil bir soru, ama cevap, bir devlet makine ayrıştırıcısına ihtiyacınız var , ki bu rici'nin cevabının sağladığı şey (gerçek hacker tarzında, sanırım). Diğer bir deyişle, görevin karmaşıklığını küçümsüyorsunuz, ala "Ben normal ifadelerle (HT | X) ML'yi ayrıştırmak istiyorum" -> Cevap HAYIR. Sen olamaz (sadece) kullanımı sed. Sen yapamaz (sadece) kullanılması awk. AFAIK bunu kutudan çıkaracak mevcut bir araç yok. Sans rici'nin istismarı, bir kod yazmanız gerekir.
goldilocks

Yanıtlar:


10

Tamam, genel bir çözüm. Aşağıdaki bash işlevi 2kbağımsız değişkenler gerektirir ; her çift bir yer tutucu ve bir yedek parçadan oluşur. Dizeleri fonksiyona geçirmek için uygun şekilde alıntı yapmak size kalmıştır. Bağımsız değişken sayısı tekse, son yer tutucunun oluşumlarını etkili bir şekilde silen örtük bir boş bağımsız değişken eklenir.

Tutucuları ne de değiştirmeler Ne boş karakter olabilir, ancak standart C kullanabilir \gibi -escapes \0sen ihtiyaç olduğuna NULlar (ve dolayısıyla yazmayı gerekli olan \\bir isterseniz \).

Posix benzeri bir sistemde (lex ve cc) bulunması gereken standart oluşturma araçlarını gerektirir.

replaceholder() {
  local dir=$(mktemp -d)
  ( cd "$dir"
    { printf %s\\n "%option 8bit noyywrap nounput" "%%"
      printf '"%s" {fputs("%s", yyout);}\n' "${@//\"/\\\"}"
      printf %s\\n "%%" "int main(int argc, char** argv) { return yylex(); }"
    } | lex && cc lex.yy.c
  ) && "$dir"/a.out
  rm -fR "$dir"
}

\Gerekirse argümanlarda zaten kaçtığını varsayıyoruz, ancak varsa çift tırnaktan kaçmamız gerekiyor. İkinci baskının ikinci argümanı budur. Yana lexvarsayılan eylemdir ECHO, bunu endişe gerekmez.

Örnek çalışma (şüpheci için zamanlamalar ile; sadece ucuz bir emtia dizüstü bilgisayar):

$ time echo AB | replaceholder A B B A
BA

real    0m0.128s
user    0m0.106s
sys     0m0.042s
$ time printf %s\\n AB{0000..9999} | replaceholder A B B A > /dev/null

real    0m0.118s
user    0m0.117s
sys     0m0.043s

Daha büyük girişler için bir optimizasyon bayrağı sağlamak yararlı olabilir ccve mevcut Posix uyumluluğu için kullanılması daha iyi olur c99. Daha da iddialı bir uygulama, oluşturulan yürütülebilir dosyaları her seferinde oluşturmak yerine önbelleğe almaya çalışabilir, ancak bunların oluşturulması tam olarak pahalı değildir.

Düzenle

Eğer varsa tcc , geçici bir dizin oluşturma güçlük önlemek ve normal büyüklükteki girdilere yardımcı olacaktır hızlı derleme zaman geçirebilir:

treplaceholder () { 
  tcc -run <(
  {
    printf %s\\n "%option 8bit noyywrap nounput" "%%"
    printf '"%s" {fputs("%s", yyout);}\n' "${@//\"/\\\"}"
    printf %s\\n "%%" "int main(int argc, char** argv) { return yylex(); }"
  } | lex -t)
}

$ time printf %s\\n AB{0000..9999} | treplaceholder A B B A > /dev/null

real    0m0.039s
user    0m0.041s
sys     0m0.031s

Bunun bir şaka olup olmadığından emin değilim;)
Ambroz Bizjak

3
@ambrozbizjak: Çalışıyor, büyük girdiler için hızlı ve küçük girdiler için kabul edilebilir derecede hızlı. Düşündüğünüz araçları kullanamayabilir, ancak bunlar standart araçlardır. Neden şaka olsun ki?
rici

4
+1 Şaka olmadığı için! : D
goldilocks

Bu POSIX taşınabilir gibi olurdu fn() { tcc ; } <<CODE\n$(gen code)\nCODE\n. Yine de sorabilir miyim - bu harika bir cevap ve okuduğum anda onu iptal ettim - ama kabuk dizisine ne olduğunu anlamıyorum? "${@//\"/\\\"}"Bu ne yapar ?
mikeserv

@mikeserv: «Belirtilen değer (" $ @ ") olarak her bir bağımsız değişken için, bir teklifin (\") tüm (//) örneklerini (/) bir ters eğik çizgi (\\) ve ardından bir teklif (\ ") ile değiştirin ». Bkz. Bash kılavuzundaki parametre genişletme.
rici

1
printf 'STRING1STRING1\n\nSTRING2STRING1\nSTRING2\n' |
od -A n -t c -v -w1 |
sed 's/ \{1,3\}//;s/\\$/&&/;H;s/.*//;x
     /\nS\nT\nR\nI\nN\nG\n1/s//STRING2/
     /\nS\nT\nR\nI\nN\nG\n2/s//STRING1/
     /\\n/!{x;d};s/\n//g;s/./\\&/g' |
     xargs printf %b

###OUTPUT###

STRING2STRING2

STRING1STRING2
STRING1

Bunun gibi bir şey, hedef dizelerinizin her tekrarını her sedakışta satır başına bir ısırıkta gerçekleştikçe yalnızca bir kez değiştirir . Bunu yapabileceğini hayal edebildiğim en hızlı yol bu. Sonra tekrar, C yazmıyorum Ama bu mu bunu isteyen güvenilir boş ayraçları işlemek. Nasıl çalıştığına dair bu cevaba bakınız . Bu, herhangi bir ile herhangi bir sorun özel kabuk karakterler veya benzeri içerdiği var - ama olan ASCII yerel özgü veya başka bir deyişle, odaynı hat üzerinde değil çıktı çok baytlık karakterler ve sadece bir tane olacak yapacağız olacaktır. Bu bir sorunsa eklemek isteyeceksiniz iconv.


+1 Neden yalnızca "hedef dizelerinizin en eski oluşumunun" yerini aldığını söylüyorsunuz? Çıktıda hepsinin yerini alıyor gibi görünüyor. Ben görmek istemiyorum, ama bu değerleri hardcoding olmadan bu şekilde yapılabilir?
goldilocks

@goldilocks - Evet - ama sadece meydana gelir gelmez. Belki de onu yeniden yazmalıyım. Ve evet - sadece bir orta ekleyebilir sedve bir null veya bir şeye kadar tasarruf edebilirsiniz, o zaman bunun sedsenaryosunu yazabilirsiniz; ya da bir kabuk işlevine koy ve her satırda bir ısırık gibi değerler ver "/$1/"... "/$2/"- belki de bu işlevleri de yazacağım ...
mikeserv

Bu tutuculardır durumda çalışmak için görünmüyor PLACE1, PLACE2ve PLA. PLAher zaman kazanır. OP diyor ki: " verilen yedek dizelerden birine en uzun eşleşme için girişi soldan sağa taramaya eşdeğer " (vurgu eklendi)
rici

@rici - teşekkürler. O zaman sıfır sınırlayıcılarını yapmak zorunda kalacağım. Bir an önce.
mikeserv

@rici - Açıkladığınız şeyi işleyecek başka bir sürüm yayınlamak üzereydim, ancak tekrar bakıyorum ve yapmam gerektiğini düşünmüyorum. Verilen yedek dizelerden biri için en uzun süreyi söylüyor . Bunu yapar. Bir dizenin diğerinin bir alt kümesi olduğuna dair hiçbir belirti yoktur, sadece değiştirilen değerin olabileceğini gösterir. Bir liste üzerinden yineleme yapmanın sorunu çözmenin geçerli bir yolu olduğunu da düşünmüyorum. Anladığım kadarıyla sorun göz önüne alındığında, bu çalışan bir çözümdür.
mikeserv

1

Bir perlçözüm. Bazıları mümkün olmasa bile, birini buldum ama genel olarak basit bir eşleşme ve değiştirme mümkün değil ve hatta bir NFA'nın geri izlenmesi nedeniyle daha da kötüleşiyor, sonuç beklenmedik olabilir.

Genel olarak ve bunun söylenmesi gerekir ki, sorun, yedek tüplerin sırasına ve uzunluğuna bağlı olarak farklı sonuçlar doğurur. yani:

A B
AA CC

ve giriş veya ile AAAsonuçlanır .BBBCCB

İşte kod:

#!/usr/bin/perl

$v='if (0) {} ';
while (($a,$b)=split /\s+/, <DATA>) {
  $k.=$a.'|';
  $v.='elsif ($& eq \''.$a.'\') {print \''.$b.'\'} ';
}
$k.='.';
$v.='else {print $&;}';

eval "
while (<>) {
  \$_ =~ s/($k)/{$v}/geco;
}";  
print "\n";


__DATA__
A    B
B    A
abba baab
baab abbc
abbc aaba

Checkerbunny:

$ echo 'ABBabbaBBbaabAAabbc'|perl script
$ BAAbaabAAabbcBBaaba
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.