How do I manipulate the last line matching in a text? I have a configuration file in which I wish to change a certain value:


I only want to change the last line matching the value to grep for. sed -i 's/\(^val2=\)/\1yes/' is a good start, but replaces both val2 lines instead of only the last.

I tagged the question very generically as Unix because I'm open to any solution: sed or awk, Perl or Ruby, Bash or whatever. A shorter solution is sexier than a mouthful one.


The best I came up with is this:

tac config |while read LINE; do
  if [ "$DONE" != 1 ] && echo "$LINE" |grep '^\s*val2\s*=' >/dev/null; then
    echo "$LINE" |sed 's/^\(\s*val2\s*=\s*\)/\1yes/'
    echo "$LINE"
done |tac >config.new

But this doesn't feel like the quick-and-dirty pipelines I like for such a common task.


Based on the script found here:

sed -r ':0;/^val2=/!b;:1;$!N;/\n.*word/{h;s/\n[^\n]*$//p;g;s/^.*\n//};$!b 1;s/^(.*)(val2=)[^\n]*/\1\2yes/' file
  • I may be getting it wrong, but this seems to omit lines. In a test case I ran I lost the last config line. – wilhelmtell Sep 10 '10 at 15:12
  • @wilhelmtell: I changed the dot near the end to a non-newline [^\n]. Try it again. – Dennis Williamson Sep 10 '10 at 15:29

General idea: read the input in chunks separated by \nval2=. Print every chunk as is, except for the last chunk where we change the text up to the first newline. Here's a relatively simple gawk solution that handles most cases:

gawk -v 'RS=\nval2=' -v ORS= \
  'RT {print $0 RT} !RT {sub(/[^\n]*/, "yes"); print $0}'

This assumes that the first line doesn't start with val2= and that there is at least one match. Fixing both defects complicates the script significantly:

gawk -v 'RS=\nval2=' -v ORS= \
  '!RT {if (NR==1) sub(/^val2=[^\n]*/, "val2=yes"); else sub(/[^\n]*/, "yes")}
   {print $0 RT}'

Here's a Perl solution based on the same general principle, but reading the whole file at once. I find it clearer because it doesn't have to handle special cases (other than the empty file).

perl -e 'undef $/;
         @chunks = split /(?=^val2=)/m, <>;
         $chunks[@chunks-1] =~ s/(?<=^val2=).*/yes/ if @chunks;
         print @chunks'

With Perl's extended regexps, we can use a single regexp substitution. Just match the ^val2= that's not followed by another ^val2=:

perl -e 'undef $/; $_=<>; s/^val2=.*\n(?!.*\nval2=)/val2=yes\n/m; print'

I tried taking advantage of Python's rsplit which can split off the last N fields, but gave up when I couldn't get it to work without being much more cryptic that my Perl solutions.

  • Haven't confirmed this works, but: a great answer! – Arjan Sep 10 '10 at 19:34

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.