Как заменить различное количество строк в строках, соответствующих шаблонам

File1

<filename1.txt> stringA string1
<filename2.txt> stringA string2
<filename2.txt> stringB string3

File2

<filename1.txt>      words and symbols < $ stringA words and symbols 9 
<filename2.txt>      more words and symbols % @ stringA words and symbols stringB

File2 Transformed

<filename1.txt>      words and symbols < $ string1 words and symbols 9
<filename2.txt>      more words and symbols % @ string2 words and symbols string3

Мой подход состоит в том, чтобы перебирать строки File1 для установки переменных. Итак, для строки 1 переменные устанавливаются следующим образом:

filenamevar = <filename1.txt>
string_old_var = stringA 
string_new_var = string1 

Затем для строк grep, которые соответствуют filenamevar и string_old_var в качестве входных данных для sed. Здесь мне нужна помощь (если вы не думаете, что есть лучший способ в целом).

Ближайшим пока является

grep -e "$filenamevar.*$string_old_var" File2.txt | sed s/$string_old_var/$string_new_var/ >> File2Transformed.txt

, который работает, за исключением того, что вторая строка выводится дважды:

<filename1.txt>      words and symbols < $ string1 words and symbols 9 
<filename2.txt>      more words and symbols % @ string2 words and symbols stringB
<filename2.txt>      more words and symbols % @ stringA words and symbols string3

Я также пробовал

grep -e "$filenamevar.*$string_old_var" File2 | sed s/$string_old_var/$string_new_var/

и

sed -i s/$string_old_var/$string_new_var/ $(grep -e "$filenamevar.*$string_old_var" File2)

, но ни один из них не дает желаемых результатов.

Вот цикл, который я использую

Numlines=$(grep "" -c File1.txt) 
for (( line=1; line<=$Numlines; line++ )) ; do 
 filenamevar=$(awk -v line=$line 'NR == line {print $1}' File1.txt) 
 string_old_var=$(awk -v line=$line 'NR == line {print $2}' File1.txt)
 string_new_var=$(awk -v line=$line 'NR == line {print $3}' File1.txt)
 # insert proper sed grep code to test here
done
0
задан 7 November 2020 в 15:30

1 ответ

Если вы решили использовать для этого цикл оболочки, вы можете сделать что-то вроде

while read -r fname patt repl; do 
  sed -i.bak "/$fname/s/^$patt/$repl/" File2
done < File1

,но обратите внимание, что он может неожиданно прерваться, если какая-либо из ваших строк содержит специальные последовательности символов регулярного выражения. Другим вариантом (с тем же предупреждением о специальных символах) может быть что-то вроде

awk '{printf "/^%s/s/%s/%s/\n",$1,$2,$3}' File1 | sed -f- File2

, который использует awk для форматирования File1 в последовательность команд, которые затем передаются в sed -f для изменить файл2.

С помощью GNU awk (он же gawk), который поддерживает двумерные массивы, вы можете просто сделать:

$ gawk '
    NR==FNR {a[$1][$2] = $3; next} 
    $1 in a {for(i=2;i<=NF;i++) $i = $i in a[$1] ? a[$1][$i] : $i} 
    1
  ' File1 File2
<filename1.txt> words and symbols < $ string1 words and symbols 9
<filename2.txt> more words and symbols % @ string2 words and symbols string3

или, если вам нужно сохранить выравнивание, используйте функцию index с substr:

$ gawk '
    NR==FNR {a[$1][$2] = $3; next}
    $1 in a {
      for(s in a[$1]) {
        mstart = index($0,s);
        if(mstart > 0) $0 = substr($0,1,mstart-1) a[$1][s] substr($0,mstart+length(s))}
    }
    1
  ' File1 File2
<filename1.txt>      words and symbols < $ string1 words and symbols 9
<filename2.txt>      more words and symbols % @ string2 words and symbols string3

Похожая вещь в vanilla awk, подделка 2D-массива с помощью строки с разделителями FS:

mawk '
  NR==FNR {a[$1 FS $2] = $3; next}
  {
    for(k in a) {
      split(k,b);
      if($1 == b[1]) {
        mstart = index($0,b[2]);
        if(mstart > 0) $0 = substr($0,1,mstart-1) a[k] substr($0,mstart+length(b[2]));
      }
    }
  }
  1 
' File1 File2
1
ответ дан 30 October 2020 в 22:55

Другие вопросы по тегам:

Похожие вопросы: