How to split a string by ':' character in bash/zsh?

7,204

The : there is any arbitrary character:

You can use:

parts=(${(s/:/)str})

Some common character pairs are also supported like:

parts=(${(s[:])str})

If you're going to use the @ flag to preserve empty elements, then you need to quote:

parts=("${(@s[:])str}")

Otherwise @ makes no difference.

If it's to process variables like $PATH/$LD_LIBRARY_PATH... see also typeset -T which ties an array variable to a scalar variable:

$ typeset -T str str_array
$ str='a::b'
$ typeset -p str
typeset -T str str_array=( a '' b )

zsh does tie $path to $PATH by default (like in csh/tcsh).

bash's

parts=(${str//:/ })

Is wrong as it applies split+glob after having replaced : with SPC.

You'd want:

IFS=:            # split on : instead of default SPC TAB NL
set -o noglob    # disable glob
parts=( $str"" ) # split+glob (leave expansion unquoted), preserve trailing
                 # empty part.

That code would also work in zsh, if it was in sh or ksh emulation mode. If your goal is to write code compatible to both bash and zsh, you may want to write it using ksh syntax and make sure that zsh is put in ksh emulation (possibly only locally to some function) when interpreting it.

To test whether the shell is bash or zsh, you'd test for the presence of the $BASH_VERSION/$BASH_VERSINFO or $ZSH_VERSION variables.

split() { # args: string delimiter result_var
  if
    [ -n "$ZSH_VERSION" ] &&
      autoload is-at-least &&
      is-at-least 5.0.8 # for ps:$var:
  then
    eval $3'=("${(@ps:$2:)1}")'
  elif
    [ "$BASH_VERSINFO" -gt 4 ] || {
      [ "$BASH_VERSINFO" -eq 4 ] && [ "${BASH_VERSINFO[1]}" -ge 4 ]
      # 4.4+ required for "local -"
    }
  then
    local - IFS="$2"
    set -o noglob
    eval "$3"'=( $1"" )'
  else
    echo >&2 "Your shell is not supported"
    exit 1
  fi
}

split "$str" : parts
Share:
7,204

Related videos on Youtube

Adrian Maire
Author by

Adrian Maire

Enthusiast for most technological and scientific topics, specially computer science. I made a 300ECTS degree in computer science at UCLM (Spain) and I am currently working at Nexthink in Lausanne. I programmed my first ("finished") 2D video-game in VisualBasic-6 when I was 14, and since then, I spent most of my free time in personal projects: Real time graphics(opengl), AI neural networks, electronic, mechanical devices, house improvement...

Updated on September 18, 2022

Comments

  • Adrian Maire
    Adrian Maire over 1 year

    I am searching for a way to split a string (variable) by : character, in a compatible way between bash and zsh.

    From different sources, I found the following command:

    str="part1=part2=part3"
    parts=(${(@s:=:)str})
    echo ${#parts[@]}
    

    However, I could not find the way to escape : for this same sequence

    parts=(${(@s:::)str})  #Not working
    parts=(${(@s:\::)str}) #Not working
    

    For bash, I found this:

    parts=(${str//:/ })
    

    Which works, but is not really compatible. I could use the following line to discriminate the shell:

    if [ -z "$(ps -p $$| grep zsh)" ]; then
        echo "This is bash (Use bash solution here)"
    else
        echo "This is zsh (Use Zsh solution here)"
    fi
    

    But maybe some alternative solution is compatible? Any working solution is already a win.

    • Kusalananda
      Kusalananda over 3 years
      How come that you need to do it the same way in both shells? Don't you know what interpreter you're using?
    • Adrian Maire
      Adrian Maire over 3 years
      I need to do a script which is compatible with both shells, because I know both will be used.
    • Kusalananda
      Kusalananda over 3 years
      Huh, that's like asking for doing it the same way in Perl and Python, because the script will be executed by both. It doesn't make sense.
    • JoL
      JoL over 3 years
      @Kusalananda There are projects like rbenv and rvm that present themselves to the user as shell functions, in rbenv's case to be able to tie a ruby environment to an existing shell session. Given that bash and zsh are probably the most common shells in use by a wide margin and that their syntax are very compatible between each other (a lot more than Perl's and Python's), I think it makes a lot of sense for projects like that to try to be compatible with both.
    • JoL
      JoL over 3 years