web-dev-qa-db-ja.com

文字列のリストと対応する置換のリストに基づいてファイル内の文字列を置換します

file Aの文字列を置き換えようとしています:

Hello Peter, how is your dad? where is mom? 

置換される文字列はfile Bにあります:

Peter
dad
mom

それに対応する置換はfile Cにあります:

John
wife
grandpa

期待される結果:

Hello John, how is your wife? where is grandpa?

file Aの対応する行の値を使用してfile Bの値を置き換え、file Cを編集できますか?

これまでに行ったこと:

 cat 1.txt | sed -e "s/$(sed 's:/:\\/:g' 2.txt)/$(sed 's:/:\\/:g' 3.txt)/" > 4.txt

file Bfile Cに1行しかない場合は機能しますが、複数行がある場合は機能しません。

5
Robert Choy

sedを使用してこれを行う最も簡単な方法は、これらの2つのリストを処理してscript-fileに変換することです。

s/line1-from-fileB/line1-from-fileC/g
s/line2-from-fileB/line2-from-fileC/g
....................................
s/lineN-from-fileB/lineN-from-fileC/g

次にsedが実行され、fileAが編集されます。 properの方法は、最初にLHS/RHSを処理し、それらの行に出現する可能性のある特殊文字をエスケープしてから、LHSRHSを結合して、s、区切り文字/およびgを追加します(例:paste )と結果をsedにパイプします:

paste -ds///g /dev/null /dev/null \
<(sed 's|[[\.*^$/]|\\&|g' fileB) <(sed 's|[\&/]|\\&|g' fileC) \
/dev/null /dev/null | sed -f - fileA

つまり、行数に関係なく、各ファイルを1回だけ処理するpaste 1つとsed 3つです。
これは、シェルがプロセス置換をサポートし、sedscript-filestdinから読み取ることができることを前提としています。また、インプレースで編集されません(-iスイッチはすべてのsedsでサポートされていないため、省略しました)。

9
don_crissti

交換を互いに独立して行う場合は、たとえば次のようにします。

foo -> bar
bar -> foo

に適用

foobar

結果として:

barfoo

素朴なs/foo/bar/g; s/bar/foo/gのようなfoofooとは対照的に、次のようにすることができます。

Perl -pe '
  BEGIN{
    open STRINGS, "<", shift@ARGV or die"STRINGS: $!";
    open REPLACEMENTS, "<", shift@ARGV or die "REPLACEMENTS: $!";
    while (defined($a=<STRINGS>) and defined($b=<REPLACEMENTS>)) {
      chomp ($a, $b);
      Push @repl, $b;
      Push @re, "$a(?{\$repl=\$repl[" . $i++. "]})"
    }
    eval q($re = qr{) . join("|", @re) . "}";
  }
  s/$re/$repl/g' strings.txt replacements.txt fileA 

patterns.txtではPerl正規表現が必要です。 Perl正規表現は任意のコードを実行できるため、それらを無害化することが重要です。固定文字列のみを置き換える場合は、次のように変更できます。

Perl -pe '
  BEGIN{
    open PATTERNS, "<", shift@ARGV or die"PATTERNS: $!";
    open REPLACEMENTS, "<", shift@ARGV or die "REPLACEMENTS: $!";
    for ($i = 0; defined($a=<PATTERNS>) and defined($b=<REPLACEMENTS>); $i++) {
      chomp ($a, $b);
      Push @string, $a;
      Push @repl, $b;
      Push @re, "\\Q\$string[$i]\\E(?{\$repl=\$repl[$i]})"
    }
    eval q($re = qr{) . join("|", @re) . "}";
  }
  s/$re/$repl/g' patterns.txt replacements.txt fileA 
2

単純な例では、ターゲット単語のそれぞれがファイル内で1回だけ出現する場所を示しています。

$ paste fileB fileC | while read a b; do sed -i "s/$a/$b/" fileA; done
$ cat fileA
Hello John, how is your wife? where is grandpa? 

pasteコマンドは、両方のファイルのデータを結合して出力します。

$ paste fileB fileC
Peter   John
dad wife
mom grandpa

これを単純なwhile readループに通して、すべての行を反復し、fileBの値を$aとして保存し、fileCの値を$bとして保存します。次に、sedコマンドは、最初に出現する$a$bに置き換えます。これを3回繰り返します。

ターゲットワードがファイルに1度だけ現れることがわかっている場合(そうでなければ、置換する必要があるオカレンスを識別するために使用できる詳細を提供する必要があります)、ファイルが小さい場合、このアプローチは問題ありません。 、あなたが示したように。大きなファイルの場合、これには長い時間がかかり、単語のペアごとに1回実行する必要があるため、非常に非効率的です。

したがって、大きなファイルがある場合は、代わりに次のようなものが必要になることがあります。

paste fileB fileC | 
    Perl -lane '$words{$F[0]}=$F[1]} 
        END{open(A,"fileA"); while(<A>){s/$_/$words{$_}/ for keys %words; print}'
1
terdon