Fall Matching Pattern Ersatz mit sed

14

Ich habe einen Quellcode über mehrere Dateien verteilt.

  • Es hat ein Muster, abcdefdas ich ersetzen muss pqrstuvxyz.
  • Das Muster könnte sein Abcdef(Satz Groß- / Kleinschreibung), dann muss es ersetzt werden durch Pqrstuvxyz.
  • Das Muster könnte sein AbCdEf(Groß- / Kleinschreibung umschalten), dann muss es ersetzt werden durch PqRsTuVxYz.

Kurz gesagt, ich muss den Fall des Quellmusters abgleichen und das entsprechende Zielmuster anwenden.

Wie kann ich dies mit sedeinem anderen Tool erreichen?

user1263746
quelle
Und wenn es ist ABcDeF?
Stéphane Chazelas
PQrStUvxyz - Ich verstehe.
user1263746
Wenn also ABcDeF-> PQrStUvxyz, dann wäre sicherlich AbCdEf-> PqRsTuvxyzlogisch konsistent. Wenn der Fall von einer Zeichenfolge in die andere kopiert werden soll, was soll passieren, wenn die zweite Ersatzzeichenfolge länger ist.
Graeme
Trimmen wir den Ersatz der Kürze halber auf "pqrstu".
user1263746

Antworten:

9

Tragbare Lösung mit 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'

Mit GNU sed ist es ein bisschen einfacher:

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"

Durch die Verwendung von &&&oben wird das Fallmuster der Zeichenfolge für den Rest der Ersetzung wiederverwendet. Es ABcdefwürde also zu PQrstuVWxund AbCdEfzu geändert PqRsTuVwX. Ändern Sie es auf &, um nur den Fall der ersten 6 Zeichen zu beeinflussen.

(Beachten Sie, dass es möglicherweise nicht das tut, was Sie wollen, oder in eine Endlosschleife gerät, wenn die Ersetzung möglicherweise einer Ersetzung unterliegt (zum Beispiel, wenn Sie foofür foooder bcdfür ersetzen abcd).

Stéphane Chazelas
quelle
8

Tragbare Lösung mit 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)
  }
}'

Beispiel Eingabe:

other abcdef other
other Abcdef other
other AbCdEf other

Beispielausgabe:

other pqrstu other
other Pqrstu other
other PqRsTu other

Aktualisieren

Wie in den Kommentaren erwähnt, ersetzt das Obige nur die erste Instanz von findin jeder Zeile. So ersetzen Sie alle Instanzen:

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

Beispiel Eingabe:

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

Beispielausgabe:

other pqrstu other PQRstu other
other Pqrstu other pqrSTU
other PqRsTu other pQRsTu other
Graeme
quelle
Beachten Sie, dass nur eine Instanz pro Zeile verarbeitet wird.
Stéphane Chazelas
@StephaneChazelas, aktualisiert, um mehrere Instanzen zu verarbeiten.
Graeme
6

Du könntest es gebrauchen perl. Direkt aus der FAQ - zitiert aus perldoc perlfaq6:

Wie ersetze ich die Groß- / Kleinschreibung in der LHS, während die Groß- / Kleinschreibung in der RHS beibehalten wird?

Hier ist eine schöne Perlish-Lösung von Larry Rosler. Es nutzt die Eigenschaften von bitweisem xor für ASCII-Zeichenfolgen.

   $_= "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;

Und hier handelt es sich um eine Subroutine, die wie folgt aufgebaut ist:

       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";

Dies druckt:

           this is a SUcCESS case

Alternativ können Sie den folgenden Code von Jeff Pinyan verwenden, um die Groß- und Kleinschreibung des Ersatzworts beizubehalten, falls es länger als das Original ist:

   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);
           }

Dies ändert den Satz in "Dies ist ein Erfolgsfall".

Nur um zu zeigen, dass C-Programmierer C in jeder Programmiersprache schreiben können. Wenn Sie eine C-ähnliche Lösung bevorzugen, wird im folgenden Skript für die Ersetzung der gleiche Groß- und Kleinschreibung verwendet, Buchstabe für Buchstabe wie für das Original. (Es ist auch ungefähr 240% langsamer als die Perlish-Lösung.) Wenn die Ersetzung mehr Zeichen als die zu ersetzende Zeichenfolge enthält, wird der Fall des letzten Zeichens für den Rest der Ersetzung verwendet.

   # 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;
   }
devnull
quelle
Beachten Sie, dass es auf ASCII-Buchstaben beschränkt ist.
Stéphane Chazelas
5

Wenn Sie das Ersetzen auf pqrstuzuschneiden, versuchen Sie Folgendes :

Eingang:

abcdef
Abcdef
AbCdEf
ABcDeF

Ausgang:

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

Wenn Sie ersetzen möchten prstuvxyz, kann dies sein:

$ 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

Ich kann keine Regel zur Zuordnung finden ABcDeF-> PQrStUvxyz.

cuonglm
quelle
Beachten Sie, dass es auf ASCII-Buchstaben beschränkt ist.
Stéphane Chazelas
3

So etwas würde das tun, was Sie beschrieben haben.

sed -i.bak -e "s/abcdef/pqrstuvxyz/g" \
 -e "s/AbCdEf/PqRsTuVxYz/g" \
 -e "s/Abcdef/Pqrstuvxyz/g" files/src
UnX
quelle