4

Consider I have the following file:

echo "1
2
3
4
1
2
3
4
1
2
3
4
1
2
3
4
" > ztest

Here I'd like to change only the first 1 into 5 and leave everything else intact.

I know sed has a quit command - so I'm trying the following:

$ sed 's/1/{5;q}/' ztest 
{5;q}
2
3
4
{5;q}
2
3
4
{5;q}
2
3
4
{5;q}
2
3
4

This changes all lines, so it's no good. So, then I try this:

$ sed '{s/1/5/;q}' ztest 
5

Now, this apparently does as requested - changes the first line and exits; however, I'd like the rest of the lines intact! (since this, basically has the effect of replacing the entire file with a single '5')

So I'm at a loss at what kind of syntax is needed? Note that I need this to change an "early" line in a gigabyte file inline (using sed -i); thus I'd like sed to only search and replace in the first brief section - and after that, output lines unchanged (since sed -i anyways dumps into a temp file first, and then overwrites the original); hoping to save some processing time.

Thanks in advance for any answers,
Cheers!

EDIT: forgot subquestion: is it possible to extend this to first n matches? (say, in the file above, first two '1's are changed to 5 - rest of it is output verbatim?)

4
  • Are you stuck with sed only?
    – Mat
    Feb 26, 2012 at 18:43
  • HI @Mat - not really; just the first one I resort to when I have t replace inline in text files - any other suggestions are welcome!
    – sdaau
    Feb 26, 2012 at 18:46
  • 1
    same situation here
    – selman
    Feb 26, 2012 at 19:11
  • Many thanks for that link, @selman - I like the trick with line 0 (as in 0,/Apple/{s/Apple/Banana/}) from there - cheers!
    – sdaau
    Feb 26, 2012 at 19:58

6 Answers 6

8

I have seen it somewhere else on this site:

sed '/1/{s/1/5/;:a;n;ba}' ztest

So once you found the 1st occurrence, you loop till the end of the file reading and printing lines.

Update

It can be enhance to only replace the Nth match. The idea is to store a X at each match in the holdspace, and when all the Xs are there, loop till the end of file. Bellow, the script that replace the 2nd occurrence:

sed '/1/{G;s/\nX\{1\}//;tend;x;s/^/X/;x;P;d};p;d;:end;s/1/5/;:a;n;ba'

Note that you need to put (N-1) between the \{ and \}.

Update 2

I realize that the P;d above is useless if the p;d is replaced by P;d. So a simpler solution is:

sed '/1/{G;s/\nX\{1\}//;tend;x;s/^/X/;x};P;d;:end;s/1/5/;:a;n;ba'
2
  • Many thanks for that @jfgagne - I was hoping sed can refer to the n-th match; and glad to see an example (I'll need to brush up on my sed syntax, though :) ) Cheers!
    – sdaau
    Feb 26, 2012 at 19:54
  • One caveat. The OS/X (even on El Capitan) sed command doesn't recognize { } and will just punt with following error message: sed: 1: "/1/{s/1/5/;:a;n;ba}": unexpected EOF (pending }'s) Jul 28, 2016 at 0:04
4

Don't know if that's doable with sed, but here's an awk version.

awk 'BEGIN {matches=0}
     matches < 2 && /1/ { sub(/1/,"5"); matches++ }
     { print $0 }' ztest 

This will replace 1 with 5 for the first two matches of 1. (Change the 2 in there to increase/decrease the number of matches you want.)
After that, the file is printed as-is.

1
  • Many thanks for that, @Mat - by the way, your original sed answer led me this answer; but I think I like the direct number of matches specification better. Cheers!
    – sdaau
    Feb 26, 2012 at 19:53
2

This might work for you (GNU sed):

sed '0,/1/s//5/' file
1
  • 1
    Thanks @potong, care to explain a bit more please?
    – xpt
    Jul 22, 2013 at 18:53
1

Thanks to @Mat's first answer (over at StackOverflow, now deleted), the syntax for sed to replace only the match on first line is:

sed  '1s/1/5/' ztest

However, you have to know explicitly the line number where to perform the match.

So if I know the file is as above, then the first two 1's appear on lines 1 and 5; so for the first two matches, I could instruct sed to apply the expression between lines 1 and (say) 6 - the syntax for line (address) range in sed being a comma (,):

$ sed  '1,6s/1/5/' ztest
5
2
3
4
5
2
3
4
1
2
3
4
1
2
3
4

Similarly, second and third occurrence of 1 are on lines 5 and 9 - so I can similarly use the range, say, from line 3 to line 10 to change second and third occurrence of 1:

$ sed  '3,10s/1/5/' ztest
1
2
3
4
5
2
3
4
5
2
3
4
1
2
3
4

.... which I guess answers my question in terms of sed; although I hoped that I could specify the number of matches directly (as in the awk solution by @Mat).

1

Use the :a;n;ba pattern once again:

$ sed '/1/{s//5/;:A;/1/{s//5/;:B;n;bB};n;bA}'
0
-3

Use simply this: sed 's/1/5/' ztest

You must log in to answer this question.

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