Super User is a question and answer site for computer enthusiasts and power users. Join them; it only takes a minute:

Sign up
Here's how it works:
  1. Anybody can ask a question
  2. Anybody can answer
  3. The best answers are voted up and rise to the top

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:

val1=0
val2=a
val3=?
val2=lol

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.

share|improve this question
up vote 1 down vote accepted

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/'
    DONE=1
  else
    echo "$LINE"
  fi
done |tac >config.new

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

share|improve this answer

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
share|improve this answer
    
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.

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

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .