sedを使用して代替をリンクせずに複数の代替を実行する方法はありますか?

sedを使用して代替をリンクせずに複数の代替を実行する方法はありますか?

AをBBに、BをAAに置き換えるシェルスクリプトを作成したいと思います。この種の仕事に対する私の一般的な解決策は、次のようにsedへの複数の呼び出しをリンクすることですAYBBBYAA

sed 's/A/BB/g;s/B/AA/g'

ただし、この場合はにA翻訳されているため動作しません。また、代替テキストが1文字より長いため、オプションではないようです。私ができることは他にありますか? sed や tr 以外のものを使うかは関係ありません。AAAABBtr

答え1

この種の問題では、両方のパターンを同時に検索できるようにループが必要です。

awk '
    BEGIN {
        regex = "A|B"
        map["A"] = "BB"
        map["B"] = "AA"
    }
    {
        str = $0
        result = ""
        while (match(str, regex)) {
            found = substr(str, RSTART, RLENGTH)
            result = result substr(str, 1, RSTART-1) map[found]
            str = substr(str, RSTART+RLENGTH)
        }
        print result str
    }
'

もちろん、Perlが利用可能であれば、それに対応するonelinerがあります:

perl -pe '
    BEGIN { %map = ("A" => "BB", "B" => "AA"); }
    s/(A|B)/$map{$1}/g;
'

パターンに特殊文字が含まれていない場合は、動的に正規表現を作成することもできます。

perl -pe '
    BEGIN {
        %map = ("A" => "BB", "B" => "AA");
        $regex = join "|", keys %map;
    }
    s/($regex)/$map{$1}/g;
'

ところで、Tclにはonelinersという組み込みコマンドがありますが、string mapTcl onelinersを書くのは簡単ではありません。


長さでキーをソートする効果を示します。

  1. ソートされていない

    $ echo ABBA | perl -pe '
        BEGIN {
            %map = (A => "X", BB => "Y", AB => "Z");
            $regex = join "|", map {quotemeta} keys %map;
            print $regex, "\n";
        }
        s/($regex)/$map{$1}/g
    '
    
    A|AB|BB
    XYX
    
  2. 並べ替え

    $ echo ABBA | perl -pe '
          BEGIN {
              %map = (A => "X", BB => "Y", AB => "Z");
              $regex = join "|", map {quotemeta $_->[1]}
                                 reverse sort {$a->[0] <=> $b->[0]}
                                 map {[length, $_]}
                                 keys %map;
              print $regex, "\n";
          }
          s/($regex)/$map{$1}/g
      '
    
    BB|AB|A
    ZBX
    

Perlの「一般的な」並べ替えとSchwartzianの並べ替えベンチマーク:サブルーチンのコードは以下から直接取得されます。sort文書

#!perl
use Benchmark   qw/ timethese cmpthese /;

# make up some key=value data
my $key='a';
for $x (1..10000) {
    push @unsorted,   $key++ . "=" . int(rand(32767));
}

# plain sorting: first by value then by key
sub nonSchwartzian {
    my @sorted = 
        sort { ($b =~ /=(\d+)/)[0] <=> ($a =~ /=(\d+)/)[0] || uc($a) cmp uc($b) } 
        @unsorted
}

# using the Schwartzian transform
sub schwartzian {
    my @sorted =
        map  { $_->[0] }
        sort { $b->[1] <=> $a->[1] || $a->[2] cmp $b->[2] }
        map  { [$_, /=(\d+)/, uc($_)] } 
        @unsorted
}

# ensure the subs sort the same way
die "different" unless join(",", nonSchwartzian()) eq join(",", schwartzian());

# benchmark
cmpthese(
    timethese(-10, {
        nonSchwartzian => 'nonSchwartzian()',
        schwartzian    => 'schwartzian()',
    })
);

実行してください:

