web-dev-qa-db-ja.com

sedを使用したケースマッチングパターンの置換

ソースコードが複数のファイルに分散しています。

  • abcdefというパターンがありますが、これをpqrstuvxyzに置き換える必要があります。
  • パターンはAbcdef(文の場合)の場合、Pqrstuvxyzに置き換える必要があります。
  • パターンはAbCdEf(大文字と小文字の切り替え)の場合、PqRsTuVxYzに置き換える必要があります。

つまり、ソースパターンの大文字と小文字を一致させ、適切な宛先パターンを適用する必要があります。

sedまたはその他のツールを使用してこれをどのように実現できますか?

14
user1263746

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'

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"

上記の&&&を使用することで、文字列の大文字と小文字のパターンを残りの置換に再利用するため、ABcdefPQrstuVWxに、AbCdEfPqRsTuVwXに変更します。これを&に変更すると、最初の6文字の大文字と小文字のみが影響を受けます。

(置換が置換の対象となる可能性がある場合(たとえば、foofooに、またはbcdabcdに置換する場合)、期待どおりに動作しないか、無限ループに陥る可能性があることに注意してください)

9

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

入力例:

other abcdef other
other Abcdef other
other AbCdEf other

出力例:

other pqrstu other
other Pqrstu other
other PqRsTu other

更新

コメントで指摘されているように、上記はすべての行のfindの最初のインスタンスのみを置き換えます。すべてのインスタンスを置き換えるには:

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

入力例:

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

出力例:

other pqrstu other PQRstu other
other Pqrstu other pqrSTU
other PqRsTu other pQRsTu other
8
Graeme

Perlを使用できます。よくある質問-perldoc perlfaq6からの引用:

RHSで大文字と小文字を区別しながら、LHSで大文字と小文字を区別せずに置換するにはどうすればよいですか?

Larry Roslerによる素敵なPerlishソリューションを次に示します。 ASCII文字列でビットごとのxorのプロパティを利用します。

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

そして、これは上記のようにモデル化されたサブルーチンです:

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

これは印刷します:

           this is a SUcCESS case

別の方法として、元のWordよりも長い場合に置換後のWordの大文字と小文字を区別するには、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);
           }

これは文を「これはSUcCessのケースです」に変更します。

Cプログラマーが任意のプログラミング言語でCを記述できることを示すために、よりCに似たソリューションを希望する場合は、次のスクリプトにより、元の文字と同じように大文字と小文字が置き換えられます。 (Perlishソリューションの実行よりも約240%遅く実行されることもあります。)置換される文字列よりも置換の文字数が多い場合、残りの置換には最後の文字の大文字小文字が使用されます。

   # 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;
   }
6
devnull

置換をpqrstuにトリミングする場合は、これを試してください。

入力:

abcdef
Abcdef
AbCdEf
ABcDeF

出力:

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

prstuvxyzに置き換えたい場合は、次のようになります。

$ 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

ABcDeF-> PQrStUvxyzをマップするルールが見つかりません。

5
cuonglm

このような何かはあなたが説明したことをするでしょう。

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