Penggantian pola pencocokan case dengan sed

14

Saya memiliki kode sumber yang tersebar di beberapa file.

  • Ini memiliki pola abcdefyang harus saya ganti pqrstuvxyz.
  • Polanya bisa Abcdef(Kasus Kalimat) maka perlu diganti Pqrstuvxyz.
  • Polanya bisa AbCdEf(Toggle case) maka perlu diganti PqRsTuVxYz.

Singkatnya, saya harus mencocokkan kasus pola sumber dan menerapkan pola tujuan yang sesuai.

Bagaimana saya bisa mencapai ini menggunakan sedatau alat lain?

pengguna1263746
sumber
Dan apakah itu ABcDeF?
Stéphane Chazelas
PQrStUvxyz - Saya mengerti maksud Anda.
user1263746
Jadi jika ABcDeF-> PQrStUvxyz, maka pasti AbCdEf-> PqRsTuvxyzakan konsisten secara logis. Jika kasing akan disalin dari satu string ke yang lain, apa yang harus terjadi jika string pengganti yang kedua lebih panjang.
Graeme
Yah mari kita pangkas penggantian ke "pqrstu" demi singkatnya.
user1263746

Jawaban:

9

Solusi portabel menggunakan 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'

Lebih mudah dengan sed GNU:

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"

Dengan menggunakan di &&&atas, kami menggunakan kembali pola case string untuk sisa penggantian, Jadi ABcdefakan diubah ke PQrstuVWxdan AbCdEfke PqRsTuVwX. Ubah untuk &hanya mempengaruhi huruf 6 karakter pertama.

(catatan bahwa hal itu tidak mungkin melakukan apa yang Anda inginkan atau mungkin mengalami infinite loop jika penggantian dapat dikenakan substitusi (misalnya jika mengganti foountuk foo, atau bcduntuk abcd)

Stéphane Chazelas
sumber
8

Solusi portabel menggunakan 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)
  }
}'

Input contoh:

other abcdef other
other Abcdef other
other AbCdEf other

Contoh output:

other pqrstu other
other Pqrstu other
other PqRsTu other

Memperbarui

Seperti yang ditunjukkan dalam komentar, hal di atas hanya akan menggantikan instance pertama dari findsetiap baris. Untuk mengganti semua instance:

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

Input contoh:

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

Contoh output:

other pqrstu other PQRstu other
other Pqrstu other pqrSTU
other PqRsTu other pQRsTu other
Graeme
sumber
Perhatikan bahwa hanya memproses satu instance per baris.
Stéphane Chazelas
@StephaneChazelas, diperbarui untuk menangani beberapa kejadian.
Graeme
6

Anda bisa menggunakannya perl. Langsung dari faq - mengutip dari perldoc perlfaq6:

Bagaimana cara mengganti case-insensitive pada LHS sambil mempertahankan case pada RHS?

Inilah solusi Perlish yang bagus dari Larry Rosler. Ini mengeksploitasi properti bitwise xor pada string 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;

Dan ini dia sebagai subrutin, dimodelkan seperti di atas:

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

Ini mencetak:

           this is a SUcCESS case

Sebagai alternatif, untuk menjaga huruf pengganti kata jika lebih panjang dari aslinya, Anda dapat menggunakan kode ini, oleh 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);
           }

Ini mengubah kalimat menjadi "ini adalah kasus SUcCess."

Hanya untuk menunjukkan bahwa pemrogram C dapat menulis C dalam bahasa pemrograman apa pun, jika Anda lebih suka solusi yang lebih mirip C, skrip berikut membuat substitusi memiliki kasus yang sama, huruf per huruf, seperti aslinya. (Ini juga terjadi untuk menjalankan sekitar 240% lebih lambat daripada berjalan solusi Perlish.) Jika substitusi memiliki karakter lebih dari string yang diganti, kasus karakter terakhir digunakan untuk sisa substitusi.

   # 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
sumber
Perhatikan bahwa ini terbatas pada surat ASCII.
Stéphane Chazelas
5

Jika Anda memotong pengganti ke pqrstu, coba ini:

Memasukkan:

abcdef
Abcdef
AbCdEf
ABcDeF

Ouput:

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

Jika Anda ingin mengganti dengan prstuvxyz, mungkin ini:

$ 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

Saya tidak dapat menemukan aturan apa pun untuk dipetakan ABcDeF-> PQrStUvxyz.

cuonglm
sumber
Perhatikan bahwa ini terbatas pada surat ASCII.
Stéphane Chazelas
3

Sesuatu seperti ini akan melakukan apa yang Anda gambarkan.

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