$ perl benchmark.pl
Benchmark: running nonSchwartzian, schwartzian for at least 10 CPU seconds...
nonSchwartzian: 11 wallclock secs (10.43 usr +  0.05 sys = 10.48 CPU) @  9.73/s (n=102)
schwartzian: 11 wallclock secs (10.13 usr +  0.03 sys = 10.16 CPU) @ 49.11/s (n=499)
                 Rate nonSchwartzian    schwartzian
nonSchwartzian 9.73/s             --           -80%
schwartzian    49.1/s           405%             --

Schwartzian変換を使用するコードは4倍高速です。

比較関数はどこにありますか?ただ length各要素に対して、次の操作を行います。

Benchmark: running nonSchwartzian, schwartzian for at least 10 CPU seconds...
nonSchwartzian: 11 wallclock secs (10.06 usr +  0.03 sys = 10.09 CPU) @ 542.52/s (n=5474)
schwartzian: 10 wallclock secs (10.21 usr +  0.02 sys = 10.23 CPU) @ 191.50/s (n=1959)
                Rate    schwartzian nonSchwartzian
schwartzian    191/s             --           -65%
nonSchwartzian 543/s           183%             --

Schwartzianは、この安価なソート機能を使用すると速度がはるかに遅くなります。

今、悪意のあるコメントから抜け出すことができますか?

答え2

で単一置換を使用してすべての操作を実行することはできませんが、2つの部分文字列と合計が単一文字か長い文字列であるかに応じて、さまざまなsed方法で操作を正しく実行できます。AB

2つの部分文字列の合計が単一文字であると仮定するAと...B

AYBあなたはに変身したいと思いますBBYAA

  1. Aそれぞれをに変更しBB使用してくださいAy/AB/BA/
  2. A新しい文字列の各項目をAAusingに置き換えますs/A/AA/g
  3. B新しい文字列の各項目をBBusingに置き換えますs/B/BB/g
$ echo AYB | sed 'y/AB/BA/; s/B/BB/g; s/A/AA/g'
BBYAA

我々が得る最後の2つのステップを組み合わせると

$ echo AYB | sed 'y/AB/BA/; s/[AB]/&&/g'
BBYAA

実際、ここでの作業順序は重要ではありません。

$ echo AYB | sed 's/[AB]/&&/g; y/AB/BA/'
BBYAA

editsedコマンドは、y///ユーティリティプログラムと同様に、最初の引数の文字を2番目の引数の対応する文字に変換しますtr。これAは単一の操作で行われBますy/AB/BA/。一般的に言えばy///そうです。たくさん個々の文字の翻訳には正規表現が含まれていないため、たとえば、より速く、移植不可能な便利な拡張を文字列で改行文字をs///g挿入することもできます。\ns///s///sed

&コマンドの代替部分の文字は、s///最初の引数と一致する式で置き換えられるため、入力データの文字はs/[AB]/&&/g2倍になります。AB


oo複数文字のサブストリングの場合、サブストリングが互いに異なると仮定すると(つまり、およびの場合のように、あるサブストリングが別のサブストリングに見つからないfoo)、次のようなものを使用します。

$ echo fooxbar | sed 's/foo/@/g; s/bar/foofoo/g; s/@/barbar/g'
barbarxfoofoo

つまり、2つの文字列をデータにない中間文字列に置き換えます。中間文字列は、単一文字ではなく、データ内に見つからない任意の文字列にすることができます。

答え3

渡すとawk使えますモード1フィールド区切りFS記号1個交換出力フィールド区切り文字としてOFS。その後、各フィールドを繰り返し交換します。モード2渡す2個交換:

awk '{for (f=1;f<=NF;f++){gsub(p,r,$f)} $1=$1}1' FS=A OFS=BB p=B r=AA file

ポイントは$1=$1記録の再構成を強制することですそれ以外の場合は失敗します0A

これはPOSIX仕様であり、途中に文字列が含まれていないので完璧です。

答え4

GNU sedallをAsレコード区切り文字に変更すると、これを行うことができます\n。これは決して起こりません。

echo AYB |
sed -e '
  y/A/\n/
  s/[\nB]/&&/g
  y/\nB/BA/
'
BBYAA

関連情報