Search the file for the last line with a given String to add a line after that match. Example:
Input:
Apple
Banana
Apple
Orange
Apple
GrapefruitOutput:
Apple
Banana
Apple
Orange
Apple
I am new string replaced at last apple
Grapefruit 7 4 Answers
With GNU sed you can do this:
sed ':a;N;$! ba;s/.*\nApple/&\nYour appended line/'The pattern :a;N;$! ba appends lines from the file to the buffer with N while the end of the file ($ address) is not (!) reached (jump to :a with ba). So if you exit the loop, all lines are in the buffer and search pattern .*\nApple matches the whole file up to the last line starting with Apple. The & in the replacement string insert the whole match and appends your text.
A portable solution working on other sed versions as well would be
sed -e ':a' -e 'N;$! ba' -e 's/.*\(\n\)Apple/&\1Your appended line/'Here the script is split at the labels and instead of using \n in the replacement string (which is not guaranteed to work with non-GNU seds) we refer to the one from the pattern surrounded with \(\) with the \1 reference.
I saw this example here
sed -i -e "\$aTEXTTOEND" <filename>Information:
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 5 I assume that your example input is a file named fruits.txt.
If you want to insert a line after the last occurrence of "Apple", this can be done with sed like this:
sed -rz 's/(^(.*\n)?Apple)(\n|$)/\1\ninserted line\n/'The -r option enables extended regular expressions.
The -z option tells sed to take NUL characters (ASCII code 0) as line separators instead of the normal newline characters. This way, the whole text file will be processed at once instead of line by line.
The sed command s/PATTERN/REPLACEMENT/ searches for the (extended, because of -r) regular expression in PATTERN and replaces it with the evaluated REPLACEMENT string.
The regular expression (^(.*\n)?Apple)(\n|$) matches any number of lines from the beginning (^) to the last occurrence of Apple followed by a line break (\n) or the end of the file ($).
Now this whole match gets replaced with \1\ninserted line\n, where \1 stands for the original content of the first match group, i.e. everything up to Apple. So actually it inserts inserted line preceded and followed by a line break (\n) right after the last occurrence of "Apple".
This also works if the "Apple" line is the first, last or only line in the file.
Example input file (added one line to your example, to make the behaviour clear):
Apple
Banana
Apple
Orange
Apple
CherryExample output:
Apple
Banana
Apple
Orange
Apple
inserted line
CherryIf you are happy with the result and want to edit fruits.txt in place, instead of just outputting the modification, you can add the -i option:
sed -irz 's/(^(.*\n)?Apple)(\n|$)/\1\ninserted line\n/'If you just want to append a line of text to the end of that file though, sed is not the optimal tool.
You can simply use Bash's >> appending output redirection then:
echo "I am new string replaced at last apple" >> fruits.txtThe >> will take the standard output of the command on its left (the echo command here simply prints its arguments to standard output) and appends it to the file specified on is right. If the file does not exist yet, it will be created.
In this answer:
- sed+tac (double input reversal)
- AWK with double input
- Perl with double input
- Alternative Perl with double input reversal
sed + tac
This approach uses 3 processes, first reversing the file, using sed to find first match and inserting desired text after the first match, and reversing text again.
$ tac input.txt | sed '0,/Apple/{s/Apple/NEW THING\n&/}' | tac
Apple
Banana
Apple
Orange
Apple
NEW THING
something else
something other entirely
blahBasic idea is the same as alternative perl version written below: the first match in reverse list of lines, is the last match in the original list. The advantage of this approach is simplicity and readability as far as the user is concerned.
This approach will work well for small files, but it is cumbersome for large files and likely will take considerable time to process big amount of lines.
AWK
Awk can be a little friendliner for what you're trying to do:
$ 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
blahBasic idea here is to give your input file to awk twice - first to find where the last Apple is, and second to insert the text when we pass position of that last "apple" that we found from the first iteration.
The key to making this work is to evaluate NR (which keeps counting all processed lines, totals) and FNR (line number in current file) variables. When these two files are unequal, we know that we're processing the second file, thus we know that we have made our first pass of finding the location of the last occurrence of Apple.
Since we're reading the file twice, the performance will depend on the size of the file, but it's better than sed + tac combination, since we don't need to reverse the file twice.
Perl
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.txtThe perl solution follows the same idea as the AWK one. We pass the input twice, and use the first time the file is passed to find where is the last occurrence of Apple and store its line number into $l variable. What is different from awk here, is that there's no FNR variable in perl, hence we have to use $t+=1 and close ARGV if eof for that purpose.
What is also different here is that with -s switch perl allows you to pass arguments. In this case the string that we want to insert is passed via -n switch.
Alternative Perl
Here's yet another way to do it in Perl. The idea is to simply read the file into array of lines, and reverse that array. The first match in the reversed array is the last in the original list of lines. Thus, we can insert a desired text before that line, and reverse the list again:
$ 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 4