
Найдите в файле последнюю строку с заданной строкой, чтобы добавить строку после этого совпадения. Пример:
Вход:
Apple
Banana
Apple
Orange
Apple
Grapefruit
Выход:
Apple
Banana
Apple
Orange
Apple
I am new string replaced at last apple
Grapefruit
решение1
С помощью GNU sed
вы можете сделать это:
sed ':a;N;$! ba;s/.*\nApple/&\nYour appended line/'
Шаблон :a;N;$! ba
добавляет строки из файла в буфер с помощью N
пока конец файла ( $
адрес) не !
достигнут ( ) (перейти к :a
с помощью ba
). Таким образом, если вы выходите из цикла, все строки находятся в буфере и шаблон поиска .*\nApple
соответствует всему файлу до последней строки, начинающейся с Apple
. Затем &
в строку замены вставьте все совпадение и добавьте ваш текст.
Портативное решение, работающее sed
и на других версиях, было бы
sed -e ':a' -e 'N;$! ba' -e 's/.*\(\n\)Apple/&\1Your appended line/'
Здесь скрипт разделен на метки, и вместо использования \n
в заменяющей строке (которая не гарантирует работу с не-GNU sed
s) мы ссылаемся на ту из шаблона, окруженную \(\)
ссылкой \1
.
решение2
Я видел этот примерздесь
sed -i -e "\$aTEXTTOEND" <filename>
Информация:
i: edit files in place
e: add the script to the commands to be executed
$: sed address location
a: append command
TEXTTOEND: text to append to end of file
решение3
Я предполагаю, что ваш пример входных данных — это файл с именем fruits.txt
.
Если вы хотите вставить строку после последнего вхождения слова «Apple», это можно сделать sed
следующим образом:
sed -rz 's/(^(.*\n)?Apple)(\n|$)/\1\ninserted line\n/'
Опция -r
включает расширенные регулярные выражения.
Опция -z
указывает sed
использовать символы NUL (код ASCII 0) в качестве разделителей строк вместо обычных символов новой строки. Таким образом, весь текстовый файл будет обработан сразу, а не построчно.
Команда ищет регулярное выражение (расширенное, из-за sed
) и заменяет его вычисленной строкой.s/PATTERN/REPLACEMENT/
-r
PATTERN
REPLACEMENT
Регулярное выражение (^(.*\n)?Apple)(\n|$)
соответствует любому количеству строк от начала ( ^
) до последнего вхождения с Apple
последующим переносом строки ( \n
) или концом файла ( $
).
Теперь все это совпадение заменяется на \1\ninserted line\n
, где \1
обозначает исходное содержимое первой группы совпадений, т. е. все до Apple
. Таким образом, на самом деле он вставляет inserted line
предшествующий и последующий перенос строки ( \n
) сразу после последнего вхождения "Apple".
Это также работает, если строка «Apple» является первой, последней или единственной строкой в файле.
Пример входного файла (добавил одну строку к вашему примеру, чтобы сделать поведение понятнее):
Apple
Banana
Apple
Orange
Apple
Cherry
Пример вывода:
Apple
Banana
Apple
Orange
Apple
inserted line
Cherry
Если вы довольны результатом и хотите отредактировать его fruits.txt
на месте, а не просто вывести изменение, вы можете добавить -i
опцию:
sed -irz 's/(^(.*\n)?Apple)(\n|$)/\1\ninserted line\n/'
Однако если вы просто хотите добавить строку текста в конец файла, sed
это не оптимальный инструмент.
>>
Вы можете просто использовать перенаправление вывода Bash :
echo "I am new string replaced at last apple" >> fruits.txt
Будет >>
брать стандартный вывод команды слева ( echo
здесь команда просто печатает свои аргументы в стандартный вывод) и добавлять его в файл, указанный справа. Если файл еще не существует, он будет создан.
решение4
В этом ответе:
- sed+tac (двойное изменение входа)
- AWK с двойным входом
- Perl с двойным вводом
- Альтернативный Perl с двойным инверсированием ввода
СЭД + ТАК
В этом подходе используются 3 процесса: сначала файл реверсируется, затем используется sed для поиска первого совпадения и вставки нужного текста после первого совпадения, а затем текст снова реверсируется.
$ tac input.txt | sed '0,/Apple/{s/Apple/NEW THING\n&/}' | tac
Apple
Banana
Apple
Orange
Apple
NEW THING
something else
something other entirely
blah
Основная идея та же, что и в альтернативной версии perl, написанной ниже: первое совпадение в обратном списке строк является последним совпадением в исходном списке. Преимущество этого подхода заключается в простоте и читабельности с точки зрения пользователя.
Этот подход хорошо работает для небольших файлов, но он громоздкий для больших файлов и, вероятно, потребует значительного времени для обработки большого количества строк.
АВК
Awk может быть немного удобнее для того, что вы пытаетесь сделать:
$ awk -v n="AFTER LAST APPLE" 'NR==FNR&&/Apple/{l=NR};NR!=FNR{if(FNR==l){print $0;print n}else print}' input.txt input.t>
Apple
Banana
Apple
Orange
Apple
AFTER LAST APPLE
something else
something other entirely
blah
Основная идея здесь заключается в том, чтобы дважды передать входной файл в awk — сначала для поиска последнего яблока, а затем для вставки текста при прохождении позиции последнего «яблока», найденного нами в первой итерации.
Ключ к выполнению этой работы — оценка переменных NR
(которая продолжает подсчитывать все обработанные строки, итоги) и FNR
(номер строки в текущем файле). Когда эти два файла не равны, мы знаем, что обрабатываем второй файл, таким образом, мы знаем, что мы сделали наш первый проход по поиску местоположения последнего появления Apple.
Поскольку мы считываем файл дважды, производительность будет зависеть от размера файла, но это лучше, чем комбинация sed + tac, поскольку нам не нужно дважды откатывать файл.
Перл
perl -sne '$t+=1;$l=$. if /Apple/ and $.==$t;
if($t!=$.){if($.==$l){print $_ . $n . "\n";}else{print $_}};
close ARGV if eof;' -- -n="AFTER LAST APPLE" input.txt input.txt
Решение perl следует той же идее, что и AWK. Мы передаем входные данные дважды и используем первый раз, когда файл передается, чтобы найти, где находится последнее вхождение Apple, и сохранить его номер строки в $l
переменной. Отличие от awk здесь в том, что в perl нет переменной, поэтому для этой цели FNR
нам приходится использовать $t+=1
и .close ARGV if eof
Что здесь также отличается, так это то, что с помощью -s
switch perl позволяет передавать аргументы. В этом случае строка, которую мы хотим вставить, передается через -n
switch.
Альтернативный Perl
Вот еще один способ сделать это в Perl. Идея заключается в том, чтобы просто прочитать файл в массив строк и перевернуть этот массив. Первое совпадение в перевернутом массиве является последним в исходном списке строк. Таким образом, мы можем вставить нужный текст перед этой строкой и снова перевернуть список:
$ perl -le '@a1=<>;END{do{push @a2,"NEW LINE\n" and $f=1 if $_ =~ /Apple/ and !$f; push @a2,$_} for reverse(@a1); print reverse(@a2)}' input.txt
Apple
Banana
Apple
Orange
Apple
NEW LINE
something else
something other entirely
blah