16

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.

5
  • How come that you need to do it the same way in both shells? Don't you know what interpreter you're using?
    – Kusalananda
    Commented Oct 13, 2020 at 12:14
  • 2
    I need to do a script which is compatible with both shells, because I know both will be used. Commented Oct 13, 2020 at 12:16
  • 1
    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.
    – Kusalananda
    Commented Oct 13, 2020 at 13:06
  • 3
    @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
    Commented Oct 13, 2020 at 21:22
  • 1
    @Kusalananda There are also projects that add some shell initialization like how Gnome's VTE adds a file in /etc/profile.d/ compatible with both bash and zsh to set the window title to include the working directory of the shell as it changes.
    – JoL
    Commented Oct 13, 2020 at 21:44

1 Answer 1

23

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
1
  • This is great, although beware if bash is invoked from zsh, or vice versa, it seems for me that the environment variables linger, making this shell detection confused.
    – Jack Wasey
    Commented Nov 11, 2022 at 12:52

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .