Sed: Replace N first occurrences of a character
Solution 1
reading your question I've remembered that at least GNU Sed (probably not the one you have in Solaris) has the opposite feature that you want:
g: Apply the replacement to all matches to the regexp, not just the first.
number: Only replace the numberth match of the regexp.
Note: the posix standard does not specify what should happen when
you mix the g and number modifiers, and currently there is no widely agreed upon meaning across sed implementations. For GNU sed, the interaction is defined to be: ignore matches before the numberth, and then match and replace all matches from the numberth on.
So instead of:
hmontoliu@ulises:/tmp/wb$ echo one two three four five six seven | sed 's/ /;/g5'
one two three four five;six;seven
you can get a more terse command to achieve what you want by doing:
hmontoliu@ulises:/tmp/wb$ echo one two three four five six seven | sed -e 's/ /;/g' -e 's/;/ /6g'
one;two;three;four;five;six seven
Tell us if the Solaris implementation has that feautre.
HTH
Solution 2
It will be easier to keep to your basic idea, than to do some itterative sed juggling. Maybe a simple for-loop to build the pattern will make it easier to work with.
pat=; for ((i=1; i<=5; i++)) ;do pat="$pat s/ /;/;"; done
sed -e "$pat" myfile
or just dispense with the multiple -e
expression options, and just group them all with the ;
expression seperator.
sed -e "s/ /;/; s/ /;/; s/ /;/; s/ /;/; s/ /;/" myfile
Here is the sed-only version, that you typically probably won't bother with, but which does allow you to specify any number of replacements. (via the {5}
) ...
sed -nre ':check;G;/^.*\nx{5}$/{b print};s/^(.*)\n.*$/\1/;s/ /;/;t inc-tally;:print;P;x;s/.*//;x;b;:inc-tally;x;s/(.*)/\1x/;x;b check' myfile
The above one-liner(?) is a bit scary, so here it is as structured code, to be called via a sed-script file: sed -nrf "$fsed" myfile
:check ## check the tally
G ## pattern+=nl+hold
/^.*\nx{5}$/{ ## we have the full complement of replacements
b print ## branch to print (and continue)
} ##
s/^(.*)\n.*$/\1/ ##
s/ /;/ ## change one space (if one is present)
t inc-tally ## branch_on_substitute
:print ## no more spaces to change
P ## pattern_first_line_print
x;s/.*//;x ## kill the accumulated tally chars in hold
b ## branch to end of proc (continue)
:inc-tally ##
x ## swap_pattern_and_hold
s/(.*)/\1x/ ##
x ## swap_pattern_and_hold
b check ## branch_unconditional
rahmu
Updated on September 18, 2022Comments
-
rahmu over 1 year
I am looking to replace the 5 first occurences of the whitespace character per line inside a sed script. Here's what I have so far
sed -e "s/ /;/" -e "s/ /;/" -e "s/ /;/" -e "s/ /;/" -e "s/ /;/" myfile
Is there any nicer way of doing it?
For the record, I am using the Solaris sed. I don't know if it makes any difference.
-
Admin over 12 yearsCan you clarify if you mean the first n occurrences per line or the first n occurrences in the entire file? I think everyone is assuming the former based on your example, and in that case I'd probably go with fred's answer.
-
Admin over 12 yearsI meant per line. I assumed it was obvious. I'm edited my question so that it would be clearer.
-
-
rahmu over 12 yearsYes it does work. I like the ingenuity of the answer. However it feels a little hackish, that final
g6
may not be clear to everyone, not to mention that the behavior would be unpredictable on other versions of sed. -
Peter.O almost 12 yearsUsing ';' is risky when dealing with unknown data, eg.
"seven;7"
. A value of\x00
would be a safer choice. I don't know if allsed
s handle\x00
, but they should at least handle\x01
... they are both very unliklely to be in normal text data.