Zamiennik wzoru pasującego do skrzynki z sed

14

Mam kod źródłowy rozłożony na kilka plików.

  • Ma wzór, abcdefktóry muszę zastąpić pqrstuvxyz.
  • Wzorem może być Abcdef(Zdanie), a następnie należy go zastąpić Pqrstuvxyz.
  • Wzorem może być AbCdEf(Przełącz wielkość liter), a następnie należy go zastąpić PqRsTuVxYz.

Krótko mówiąc, muszę dopasować wzór wzorca źródłowego i zastosować odpowiedni wzorzec docelowy.

Jak mogę to osiągnąć za pomocą sedlub innego narzędzia?

użytkownik1263746
źródło
A jeśli to jest ABcDeF?
Stéphane Chazelas,
PQrStUvxyz - Rozumiem o co ci chodzi.
user1263746,
Więc jeśli ABcDeF-> PQrStUvxyz, to na pewno AbCdEf-> PqRsTuvxyzbyłoby logicznie spójne. Jeśli sprawa ma zostać skopiowana z jednego ciągu na drugi, co powinno się stać, jeśli drugi ciąg zastępujący jest dłuższy.
Graeme,
Cóż, pozwólmy przyciąć zamiennik na „pqrstu” ze względu na zwięzłość.
user1263746,

Odpowiedzi:

9

Przenośne rozwiązanie wykorzystujące sed:

sed '
:1
/[aA][bB][cC][dD][eE][fF]/!b
s//\
&\
pqrstu\
PQRSTU\
/;:2
s/\n[[:lower:]]\(.*\n\)\(.\)\(.*\n\).\(.*\n\)/\2\
\1\3\4/;s/\n[^[:lower:]]\(.*\n\).\(.*\n\)\(.\)\(.*\n\)/\3\
\1\2\4/;t2
s/\n.*\n//;b1'

Z GNU sed jest to trochę łatwiejsze:

search=abcdef replace=pqrstuvwx
sed -r ":1;/$search/I!b;s//\n&&&\n$replace\n/;:2
    s/\n[[:lower:]](.*\n)(.)(.*\n)/\l\2\n\1\3/
    s/\n[^[:lower:]](.*\n)(.)(.*\n)/\u\2\n\1\3/;t2
    s/\n.*\n(.*)\n/\1/g;b1"

Używając &&&powyższego, ponownie wykorzystujemy wzór wielkości liter w ciągu do końca zamiany, więc ABcdefzmieniono by na PQrstuVWxi AbCdEfna PqRsTuVwX. Zmień to, aby &wpływać tylko na wielkość pierwszych 6 znaków.

(Zauważ, że nie może zrobić, co chcesz lub mogą działać w nieskończoną pętlę jeśli wymiana może podlegać podstawienia (na przykład jeśli zastępując foona foolub bcdza abcd)

Stéphane Chazelas
źródło
8

Przenośne rozwiązanie wykorzystujące awk:

awk -v find=abcdef -v rep=pqrstu '{
  lwr=tolower($0)
  offset=index(lwr, tolower(find))

  if( offset > 0 ) {
    printf "%s", substr($0, 0, offset)
    len=length(find)

    for( i=0; i<len; i++ ) {
      out=substr(rep, i+1, 1)

      if( substr($0, offset+i, 1) == substr(lwr, offset+i, 1) )
        printf "%s", tolower(out)
      else
        printf "%s", toupper(out)
    }

    printf "%s\n", substr($0, offset+len)
  }
}'

Przykładowe dane wejściowe:

other abcdef other
other Abcdef other
other AbCdEf other

Przykładowe dane wyjściowe:

other pqrstu other
other Pqrstu other
other PqRsTu other

Aktualizacja

Jak wskazano w komentarzach, powyższe zastąpi tylko pierwsze wystąpienie findw każdym wierszu. Aby zastąpić wszystkie wystąpienia:

awk -v find=abcdef -v rep=pqrstu '{
  input=$0
  lwr=tolower(input)
  offset=index(lwr, tolower(find))

  if( offset > 0 ) {
    while( offset > 0 ) {

      printf "%s", substr(input, 0, offset)
      len=length(find)

      for( i=0; i<len; i++ ) {
        out=substr(rep, i+1, 1)

        if( substr(input, offset+i, 1) == substr(lwr, offset+i, 1) )
          printf "%s", tolower(out)
        else
          printf "%s", toupper(out)
      }

      input=substr(input, offset+len)
      lwr=substr(lwr, offset+len)
      offset=index(lwr, tolower(find))
    }

    print input
  }
}'

Przykładowe dane wejściowe:

other abcdef other ABCdef other
other Abcdef other abcDEF
other AbCdEf other aBCdEf other

Przykładowe dane wyjściowe:

other pqrstu other PQRstu other
other Pqrstu other pqrSTU
other PqRsTu other pQRsTu other
Graeme
źródło
Zauważ, że przetwarza tylko jedną instancję w linii.
Stéphane Chazelas
@StephaneChazelas, zaktualizowano do obsługi wielu wystąpień.
Graeme
6

