Split string on colon in /bin/sh

Just do:

case $1 in
  (*:*) host=${1%:*} port=${1##*:};;
  (*)   host=$1      port=$default_port;;
esac

You may want to change the case $1 to case ${1##*[]]} to account for values of $1 like [::1] (an IPv6 address without port part).

To split, you can use the split+glob operator (leave a parameter expansion unquoted) as that's what it's for after all:

set -o noglob # disable glob part
IFS=:         # split on colon
set -- $1     # split+glob

host=$1 port=${2:-$default_port}

(though that won't allow hostnames that contain a colon (like for that IPv6 address above)).

That split+glob operator gets in the way and causes so much harm the rest of the time that it would seem only fair that it be used whenever it's needed (though, I'll agree it's very cumbersome to use especially considering that POSIX sh has no support for local scope, neither for variables ($IFS here) nor for options (noglob here) (though ash and derivatives like dash are some of the ones that do (together with AT&T implementations of ksh, zsh and bash 4.4 and above)).

Note that IFS=: read A B <<< "$1" has a few issues of its own:

  • you forgot the -r which means backslash will undergo some special processing.
  • it would split [::1]:443 into [ and :1]:443 instead of [ and the empty string (for which you'd need IFS=: read -r A B rest_ignored or [::1] and 443 (for which you can't use that approach)
  • it strips everything past the first occurrence of a newline character, so it can't be used with arbitrary strings (unless you use -d '' in zsh or bash and the data doesn't contain NUL characters, but then note that herestrings (or heredocs) do add an extra newline character!)
  • in zsh (where the syntax comes from) and bash, here strings are implemented using temporary files, so it's generally less efficient than using ${x#y} or split+glob operators.

Just remove the : in a separate statement; also, remove $host from the input to get the port:

host=${1%:*}
port=${1#"$host"}
port=${port#:}

Another thought:

host=${1%:*}
port=${1##*:}
[ "$port" = "$1" ] && port=''

Tags:

String

Shell

Dash