sed - perform only first (nth) matched replacement?
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?)
Solution 1:
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 X
s 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'
Solution 2:
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.
Solution 3:
This might work for you (GNU sed):
sed '0,/1/s//5/' file
Solution 4:
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).