Take the 2-minute tour ×
Super User is a question and answer site for computer enthusiasts and power users. It's 100% free, no registration required.

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
add comment

3 Answers 3

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
add comment
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
add comment

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
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

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