Как сделать замену sed (s///g) на основе списка? Мне нужно поменять местами несколько слов, с другими соответствующими словами

Как сделать замену sed (s///g) на основе списка? Мне нужно поменять местами несколько слов, с другими соответствующими словами

Я не думаю, что этот вопрос задавался ранее, поэтому я не знаю, sedспособен ли он на это.

Предположим, у меня есть несколько цифр в предложении, которые мне нужно разложить на слова. Практическим примером может служить преобразование пронумерованных цитат в типичном эссе в формат MLA:

essay.txt:

Sentence 1 [1]. sentence two [1][2]. Sentence three[1][3].

Key.txt(это файл с разделителями табуляции):

1   source-one
2   source-two
3   source-three
...etc

Ожидал Result.txt:

Sentence 1 [source-one]. sentence two [source-one][source-two]. Sentence three[source-one][source-three]

Вот моя попытка написать псевдокод, но я недостаточно разбираюсь в нем sedили не trмогу сделать его правильно:

 cat essay.txt | sed s/$(awk {print $1} key.txt)/$(awk {print $2} key.txt)/g

PS: Если в notepad++ есть трюк для массового поиска и замены с использованием нескольких терминов, это было бы здорово. В таком виде, похоже, что поиск и замена работают только для одного термина за раз, но мне нужен способ сделать это массово для многих терминов одновременно.

решение1

Вместо этого следует использовать perl:

$ perl -ne '
  ++$nr;
  if ($nr == $.) {
    @w = split;
    $k{$w[0]} = $w[1];
  }
  else {
    for $i (keys %k) {
      s/(\[)$i(\])/$1.$k{$i}.$2/ge
    }
    print;
  }
  close ARGV if eof;
' key.txt essay.txt
Sentence 1 [source-one]. sentence two [source-one][source-two]. Sentence three[source-one][source-three]

решение2

awkможет эффективно делать то же самое, что и perlздесьнемного проще, хотя реализации, отличные от GNU, могут тратить немного процессорного времени на ненужное разделение (большого?) текстового файла:

awk 'NR==FNR{a["\\["$1"\\]"]="["$2"]";next} {for(k in a) gsub(k,a[k]);print}' key.txt essay.txt

Так как вы просилиобъяснение:

  • awkработает, принимая «скрипт», состоящий из пар шаблон-действие, затем считывает один или несколько файлов (или стандартный ввод) по одной «записи» за раз, где по умолчанию каждая запись является строкой, и для каждой записи разбивает ее на поля по умолчанию по пробелу (включая табуляцию) и применяет скрипт, по очереди (если не указано иное) проверяя каждый шаблон (который часто смотрит на текущую запись и/или ее поля) и, если он соответствует, выполняя действие (которое часто что-то делает с указанной записью и/или полями). Здесь я указываю два файла, key.txt essay.txtпоэтому он считывает эти два файла в этом порядке, строка за строкой. Скриптможетможно поместить в файл, а не в командную строку, но здесь я решил этого не делать.

  • первый шаблон — NR==FNR. NR— встроенная переменная, которая является номером обрабатываемой записи; FNRаналогично — номер записи в текущем входном файле. Для первого файла ( key.txt) они равны; для второго файла (и любых других) они не равны

  • первое действие — {a["\\["$1"\\]"]="["$2"]";next}. awkимеет «ассоциативные» или «хешированные» массивы; arrayname[subexpr]где subexpr— строковое выражение, считывает или задает элемент массива. $numberнапример $1 $2, и т. д. ссылаются на поля и $0ссылаются на всю запись. Согласно вышеизложенному, это действие выполняется только для строк, key.txtтак что, например, в последней строке этого файла $1есть 3и $2есть source-three, и это сохраняет запись массива с индексом \[3\]и содержимым [source-three]; см. ниже, почему я выбрал эти значения. "\\["И "\\]"— это строковые литералы, использующие escape-символы, фактические значения которых \[и , \]тогда как "[" "]"есть только [ ], и строковые операнды без оператора между ними объединяются. Наконец, это действие выполняется, nextчто означает пропуск оставшейся части скрипта для этой записи, просто вернитесь к началу цикла и начните со следующей записи.

  • второй шаблон пуст, поэтому он соответствует каждой строке во втором файле и выполняет действие {for(k in a) gsub(k,a[k]);print}. for(k in a)Конструкция создает цикл, во многом похожий на то, что делают оболочки типа Bourne в for i in this that other; do something with $i; done, за исключением того, что здесь значения kявляютсяиндексымассива a. Для каждого такого значения он выполняет gsub(глобальную замену), которая находит все совпадения заданного регулярного выражения и заменяет их заданной строкой; я выбрал индексы и содержимое в массиве (выше), так что, например, \[3\]это регулярное выражение, которое соответствует текстовой строке [3], и [source-three]это текстовая строка, которую вы хотите заменить для каждого такого совпадения. по умолчанию gsubработает с текущей записью $0. После выполнения этой замены для всех значений в aнем выполняется print, который по умолчанию выводит $0то, что есть сейчас, со всеми желаемыми выполненными заменами.

Примечание: GNU awk (gawk), который особенно распространен в Linux, но не универсален, имеет оптимизацию, при которой он фактически не выполняет разделение полей, если ни один из шаблонов или выполняемых действий не нуждается в значениях полей. В других реализациях может быть потрачено небольшое количество процессорного времени, чего perlметод cuonglm избегает, но если ваши файлы не огромны, это, скорее всего, даже не будет заметно.

решение3

bash$ sed -f  <( sed -rn 's#([0-9]+)\s+(.*)#s/\\[\1]/[\2]/g#p' key.txt ) essay.txt

Sentence 1 [source-one]. sentence two [source-one][source-two]. Sentence three[source-one][source-three].

решение4

Для достижения этого можно использовать подстановку sed на месте внутри цикла:

$ cp essay.txt Result.txt
$ while read n k; do sed -i "s/\[$n\]/\[$k\]/g" Result.txt; done < key.txt
$ cat Result.txt 
Sentence 1 [source-one]. sentence two [source-one][source-two]. Sentence three[source-one][source-three].

Связанный контент