How to split a string by the third .(dot) delimiter

Used sed for example:

$ echo 'version: 1.8.0.110' | sed 's/\./-/3'
version: 1.8.0-110

Explanation:

sed s/search/replace/x searches for a string and replaces the second. x determines which occurence to replace - here the 3rd. Often g is used for x to mean all occurances.

Here we wish to replace the dot . but this is a special character in the regular expression sed expects in the search term. Therefore we backslashify the . to \. to specify a literal ..

Since we use special characters in the argument to sed (here, the backslash \) we need to put the whole argument in single quotes ''. Many people always use quotes here so as not to run into problems when using characters that might be special to the shell (like space ).


Use bash

You do not need an external program to do this, especially since it appears you want to replace the last period with a dash. Bash can handle string manipulations itself.

Presuming you have

vers='version: 1.8.0.110'

the answer is simply:

$ echo ${vers%.*}-${vers##*.}
1.8.0-110

The % means delete the shortest string to the right that matches. The ## means delete the longest string to the left that matches.

What if there are more than three periods?

If you actually want to replace the third period, not the last period as I presumed above, it gets a little trickier in bash, but it is still possible. For example, if vers='version: 1.8.0.110.hut.hut.hut.hike!', you can find everything after the third period and use that to truncate the string:

$ end=${vers#*.*.*.}
$ start=${vers%$end}
$ echo ${start%.}-$end
version: 1.8.0-110.hut.hut.hut.hike!

Another way: use IFS to split string into a bash array

While this is overkill for the question, it is sometimes useful to split a string into components and load them into an array.

IFS=. read -a foo <<<'version: 1.8.0.110.hut.hut.hut.hike!'

You can iterate over the array $foo and choose what to print when. For example,

echo -n $foo
for ((i=1; i<${foo[@]}; i++)); do 
  [[ i -eq 3 ]] && echo -n - || echo -n .
  echo -n ${foo[i]};
done
echo

In this case, the third is also the last one. If you can safely assume you always need to change the last ., you could do:

$ echo 'version: 1.8.0.110' | sed 's/\.\([^.]*\)$/-\1/'
version: 1.8.0-110

or, if your sed supports -E (most do) with:

$ echo 'version: 1.8.0.110' | sed -E 's/\.([^.]*)$/-\1/'
version: 1.8.0-110

That will look for a . and then 0 or more non-. characters ([^.]) until the end of the line ($). Because the [^.]* is in parentheses (\( \)), it is "captured" and can be referred to as \1 on the right hand side of the replacement operator. So this will replace the . and the non-. characters after it with a - and those characters.


If it must be the third and not the last, @Ned64 already gave the simplest approach, but as an alternative, you could do:

$ echo 'version: 1.8.0.110' | perl -pe 's/(([^.]+\.){2}[^.]+)\./$1-/'
version: 1.8.0-110