文字列リストとその代替リストに基づいてファイル内の文字列を置き換える

文字列リストとその代替リストに基づいてファイル内の文字列を置き換える

次の文字列を置き換えようとしています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 Afile Bfile C

これまで私がしたこと:

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

file B& に 1 行だけあれば有効でfile C、 2 行以上あれば有効ではありません。

答え1

最も簡単な方法sedは、これら2つのリストを処理して1つに置き換えることです。スクリプトファイル例えば

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編集します。適切アプローチは、最初にLHS/を処理しRHS、その行に現れる可能性がある特殊文字をエスケープしてから、区切り文字とwithなどを組み合わせて追加し、LHS結果RHSを次にパイプすることです。s/gpastesed

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

したがって、onepasteとthreeは、sed行数に関係なく各ファイルを一度だけ処理します。
これは、シェルがプロセス置換をサポートし、以下をsed読み取ることができると仮定します。スクリプトファイル~から標準入力。また、その場で編集できません。 (-iすべてのsedバージョンがサポートしているわけではないので、スイッチは省略しました。)

答え2

置換を互いに独立して実行するには、次の手順を実行します。

foo -> bar
bar -> foo

に適用

foobar

その結果:

barfoo

foofoo無邪気な翻訳とは異なり、s/foo/bar/g; s/bar/foo/g次のようにすることができます。

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 

これperlはで予想される正規表現ですpatterns.txt。 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 

答え3

各ターゲット単語がファイル内で一度だけ発生することを示すこの簡単な例では、単に次のことができます。

$ 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コマンドは、結合された2つのファイルのデータを印刷します。

$ paste fileB fileC
Peter   John
dad wife
mom grandpa

while read各行を繰り返す単純なループを介してfileBas$afileCasの値を保存します$b。次に、コマンドはsed最初の項目を 。これを3回繰り返します。$a$b

ターゲット単語がファイルに一度だけ表示されることを知っていて(そうでない場合は、置き換える必要がある単語を決定するために使用できる追加の詳細を提供する必要があります)、ファイルが小さい場合、このアプローチはお勧めします。 、あなたが示したように。大きなファイルの場合は、各単語のペアに対して一度実行する必要があるため、時間がかかり、非常に非効率的です。

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

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

答え4

私が作ったソリューションはそれほど短くはありませんが、十分にシンプルで読みやすいです。あなたの使命がsedですべてのことをするのではない場合...?

 #!/usr/bin/bash

 cp A.txt D.txt

 x=1
 length=$(wc -l B.txt | sed 's/\ .*//g')

 until [ $x -eq $length ]; do

    Bx=$(awk "NR==$x" B.txt)
    Cx=$(awk "NR==$x" C.txt)

    sed -i "s/$Bx/$Cx/g" D.txt

    x=$(($x+1))

 done

 rm -f ./sed*

B.txtがC.txtより長い場合、またはその逆の場合、このスクリプトは多くのジャンクファイルを生成することに注意してください(まだテストしていません)。

関連情報