Пример:
Вход:
Apple
Banana
Apple
Orange
Apple
Выход:
Apple
Banana
Apple
Orange
Apple
I am new string replaced at last apple
Я видел этот пример здесь
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
С помощью 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
.
Я предполагаю, что ваш пример ввода - это файл с именем 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
здесь просто выводит свои аргументы в стандартный вывод) и добавляет его в указанный файл. Если файл еще не существует, он будет создан.
В этом ответе:
Этот подход использует 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
Основная идея совпадает с альтернативной версией жемчуга, записанной ниже: первое соответствие в обратном списке строк, последнее соответствие в исходном списке. Преимуществом этого подхода является простота и удобочитаемость, что касается пользователя.
Этот подход будет работать хорошо на маленькие файлы, но это является громоздким для больших файлов и вероятно займет большое количество времени для обработки большой суммы строк.
Awk может быть небольшим friendliner для того, что Вы пытаетесь сделать:
$ 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 дважды - сначала для нахождения, где последняя Apple, и вторая для вставки текста, когда мы передаем положение того последнего "яблока", которое мы нашли от первого повторения.
Ключ к созданию этой работы должен оценить 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
Решение для жемчуга следует за той же идеей как AWK один. Мы передаем вход дважды и используем первый раз, когда файл передается для нахождения, где последнее вхождение Apple, и сохраните ее номер строки в $l
переменная. То, что отличается от awk здесь, то, что существует нет FNR
переменная в жемчуге, следовательно мы должны использовать $t+=1
и close ARGV if eof
с этой целью.
Что также отличается, вот это с -s
жемчуг переключателя позволяет Вам передавать аргументы. В этом случае строка, которую мы хотим вставить, передается через -n
переключатель.
Вот еще один способ сделать это в 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