Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Behavior of Arrays in bash scripting and zsh shell (Start Index 0 or 1?)

I need explenation about the following behavior of arrays in shell scripting:

Imagine the following is given:

arber@host ~> ls
fileA fileB script.sh

Now i can do the following commands:

arber@host ~> ARR=($(ls -d file*))
arber@host ~> echo ${ARR[0]}          # start index 0

arber@host ~> echo ${ARR[1]}          # start index 1
fileA
arber@host ~> echo ${ARR[2]}          # start index 2
fileB

But when I do this via script.sh it behaves different (Start Index = 0):

arber@host ~> cat script.sh
#!/bin/bash
ARR=($(ls -d file*))

# get length of an array
aLen=${#ARR[@]}

# use for loop read all items (START INDEX 0)
for (( i=0; i<${aLen}; i++ ));
do
  echo ${ARR[$i]}
done

Here the result:

arber@host ~> ./script.sh
fileA
fileB

I use Ubuntu 18.04 LTS and zsh. Can someone explain this?

like image 680
Arber Avatar asked May 19 '18 16:05

Arber


2 Answers

TL;DR:

  • bash array indexing starts at 0 (always)
  • zsh array indexing starts at 1 (unless option KSH_ARRAYS is set)

To always get consistent behaviour, use:

${array[@]:offset:length}

Explanation

For code which works in both bash and zsh, you need to use the offset:length syntax rather than the [subscript] syntax.

Even for zsh-only code, you'll still need to do this (or use emulate -LR zsh) since zsh's array subscripting basis is determined by the KSH_ARRAYS option.

Eg, to reference the first element in an array:

${array[@]:0:1}

Here, array[@] is all the elements, 0 is the offset (which always is 0-based), and 1 is the number of elements desired.

like image 189
Tom Hale Avatar answered Nov 09 '22 02:11

Tom Hale


Arrays in Bash are indexed from zero, and in zsh they're indexed from one.

But you don't need the indices for a simple use case such as this. Looping over ${array[@]} works in both:

files=(file*)
for f in "${files[@]}"; do
    echo "$f"
done

In zsh you could also use $files instead of "${files[@]}", but that doesn't work in Bash. (And there's the slight difference that it drops empty array elements, but you won't get any from file names.)


Also, don't use $(ls file*), it will break if you have filenames with spaces (see WordSpliting on BashGuide), and is completely useless to begin with.

The shell is perfectly capable of generating filenames by itself. That's actually what will happen there, the shell finds all files with names matching file*, passes them to ls, and ls just prints them out again for the shell to read and process.

like image 21
ilkkachu Avatar answered Nov 09 '22 01:11

ilkkachu