Możesz użyć perl. Prosto z FAQ - cytowanie z perldoc perlfaq6:

Jak zastąpić LHS bez rozróżniania wielkości liter, jednocześnie zachowując skrzynkę z RHS?

Oto piękne rozwiązanie Perlish autorstwa Larry'ego Roslera. Wykorzystuje właściwości bitowej xor na ciągach ASCII.

   $_= "this is a TEsT case";

   $old = 'test';
   $new = 'success';

   s{(\Q$old\E)}
   { uc $new | (uc $1 ^ $1) .
           (uc(substr $1, -1) ^ substr $1, -1) x
           (length($new) - length $1)
   }egi;

   print;

A tutaj jest to podprogram, wzorowany na powyższym:

       sub preserve_case($$) {
               my ($old, $new) = @_;
               my $mask = uc $old ^ $old;

               uc $new | $mask .
                       substr($mask, -1) x (length($new) - length($old))
   }

       $string = "this is a TEsT case";
       $string =~ s/(test)/preserve_case($1, "success")/egi;
       print "$string\n";

To drukuje:

           this is a SUcCESS case

Alternatywnie, aby zachować wielkość słowa zastępującego, jeśli jest ono dłuższe niż oryginał, możesz użyć tego kodu autorstwa Jeffa Pinyana:

   sub preserve_case {
           my ($from, $to) = @_;
           my ($lf, $lt) = map length, @_;

           if ($lt < $lf) { $from = substr $from, 0, $lt }
           else { $from .= substr $to, $lf }

           return uc $to | ($from ^ uc $from);
           }

Zmienia to zdanie na „jest to przypadek SUcCess”.

Aby pokazać, że programiści C mogą pisać C w dowolnym języku programowania, jeśli wolisz bardziej podobne do C rozwiązanie, poniższy skrypt sprawia, że ​​podstawienie ma taką samą wielkość liter, litera po literze, jak oryginał. (Zdarza się również, że działa o około 240% wolniej niż w rozwiązaniu Perlish.) Jeśli podstawienie ma więcej znaków niż podstawiany ciąg, to do końca podstawienia używana jest wielkość ostatniego znaku.

   # Original by Nathan Torkington, massaged by Jeffrey Friedl
   #
   sub preserve_case($$)
   {
           my ($old, $new) = @_;
           my ($state) = 0; # 0 = no change; 1 = lc; 2 = uc
           my ($i, $oldlen, $newlen, $c) = (0, length($old), length($new));
           my ($len) = $oldlen < $newlen ? $oldlen : $newlen;

           for ($i = 0; $i < $len; $i++) {
                   if ($c = substr($old, $i, 1), $c =~ /[\W\d_]/) {
                           $state = 0;
                   } elsif (lc $c eq $c) {
                           substr($new, $i, 1) = lc(substr($new, $i, 1));
                           $state = 1;
                   } else {
                           substr($new, $i, 1) = uc(substr($new, $i, 1));
                           $state = 2;
                   }
           }
           # finish up with any remaining new (for when new is longer than old)
           if ($newlen > $oldlen) {
                   if ($state == 1) {
                           substr($new, $oldlen) = lc(substr($new, $oldlen));
                   } elsif ($state == 2) {
                           substr($new, $oldlen) = uc(substr($new, $oldlen));
                   }
           }
           return $new;
   }
diabelnie
źródło
Pamiętaj, że jest ograniczony do liter ASCII.
Stéphane Chazelas
5

Jeśli przycinasz zamień na pqrstu, spróbuj tego:

Wejście:

abcdef
Abcdef
AbCdEf
ABcDeF

Ouput:

$ perl -lpe 's/$_/$_^lc($_)^"pqrstu"/ei' file
pqrstu
Pqrstu
PqRsTu
PQrStU

Jeśli chcesz zastąpić prstuvxyz, może to być:

$ perl -lne '@c=unpack("(A4)*",$_);
    $_ =~ s/$_/$_^lc($_)^"pqrstu"/ei;
    $c[0] =~ s/$c[0]/$c[0]^lc($c[0])^"vxyz"/ei;
    print $_,$c[0]' file
pqrstuvxyz
PqrstuVxyz
PqRsTuVxYz
PQrStUVXyZ

Nie mogę znaleźć żadnej reguły do ​​zmapowania ABcDeF-> PQrStUvxyz.

Cuonglm
źródło
Pamiętaj, że jest ograniczony do liter ASCII.
Stéphane Chazelas
3

Coś takiego zrobiłoby to, co opisałeś.

sed -i.bak -e "s/abcdef/pqrstuvxyz/g" \
 -e "s/AbCdEf/PqRsTuVxYz/g" \
 -e "s/Abcdef/Pqrstuvxyz/g" files/src
UnX
źródło