%20%D0%BD%D0%B0%20%D0%BE%D1%81%D0%BD%D0%BE%D0%B2%D0%B5%20%D1%81%D0%BF%D0%B8%D1%81%D0%BA%D0%B0%3F%20%D0%9C%D0%BD%D0%B5%20%D0%BD%D1%83%D0%B6%D0%BD%D0%BE%20%D0%BF%D0%BE%D0%BC%D0%B5%D0%BD%D1%8F%D1%82%D1%8C%20%D0%BC%D0%B5%D1%81%D1%82%D0%B0%D0%BC%D0%B8%20%D0%BD%D0%B5%D1%81%D0%BA%D0%BE%D0%BB%D1%8C%D0%BA%D0%BE%20%D1%81%D0%BB%D0%BE%D0%B2%2C%20%D1%81%20%D0%B4%D1%80%D1%83%D0%B3%D0%B8%D0%BC%D0%B8%20%D1%81%D0%BE%D0%BE%D1%82%D0%B2%D0%B5%D1%82%D1%81%D1%82%D0%B2%D1%83%D1%8E%D1%89%D0%B8%D0%BC%D0%B8%20%D1%81%D0%BB%D0%BE%D0%B2%D0%B0%D0%BC%D0%B8.png)
Я не думаю, что этот вопрос задавался ранее, поэтому я не знаю, 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].