Пример:
Вход:
Apple
Banana
Apple
Orange
Apple
Выход:
Apple
Banana
Apple
Orange
Apple
I am new string replaced at last apple
Я предполагаю, что ваш пример ввода - это файл с именем 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, за которым следует строка break (\n) или конец файла ($).
Теперь все это соответствие заменяется на \1\ninserted line\n, где \1 обозначает исходное содержимое первой группы совпадений , т.е. все до Apple. Таким образом, на самом деле он вставляет inserted line, предшествующий и сопровождаемый прерыванием строки (\n) сразу после последнего появления «Яблока».
Это также работает, если строка «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 команда здесь просто выводит свои аргументы на стандартный вывод) и добавляет его в указанный файл справа. Если файл еще не существует, он будет создан.
Я видел этот пример здесь
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. [[12]] в заменяющей строке вставляют все совпадения и добавляет ваш текст.
Переносимым решением, работающим с другими sed версиями, было бы
sed -e ':a' -e 'N;$! ba' -e 's/.*\(\n\)Apple/&\1Your appended line/'
сценарий разбивается на метки и вместо использования \n в заменяющей строке (которая не гарантируется для работы с не-GNU sed s), мы ссылаемся на один из шаблона, окруженного \(\), с \1 ссылка.
В этом ответе:
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 дважды - сначала, чтобы найти, где находится последнее 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
Решение perl следует той же идее, что и AWK. Мы передаем вход дважды и используем первый раз, когда файл передается, чтобы найти, где находится последнее вхождение Apple, и сохранить его номер строки в переменной $l. В отличие от awk здесь нет переменной FNR в perl, поэтому для этой цели мы должны использовать $t+=1 и close ARGV if eof.
То, что здесь также отличается, это то, что с -s switch perl позволяет передавать аргументы. В этом случае строка, которую мы хотим вставить, передается через переключатель -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
Я предполагаю, что ваш пример ввода - это файл с именем 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, за которым следует строка break (\n) или конец файла ($).
Теперь все это соответствие заменяется на \1\ninserted line\n, где \1 обозначает исходное содержимое первой группы совпадений , т.е. все до Apple. Таким образом, на самом деле он вставляет inserted line, предшествующий и сопровождаемый прерыванием строки (\n) сразу после последнего появления «Яблока».
Это также работает, если строка «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 команда здесь просто выводит свои аргументы на стандартный вывод) и добавляет его в указанный файл справа. Если файл еще не существует, он будет создан.
Я видел этот пример здесь
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. [[12]] в заменяющей строке вставляют все совпадения и добавляет ваш текст.
Переносимым решением, работающим с другими sed версиями, было бы
sed -e ':a' -e 'N;$! ba' -e 's/.*\(\n\)Apple/&\1Your appended line/'
сценарий разбивается на метки и вместо использования \n в заменяющей строке (которая не гарантируется для работы с не-GNU sed s), мы ссылаемся на один из шаблона, окруженного \(\), с \1 ссылка.
В этом ответе:
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 дважды - сначала, чтобы найти, где находится последнее 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
Решение perl следует той же идее, что и AWK. Мы передаем вход дважды и используем первый раз, когда файл передается, чтобы найти, где находится последнее вхождение Apple, и сохранить его номер строки в переменной $l. В отличие от awk здесь нет переменной FNR в perl, поэтому для этой цели мы должны использовать $t+=1 и close ARGV if eof.
То, что здесь также отличается, это то, что с -s switch perl позволяет передавать аргументы. В этом случае строка, которую мы хотим вставить, передается через переключатель -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