8

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.

2
  • 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, 2011 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, 2011 at 16:41

2 Answers 2

8

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

2
  • 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, 2011 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, 2012 at 19:18
5

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 

You must log in to answer this question.

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