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.

  • Can 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. – jw013 Sep 21 '11 at 16:31
  • I meant per line. I assumed it was obvious. I'm edited my question so that it would be clearer. – rahmu Sep 21 '11 at 16:41

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.


  • 1
    Yes 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. – rahmu Sep 21 '11 at 16:43
  • Using ';' is risky when dealing with unknown data, eg. "seven;7". A value of \x00 would be a safer choice. I don't know if all seds handle \x00, but they should at least handle \x01... they are both very unliklely to be in normal text data. – Peter.O Jun 15 '12 at 19:18

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 

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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