Zsh arrays aren’t zero-indexed like in most programming languages; they’re one-indexed by default, which is a common source of confusion.
Let’s see a basic array in action. Imagine you have a list of your favorite programming languages.
# Declare an array
languages=("Python" "JavaScript" "Go" "Rust")
# Access an element (remember, it's 1-indexed!)
print ${languages[1]}
# Output: Python
print ${languages[3]}
# Output: Go
# Get the number of elements
print ${#languages[@]}
# Output: 4
# Iterate over elements
for lang in "${languages[@]}"; do
print "I like $lang."
done
# Output:
# I like Python.
# I like JavaScript.
# I like Go.
# I like Rust.
This array syntax is a way for Zsh to store and manage ordered lists of strings or other values. It solves the problem of needing to group related pieces of data together and access them programmatically. Internally, Zsh treats these as special string structures that it can parse and manipulate efficiently. The () are the delimiters for array definition, and ${array_name[index]} is the core syntax for accessing elements. The @ symbol is a wildcard meaning "all elements."
You can also assign values to specific indices, and Zsh will automatically expand the array if the index is beyond its current size.
# Assign to an existing index
languages[2]="TypeScript"
print ${languages[@]}
# Output: Python TypeScript Go Rust
# Assign to a new, higher index
languages[10]="Haskell"
print ${#languages[@]}
# Output: 10
print ${languages[10]}
# Output: Haskell
print ${languages[5]}
# Output: (empty string)
The real power comes from Zsh’s extended array slicing and manipulation capabilities. You can grab ranges of elements, reverse arrays, and even perform operations like joining or splitting.
# Get a slice (elements 2 through 4)
print ${languages[2,4]}
# Output: TypeScript Go Rust
# Get all elements from index 3 onwards
print ${languages[3,-1]}
# Output: Go Rust Haskell
# Reverse the array
print ${languages[@]/#//}`
# Output: Haskell Rust Go TypeScript Python
The [@] expansion, when used with ${array_name[@]}, is crucial because it expands each element of the array as a separate word. If you were to use ${array_name[*]}, all elements would be expanded as a single word, joined by the first character of the IFS (Internal Field Separator) variable, which is usually a space. This distinction is vital for correctly passing array elements as distinct arguments to commands.
When you assign a value to an index that doesn’t exist, Zsh doesn’t just add that element; it creates a sparse array, filling in any intermediate indices with empty values. This means you can jump around in your array assignments without needing to pre-allocate space, but it also means that accessing an index that hasn’t been explicitly set will yield an empty string.
The (/#//) syntax is a pattern substitution applied to the entire array. The # signifies the beginning of each element, and // is the replacement string (empty in this case), effectively reversing the order of elements. This is a compact way to achieve array reversal without explicit loops.
A subtle but powerful feature is the ability to perform arithmetic on array indices. For example, you can access the last element of an array without knowing its exact size using negative indexing: ${array[-1]}.
The next concept you’ll likely encounter is associative arrays, which use string keys instead of numerical indices, offering a more dictionary-like data structure.