Remplacement du modèle assorti avec sed

14

J'ai un code source réparti sur plusieurs fichiers.

  • Il a un modèle abcdefque je dois remplacer pqrstuvxyz.
  • Le modèle peut être Abcdef(Phrase Sentence), puis il doit être remplacé par Pqrstuvxyz.
  • Le modèle peut être AbCdEf(Basculer la casse), il doit alors être remplacé par PqRsTuVxYz.

En bref, je dois faire correspondre le cas du motif source et appliquer le motif de destination approprié.

Comment puis-je y parvenir en utilisant sedou tout autre outil?

user1263746
la source
Et si c'est le cas ABcDeF?
Stéphane Chazelas
PQrStUvxyz - Je comprends votre point.
user1263746
Donc si ABcDeF-> PQrStUvxyz, alors sûrement AbCdEf-> PqRsTuvxyzserait logiquement cohérent. Si le cas doit être copié d'une chaîne à l'autre, que doit-il se passer si la deuxième chaîne de remplacement est plus longue.
Graeme
Permet bien de couper le remplacement à "pqrstu" pour des raisons de brièveté.
user1263746

Réponses:

9

Solution portable utilisant 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'

C'est un peu plus facile avec GNU sed:

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"

En utilisant &&&ci-dessus, nous réutilisons le modèle de casse de la chaîne pour le reste du remplacement, donc ABcdefserait changé en PQrstuVWxet AbCdEfvers PqRsTuVwX. Modifiez-le pour &n'affecter que la casse des 6 premiers caractères.

(notez qu'il peut ne pas faire ce que vous voulez ou peut se retrouver dans une boucle infinie si le remplacement peut être soumis à une substitution (par exemple s'il remplace foopour fooou bcdpour abcd)

Stéphane Chazelas
la source
8

Solution portable utilisant 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)
  }
}'

Exemple d'entrée:

other abcdef other
other Abcdef other
other AbCdEf other

Exemple de sortie:

other pqrstu other
other Pqrstu other
other PqRsTu other

Mise à jour

Comme indiqué dans les commentaires, ce qui précède ne remplacera que la première instance de finddans chaque ligne. Pour remplacer toutes les instances:

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

Exemple d'entrée:

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

Exemple de sortie:

other pqrstu other PQRstu other
other Pqrstu other pqrSTU
other PqRsTu other pQRsTu other
Graeme
la source
Notez qu'il ne traite qu'une seule instance par ligne.
Stéphane Chazelas
@StephaneChazelas, mis à jour pour gérer plusieurs instances.
Graeme
6

Vous pourriez utiliser perl. Directement à partir de la FAQ - citant perldoc perlfaq6:

Comment puis-je remplacer la casse de manière insensible sur le LHS tout en préservant la casse sur le RHS?

Voici une belle solution Perlish de Larry Rosler. Il exploite les propriétés de xor au niveau du bit sur les chaînes 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;

Et ici, c'est comme un sous-programme, calqué sur ce qui précède:

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

Cela imprime:

           this is a SUcCESS case

Comme alternative, pour conserver la casse du mot de remplacement s'il est plus long que l'original, vous pouvez utiliser ce code, par Jeff Pinyan:

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

Cela change la phrase en "ceci est un cas de SUCESS."

Juste pour montrer que les programmeurs C peuvent écrire C dans n'importe quel langage de programmation, si vous préférez une solution plus semblable à C, le script suivant fait que la substitution a le même cas, lettre par lettre, que l'original. (Il se trouve également qu'il s'exécute environ 240% plus lentement que la solution Perlish.) Si la substitution a plus de caractères que la chaîne substituée, la casse du dernier caractère est utilisée pour le reste de la substitution.

   # 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
la source
Notez qu'il est limité aux lettres ASCII.
Stéphane Chazelas
5

Si vous ajustez le remplacement à pqrstu, essayez ceci:

Contribution:

abcdef
Abcdef
AbCdEf
ABcDeF

Sortie:

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

Si vous souhaitez remplacer par prstuvxyz, peut-être ceci:

$ 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

Je ne trouve aucune règle à mapper ABcDeF-> PQrStUvxyz.

cuonglm
la source
Notez qu'il est limité aux lettres ASCII.
Stéphane Chazelas
3

Quelque chose comme ça ferait ce que vous avez décrit.

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