Split string on colon in /bin/sh

The name of the pictureThe name of the pictureThe name of the pictureClash Royale CLAN TAG#URR8PPP











up vote
8
down vote

favorite
1












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.)?







share|improve this question


















  • 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















up vote
8
down vote

favorite
1












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.)?







share|improve this question


















  • 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













up vote
8
down vote

favorite
1









up vote
8
down vote

favorite
1






1





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.)?







share|improve this question














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.)?









share|improve this question













share|improve this question




share|improve this question








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













  • 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











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 -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.





share|improve this answer


















  • 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

















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#:





share|improve this answer





























    up vote
    3
    down vote













    Another thought:



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





    share|improve this answer



























      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





      share|improve this answer




















        Your Answer







        StackExchange.ready(function()
        var channelOptions =
        tags: "".split(" "),
        id: "106"
        ;
        initTagRenderer("".split(" "), "".split(" "), channelOptions);

        StackExchange.using("externalEditor", function()
        // Have to fire editor after snippets, if snippets enabled
        if (StackExchange.settings.snippets.snippetsEnabled)
        StackExchange.using("snippets", function()
        createEditor();
        );

        else
        createEditor();

        );

        function createEditor()
        StackExchange.prepareEditor(
        heartbeatType: 'answer',
        convertImagesToLinks: false,
        noModals: false,
        showLowRepImageUploadWarning: true,
        reputationToPostImages: null,
        bindNavPrevention: true,
        postfix: "",
        onDemand: true,
        discardSelector: ".discard-answer"
        ,immediatelyShowMarkdownHelp:true
        );



        );








         

        draft saved


        draft discarded


















        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






























        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 -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.





        share|improve this answer


















        • 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














        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 -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.





        share|improve this answer


















        • 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












        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 -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.





        share|improve this answer














        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.






        share|improve this answer














        share|improve this answer



        share|improve this answer








        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












        • 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












        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#:





        share|improve this answer


























          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#:





          share|improve this answer
























            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#:





            share|improve this answer














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



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






            share|improve this answer














            share|improve this answer



            share|improve this answer








            edited Jan 15 at 8:29

























            answered Jan 15 at 8:28









            choroba

            24.3k33967




            24.3k33967




















                up vote
                3
                down vote













                Another thought:



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





                share|improve this answer
























                  up vote
                  3
                  down vote













                  Another thought:



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





                  share|improve this answer






















                    up vote
                    3
                    down vote










                    up vote
                    3
                    down vote









                    Another thought:



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





                    share|improve this answer












                    Another thought:



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






                    share|improve this answer












                    share|improve this answer



                    share|improve this answer










                    answered Jan 15 at 14:32









                    glenn jackman

                    46.6k265103




                    46.6k265103




















                        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





                        share|improve this answer
























                          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





                          share|improve this answer






















                            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





                            share|improve this answer












                            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






                            share|improve this answer












                            share|improve this answer



                            share|improve this answer










                            answered Jan 15 at 19:43









                            chepner

                            5,1401223




                            5,1401223






















                                 

                                draft saved


                                draft discarded


























                                 


                                draft saved


                                draft discarded














                                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













































































                                Popular posts from this blog

                                Peggy Mitchell

                                Palaiologos

                                The Forum (Inglewood, California)