I would like to insert new string at last line staring with some text using sed,

9,908

Solution 1

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.

Solution 2

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

Solution 3

In this answer:

  1. sed+tac (double input reversal)
  2. AWK with double input
  3. Perl with double input
  4. 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
blah

Basic 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
blah

Basic 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.txt

The 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

Solution 4

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
Cherry

Example output:

Apple
Banana
Apple
Orange
Apple
inserted line
Cherry

If 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.txt

The >> 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.

Share:
9,908

Related videos on Youtube

user1270846
Author by

user1270846

Updated on September 18, 2022

Comments

  • user1270846
    user1270846 over 1 year

    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
    Grapefruit
    

    Output:

    Apple
    Banana
    Apple
    Orange
    Apple
    I am new string replaced at last apple
    Grapefruit
    
    • Byte Commander
      Byte Commander almost 7 years
      So that example input is a file and you want to append a line of text to the end of that file?
    • Admin
      Admin almost 7 years
      I have a feeling it will not be that simple, BC...
    • Philippos
      Philippos almost 7 years
      The line should follow the "last apple", which may be at any position in the file.
    • Sergiy Kolodyazhnyy
      Sergiy Kolodyazhnyy almost 7 years
      @Philippos Quality of what is asked directly correlates to the answers one gets. The author of this question themselves didn't specify this as requirement. While aiming at "any position in the file" is nice goal, it's not been asked by the owner of this question.
    • Philippos
      Philippos almost 7 years
      @SergiyKolodyazhnyy Did you read the title? The question was not to append to the end of the file, but after the last line starting with some text. The example says "after last apple". Just read.
    • Sergiy Kolodyazhnyy
      Sergiy Kolodyazhnyy almost 7 years
      @Philippos Can be interpreted that way, sure, but again - OP did not state so themselves.
    • Philippos
      Philippos almost 7 years
      As you please. I won't waste time to discuss. Downvote The question if you find it unclear.
  • Philippos
    Philippos almost 7 years
    This work only if Apple is the last line. This is true for the example, but probably not for each possible file.
  • Byte Commander
    Byte Commander almost 7 years
    @Philippos I added a way to insert a line after the last occurrence of "Apple". If you were the one who downvoted, please consider undoing that based on the current state of my answer. Thanks.
  • Philippos
    Philippos almost 7 years
    I didn't test, but if the keyword was in the last row, this would append after the next-to-last occurrence, right? But I'm not experienced with -z. And the trailing part is superfluous as you don't need to replace it you can leave it away.
  • Byte Commander
    Byte Commander almost 7 years
    You're right, thanks for the suggestions. I modified the command a bit.
  • Philippos
    Philippos almost 7 years
    You missed that the line is not always to be appended to the end of the file, but after the last occurrence of the word apple
  • Byte Commander
    Byte Commander almost 7 years
    "Doing it with sed might be very difficult. Awk can be a little friendliner for what you're trying to do" - looking at my (and others') sed solution, which is barely half as long and cryptic as your awk command... ;-)
  • Sergiy Kolodyazhnyy
    Sergiy Kolodyazhnyy almost 7 years
    @ByteCommander OK, that's a given, my solution might be lengthier, but it works ;)
  • Sergiy Kolodyazhnyy
    Sergiy Kolodyazhnyy almost 7 years
    @Philippos The answer provides a solution to match the example that OP provided. Again, as I mentioned in another comment, answers match what is being asked. Second of all, I don't see reason why you are asking this to be the requirement, if OP himself did not post that as requirement.
  • Philippos
    Philippos almost 7 years
    It's part of his question ("after last apple") and even in the title. I agree that he should have provided more than one example, but you can't rely on the examples only. Very often it would be easy to have a solution for the example only that will fail on many others. Reading the question often helps.
  • Philippos
    Philippos almost 7 years
    Actually, your sed solution inserts before the last apple, but you can modify it easily. I see no reason to add lengthy and cryptic solutions when easy ones are already given.
  • George Udosen
    George Udosen almost 7 years
    Please can you have the OP say something, what part of the question am I supposed to read?
  • Philippos
    Philippos almost 7 years
    Nitpicking I could remark that this adds an unwanted newline to the end of the file, if the match is in the last line. (-;
  • Sergiy Kolodyazhnyy
    Sergiy Kolodyazhnyy almost 7 years
    @Philippos Thanks for pointing that out, fixed already. Solutions are no more cryptic than sed's regex and are more procedural, plus I explained how each solution works in principle. Matter of taste and familiarity with the tools, I suppose. Again, length is also irrelevant. If it works, it works. We're not concerned with code-golf here.
  • Sergiy Kolodyazhnyy
    Sergiy Kolodyazhnyy almost 7 years
    @Philippos Again, unless OP themself states so, it is not a requirement, and as such not a reason to impose such requirements on other people answering the question.
  • Byte Commander
    Byte Commander almost 7 years
    @Philippos Oh come on. Your solution in contrast fails if "Apple" is on the first line only.
  • Philippos
    Philippos almost 7 years
    You saw the wink? (-; But you are right!