6

The fact that bash is 0-indexed and zsh is 1-indexed can cause problems. For example the following will work as intended with bash, but not zsh (PS I am aware that I don't NEED to use an array for this, it's just an example):

DISK1=Samsung_SSD_850_EVO_120GB_S21SNX0H915161E
DISK2=Samsung_SSD_850_EVO_120GB_S21SNX0H915160K

DISKS=( $DISK1 $DISK2 )

mdadm --create /dev/md0 --level=mirror --raid-devices=2 "${DISKS[0]}" "${DISKS[1]}"

I was thinking I could make this shell-agnostic by writing everything relative to the index of the current shell, if there were a way to get the index of the current shell as a variable, something like this:

INDEX=$(some_function_that_returns_index)
DISK1=Samsung_SSD_850_EVO_120GB_S21SNX0H915161E
DISK2=Samsung_SSD_850_EVO_120GB_S21SNX0H915160K

DISKS=( $DISK1 $DISK2 )

echo "${DISKS[$INDEX]}" "${DISKS[$(expr $INDEX + 1)]}"

mdadm --create /dev/md0 --level=mirror --raid-devices=2 "${DISKS[$INDEX]}" "${DISKS[$(expr $INDEX + 1)]}"

Is there a way to get the index of the current shell, or alternatively get the nth array element? Ideally using just single shell commands and not a custom little script.

Stonecraft
  • 360
  • 5
  • 15

1 Answers1

1

TLDR; In Zsh, you can use setopt KSH_ARRAYS to get zero-indexed arrays.

Longer answer: As some commenters have already said, targeting POSIX syntax (aka: /bin/sh) is the canonical way to have your script run in Bash, Zsh, Dash, etc. However, POSIX syntax can be incredibly limiting - especially when it comes to array support which POSIX only accomplishes via setting "$@".

Is it possible to target some of the more advanced features in Zsh and Bash without bothering with POSIX? Partially. You'll need to avoid a lot syntactic sugar like extended globbing, bracketed string indexing, etc. But, it can be done if you wrap Zsh specific statements with a check for something that's only there in Zsh (eg: [[ -n "$ZSH_VERSION" ]].

So, in answer to your question about getting Zsh to use zero-indexed arrays like Bash, there is an option for that called KSH_ARRAYS. Zsh doesn't have Bash specific options, but in this case the Korn Shell and Bash behave the same. So, putting it all together, your script becomes:

if [[ -n "$ZSH_VERSION" ]]; then
  # Set Zsh arrays to be zero indexed
  # This is how KSH and Bash behave
  setopt KSH_ARRAYS
fi

DISK1=Samsung_SSD_850_EVO_120GB_S21SNX0H915161E DISK2=Samsung_SSD_850_EVO_120GB_S21SNX0H915160K

DISKS=( $DISK1 $DISK2 )

mdadm --create /dev/md0 --level=mirror --raid-devices=2 "${DISKS[0]}" "${DISKS[1]}"

Note: you only want to run something like setopt KSH_ARRAYS in a shell script or in a Zsh function with setopt LOCAL_OPTIONS. Never add that to your interactive shell (or .zshrc), or else you will be in for a rough time if you use anything that expects Zsh arrays to behave like proper Zsh arrays.

There's additional options you may need to set for Zsh like POSIX_ARGZERO, depending on which other features you use in your scripts. One other thing to note - Zsh also offers an emulate statement, but it doesn't support Bash. Still, if you find KSH support is similar enough to what you're using in Bash to be adequate, adding this could be another possibility to get your scripts to work in both Zsh and Bash without targeting POSIX:

function foo {
  [[ -n "$ZSH_VERSION" ]] && emulate -L ksh
  # ...bar...baz...
}

Now that you know how to do this, I'll go back to the original advice others have given - target POSIX. It's easier, and a better way to write your scripts, and you can use shellcheck to make sure you're doing it right.

run_mdadm() {
  mdadm --create /dev/md0 --level=mirror --raid-devices="$#" "$@"
}

DISK1=Samsung_SSD_850_EVO_120GB_S21SNX0H915161E DISK2=Samsung_SSD_850_EVO_120GB_S21SNX0H915160K run_mdadm $DISK1 $DISK2

mattmc3
  • 184