Split string on colon in /bin/sh

Clash Royale CLAN TAG#URR8PPP
up vote
8
down vote
favorite
My dash script takes a parameter in the form of hostname:port, i.e.:
myhost:1234
Whereas port is optional, i.e.:
myhost
I need to read the host and port into separate variables. In the first case, I can do:
HOST=$1%%:*
PORT=$1##*:
But that does not work in second case, when port was omitted; echo $1##*: simply returns hostname, instead of an empty string.
In Bash, I could do:
IFS=: read A B <<< asdf:111
But that does not work in dash.
Can I split string on : in dash, without invoking external programs (awk, tr, etc.)?
shell string dash
add a comment |Â
up vote
8
down vote
favorite
My dash script takes a parameter in the form of hostname:port, i.e.:
myhost:1234
Whereas port is optional, i.e.:
myhost
I need to read the host and port into separate variables. In the first case, I can do:
HOST=$1%%:*
PORT=$1##*:
But that does not work in second case, when port was omitted; echo $1##*: simply returns hostname, instead of an empty string.
In Bash, I could do:
IFS=: read A B <<< asdf:111
But that does not work in dash.
Can I split string on : in dash, without invoking external programs (awk, tr, etc.)?
shell string dash
4
Make sure to split on the last colon if you want to support IPv6, and don't split on colons inside square brackets
â Ferrybig
Jan 15 at 8:50
@Ferrybig%%makes it greedy (as opposed to%), so it actually does this, at least in part; it would not work with##.
â jpaugh
Jan 15 at 22:30
add a comment |Â
up vote
8
down vote
favorite
up vote
8
down vote
favorite
My dash script takes a parameter in the form of hostname:port, i.e.:
myhost:1234
Whereas port is optional, i.e.:
myhost
I need to read the host and port into separate variables. In the first case, I can do:
HOST=$1%%:*
PORT=$1##*:
But that does not work in second case, when port was omitted; echo $1##*: simply returns hostname, instead of an empty string.
In Bash, I could do:
IFS=: read A B <<< asdf:111
But that does not work in dash.
Can I split string on : in dash, without invoking external programs (awk, tr, etc.)?
shell string dash
My dash script takes a parameter in the form of hostname:port, i.e.:
myhost:1234
Whereas port is optional, i.e.:
myhost
I need to read the host and port into separate variables. In the first case, I can do:
HOST=$1%%:*
PORT=$1##*:
But that does not work in second case, when port was omitted; echo $1##*: simply returns hostname, instead of an empty string.
In Bash, I could do:
IFS=: read A B <<< asdf:111
But that does not work in dash.
Can I split string on : in dash, without invoking external programs (awk, tr, etc.)?
shell string dash
edited Jan 15 at 13:15
Peter Mortensen
78748
78748
asked Jan 15 at 8:21
Martin Vegter
97231109218
97231109218
4
Make sure to split on the last colon if you want to support IPv6, and don't split on colons inside square brackets
â Ferrybig
Jan 15 at 8:50
@Ferrybig%%makes it greedy (as opposed to%), so it actually does this, at least in part; it would not work with##.
â jpaugh
Jan 15 at 22:30
add a comment |Â
4
Make sure to split on the last colon if you want to support IPv6, and don't split on colons inside square brackets
â Ferrybig
Jan 15 at 8:50
@Ferrybig%%makes it greedy (as opposed to%), so it actually does this, at least in part; it would not work with##.
â jpaugh
Jan 15 at 22:30
4
4
Make sure to split on the last colon if you want to support IPv6, and don't split on colons inside square brackets
â Ferrybig
Jan 15 at 8:50
Make sure to split on the last colon if you want to support IPv6, and don't split on colons inside square brackets
â Ferrybig
Jan 15 at 8:50
@Ferrybig
%% makes it greedy (as opposed to %), so it actually does this, at least in part; it would not work with ##.â jpaugh
Jan 15 at 22:30
@Ferrybig
%% makes it greedy (as opposed to %), so it actually does this, at least in part; it would not work with ##.â jpaugh
Jan 15 at 22:30
add a comment |Â
4 Answers
4
active
oldest
votes
up vote
17
down vote
accepted
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
-rwhich means backslash will undergo some special processing. - it would split
[::1]:443into[and:1]:443instead of[and the empty string (for which you'd needIFS=: read -r A B rest_ignoredor[::1]and443(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 ''inzshorbashand 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) andbash, here strings are implemented using temporary files, so it's generally less efficient than using$x#yor split+glob operators.
7
In 2018, as a New Year's resolution, we should all stop writing scripts that will break with IPv6.
â Philippos
Jan 15 at 9:12
@Philippos too late by two weeks!
â RonJohn
Jan 16 at 4:38
@RonJohn: Too late by two decades, somehow.
â Philippos
Jan 16 at 6:42
add a comment |Â
up vote
6
down vote
Just remove the : in a separate statement; also, remove $host from the input to get the port:
host=$1%:*
port=$1#"$host"
port=$port#:
add a comment |Â
up vote
3
down vote
Another thought:
host=$1%:*
port=$1##*:
[ "$port" = "$1" ] && port=''
add a comment |Â
up vote
1
down vote
A here string is just a syntactic shortcut for a single-line here document.
$ set myhost:1234
$ IFS=: read A B <<EOF
> $1
> EOF
$ echo "$A"
myhost
$ echo "B"
1234
add a comment |Â
4 Answers
4
active
oldest
votes
4 Answers
4
active
oldest
votes
active
oldest
votes
active
oldest
votes
up vote
17
down vote
accepted
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
-rwhich means backslash will undergo some special processing. - it would split
[::1]:443into[and:1]:443instead of[and the empty string (for which you'd needIFS=: read -r A B rest_ignoredor[::1]and443(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 ''inzshorbashand 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) andbash, here strings are implemented using temporary files, so it's generally less efficient than using$x#yor split+glob operators.
7
In 2018, as a New Year's resolution, we should all stop writing scripts that will break with IPv6.
â Philippos
Jan 15 at 9:12
@Philippos too late by two weeks!
â RonJohn
Jan 16 at 4:38
@RonJohn: Too late by two decades, somehow.
â Philippos
Jan 16 at 6:42
add a comment |Â
up vote
17
down vote
accepted
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
-rwhich means backslash will undergo some special processing. - it would split
[::1]:443into[and:1]:443instead of[and the empty string (for which you'd needIFS=: read -r A B rest_ignoredor[::1]and443(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 ''inzshorbashand 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) andbash, here strings are implemented using temporary files, so it's generally less efficient than using$x#yor split+glob operators.
7
In 2018, as a New Year's resolution, we should all stop writing scripts that will break with IPv6.
â Philippos
Jan 15 at 9:12
@Philippos too late by two weeks!
â RonJohn
Jan 16 at 4:38
@RonJohn: Too late by two decades, somehow.
â Philippos
Jan 16 at 6:42
add a comment |Â
up vote
17
down vote
accepted
up vote
17
down vote
accepted
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
-rwhich means backslash will undergo some special processing. - it would split
[::1]:443into[and:1]:443instead of[and the empty string (for which you'd needIFS=: read -r A B rest_ignoredor[::1]and443(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 ''inzshorbashand 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) andbash, here strings are implemented using temporary files, so it's generally less efficient than using$x#yor split+glob operators.
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
-rwhich means backslash will undergo some special processing. - it would split
[::1]:443into[and:1]:443instead of[and the empty string (for which you'd needIFS=: read -r A B rest_ignoredor[::1]and443(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 ''inzshorbashand 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) andbash, here strings are implemented using temporary files, so it's generally less efficient than using$x#yor split+glob operators.
edited Jan 15 at 20:16
answered Jan 15 at 8:27
Stéphane Chazelas
281k53518849
281k53518849
7
In 2018, as a New Year's resolution, we should all stop writing scripts that will break with IPv6.
â Philippos
Jan 15 at 9:12
@Philippos too late by two weeks!
â RonJohn
Jan 16 at 4:38
@RonJohn: Too late by two decades, somehow.
â Philippos
Jan 16 at 6:42
add a comment |Â
7
In 2018, as a New Year's resolution, we should all stop writing scripts that will break with IPv6.
â Philippos
Jan 15 at 9:12
@Philippos too late by two weeks!
â RonJohn
Jan 16 at 4:38
@RonJohn: Too late by two decades, somehow.
â Philippos
Jan 16 at 6:42
7
7
In 2018, as a New Year's resolution, we should all stop writing scripts that will break with IPv6.
â Philippos
Jan 15 at 9:12
In 2018, as a New Year's resolution, we should all stop writing scripts that will break with IPv6.
â Philippos
Jan 15 at 9:12
@Philippos too late by two weeks!
â RonJohn
Jan 16 at 4:38
@Philippos too late by two weeks!
â RonJohn
Jan 16 at 4:38
@RonJohn: Too late by two decades, somehow.
â Philippos
Jan 16 at 6:42
@RonJohn: Too late by two decades, somehow.
â Philippos
Jan 16 at 6:42
add a comment |Â
up vote
6
down vote
Just remove the : in a separate statement; also, remove $host from the input to get the port:
host=$1%:*
port=$1#"$host"
port=$port#:
add a comment |Â
up vote
6
down vote
Just remove the : in a separate statement; also, remove $host from the input to get the port:
host=$1%:*
port=$1#"$host"
port=$port#:
add a comment |Â
up vote
6
down vote
up vote
6
down vote
Just remove the : in a separate statement; also, remove $host from the input to get the port:
host=$1%:*
port=$1#"$host"
port=$port#:
Just remove the : in a separate statement; also, remove $host from the input to get the port:
host=$1%:*
port=$1#"$host"
port=$port#:
edited Jan 15 at 8:29
answered Jan 15 at 8:28
choroba
24.3k33967
24.3k33967
add a comment |Â
add a comment |Â
up vote
3
down vote
Another thought:
host=$1%:*
port=$1##*:
[ "$port" = "$1" ] && port=''
add a comment |Â
up vote
3
down vote
Another thought:
host=$1%:*
port=$1##*:
[ "$port" = "$1" ] && port=''
add a comment |Â
up vote
3
down vote
up vote
3
down vote
Another thought:
host=$1%:*
port=$1##*:
[ "$port" = "$1" ] && port=''
Another thought:
host=$1%:*
port=$1##*:
[ "$port" = "$1" ] && port=''
answered Jan 15 at 14:32
glenn jackman
46.6k265103
46.6k265103
add a comment |Â
add a comment |Â
up vote
1
down vote
A here string is just a syntactic shortcut for a single-line here document.
$ set myhost:1234
$ IFS=: read A B <<EOF
> $1
> EOF
$ echo "$A"
myhost
$ echo "B"
1234
add a comment |Â
up vote
1
down vote
A here string is just a syntactic shortcut for a single-line here document.
$ set myhost:1234
$ IFS=: read A B <<EOF
> $1
> EOF
$ echo "$A"
myhost
$ echo "B"
1234
add a comment |Â
up vote
1
down vote
up vote
1
down vote
A here string is just a syntactic shortcut for a single-line here document.
$ set myhost:1234
$ IFS=: read A B <<EOF
> $1
> EOF
$ echo "$A"
myhost
$ echo "B"
1234
A here string is just a syntactic shortcut for a single-line here document.
$ set myhost:1234
$ IFS=: read A B <<EOF
> $1
> EOF
$ echo "$A"
myhost
$ echo "B"
1234
answered Jan 15 at 19:43
chepner
5,1401223
5,1401223
add a comment |Â
add a comment |Â
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2funix.stackexchange.com%2fquestions%2f417187%2fsplit-string-on-colon-in-bin-sh%23new-answer', 'question_page');
);
Post as a guest
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
4
Make sure to split on the last colon if you want to support IPv6, and don't split on colons inside square brackets
â Ferrybig
Jan 15 at 8:50
@Ferrybig
%%makes it greedy (as opposed to%), so it actually does this, at least in part; it would not work with##.â jpaugh
Jan 15 at 22:30