← Back to DevBytes

Zsh Scripting: Arrays Complete Guide

Zsh Scripting: Arrays Complete Guide

Arrays are one of the most powerful data structures in Zsh scripting. Unlike Bash, Zsh treats arrays as first-class citizens with rich syntax, flexible indexing, and a wide range of built-in operations. Whether you are automating system tasks, parsing configuration files, or building complex CLI tools, mastering Zsh arrays will make your scripts cleaner, safer, and more expressive.

What Are Arrays in Zsh?

An array in Zsh is an ordered collection of values. Each value is identified by an index, and unlike many other shells, Zsh arrays are 1-indexed by default. This means the first element sits at index 1, not 0. Arrays can hold strings, numbers, or any combination of scalar values, and they grow or shrink dynamically as you add or remove elements.

Zsh also supports associative arrays (key-value maps), which we will cover later in this guide. Together, indexed and associative arrays give you the tools to model almost any data structure you need inside a shell script.

Why Arrays Matter in Zsh Scripting

Creating and Assigning Arrays

The simplest way to create an array is to assign a parenthesized list of words to a variable. Each word becomes an element, and quoting protects values that contain spaces.

#!/usr/bin/env zsh

# Basic indexed array
fruits=(apple banana cherry)

# Array with spaces inside elements
cities=("New York" "Los Angeles" "San Francisco")

# Empty array
empty=()

# Assign individual elements
colors=()
colors[1]=red
colors[2]=green
colors[3]=blue

You can also build arrays from command output using command substitution. Wrapping the substitution in ${(f)...} splits on newlines, which is the safest way to capture lines of output.

# Split ls output into an array, one file per element
files=(${(f)"$(ls -1)"})

# Read lines from a file
lines=(${(f)"$(cat config.txt)"})

Accessing Array Elements

To access the whole array, use the ${name} form with the array sigil. To access a single element, provide its index inside brackets. Remember that Zsh arrays start at index 1.

fruits=(apple banana cherry)

# Whole array
echo ${fruits}
# Output: apple banana cherry

# Single element
echo ${fruits[1]}
# Output: apple

echo ${fruits[2]}
# Output: banana

# Last element using negative index
echo ${fruits[-1]}
# Output: cherry

# Slice: elements 1 through 2
echo ${fruits[1,2]}
# Output: apple banana

Getting the Length and Indices

Use the # operator to get the number of elements. Use the (i) or (k) flags to retrieve indices. These are essential when looping or validating data.

fruits=(apple banana cherry)

# Number of elements
echo ${#fruits}
# Output: 3

# All indices
echo ${fruits[(i)1]}, ${fruits[(i)2]}, ${fruits[(i)3]}

# Iterate using indices
for i in {1..${#fruits}}; do
  echo "Element $i: ${fruits[$i]}"
done

Iterating Over Arrays

Zsh offers several idioms for looping through arrays. The most common is a for loop over the array directly.

fruits=(apple banana cherry)

# Simple iteration
for fruit in $fruits; do
  echo "I like $fruit"
done

# Iterating with index and value
for index in {1..${#fruits}}; do
  echo "$index: ${fruits[$index]}"
done

# Using the (K) and (V) flags for associative arrays (see below)

Adding and Removing Elements

Zsh makes it easy to append, prepend, or remove elements. The += operator appends, while [index]=() removes elements at specific positions.

fruits=(apple banana)

# Append
fruits+=(cherry date)
echo ${fruits}
# Output: apple banana cherry date

# Prepend
fruits=(apricot $fruits)
echo ${fruits}
# Output: apricot apple banana cherry date

# Remove element at index 2
fruits[2]=()
echo ${fruits}
# Output: apricot banana cherry date

# Remove last element
fruits[-1]=()
echo ${fruits}
# Output: apricot banana cherry

Array Parameter Expansion Flags

One of Zsh's standout features is its rich set of expansion flags. These let you transform arrays without external commands.

words=(zebra apple mango banana)

# Join elements with a separator
echo ${(j:, :)words}
# Output: zebra, apple, mango, banana

# Sort alphabetically
echo ${(o)words}
# Output: apple banana mango zebra

# Sort and remove duplicates
mixed=(apple banana apple cherry banana)
echo ${(ou)mixed}
# Output: apple banana cherry

# Reverse the array
echo ${(Oa)words}
# Output: banana mango apple zebra

# Uppercase every element
echo ${(U)words}
# Output: ZEBRA APPLE MANGO BANANA

# Filter elements matching a pattern
echo ${(M)words:#a*}
# Output: apple

Searching and Membership Tests

You can check whether a value exists in an array using the (r) and (i) flags. The (r) flag returns the first matching value, while (i) returns its index.

fruits=(apple banana cherry)

# Find a value
found=${fruits[(r)banana]}
if [[ -n $found ]]; then
  echo "banana is in the list"
fi

# Find an index
idx=${fruits[(i)cherry]}
echo "cherry is at index $idx"

# Check if value is NOT present (empty result means not found)
if [[ -z ${fruits[(r)grape]} ]]; then
  echo "grape is missing"
fi

Associative Arrays

Associative arrays map keys to values, similar to dictionaries in Python or objects in JavaScript. Declare them with typeset -A and assign key-value pairs.

#!/usr/bin/env zsh

# Declare an associative array
typeset -A config

# Assign values
config[host]="localhost"
config[port]="8080"
config[debug]="true"

# Access by key
echo "Host: ${config[host]}"
echo "Port: ${config[port]}"

# Iterate over keys
for key in ${(k)config}; do
  echo "$key = ${config[$key]}"
done

# Iterate over keys and values together
for key val in ${(kv)config}; do
  echo "$key -> $val"
done

# Number of entries
echo "Total config entries: ${#config}"

Reading Arrays from User Input

Reading user input into an array is useful for interactive scripts. Use read -A for indexed arrays and read -A with a prompt for a friendlier experience.

# Read multiple words into an array
echo "Enter your favorite colors separated by spaces:"
read -A colors

echo "You entered ${#colors} colors:"
for color in $colors; do
  echo " - $color"
done

Passing Arrays to Functions

Zsh functions cannot directly receive arrays as arguments because arguments are split into words. The common workaround is to pass the array name and use ${(P)name} for indirect expansion, or to serialize the array.

print_array() {
  local name=$1
  local arr=("${(@P)name}")
  for element in $arr; do
    echo "Element: $element"
  done
}

my_array=(one two three)
print_array my_array

Alternatively, you can pass elements directly and reconstruct the array inside the function, which is simpler when you do not need to preserve the original variable name.

sum_numbers() {
  local nums=("$@")
  local total=0
  for n in $nums; do
    (( total += n ))
  done
  echo "Sum: $total"
}

sum_numbers 10 20 30
# Output: Sum: 60

Practical Example: Processing a List of Files

Let's combine everything into a realistic script that collects files, filters them, and processes each one safely.

#!/usr/bin/env zsh

# Collect all .log files in the current directory
logs=(*.log)

if (( ${#logs} == 0 )); then
  echo "No log files found."
  exit 0
fi

echo "Found ${#logs} log files:"

# Sort and display
for file in ${(o)logs}; do
  size=$(stat -f%z "$file" 2>/dev/null || stat -c%s "$file" 2>/dev/null)
  echo "  - $file (${size} bytes)"
done

# Archive files larger than 1KB
large_files=()
for file in $logs; do
  size=$(stat -f%z "$file" 2>/dev/null || stat -c%s "$file" 2>/dev/null)
  if (( size > 1024 )); then
    large_files+=("$file")
  fi
done

if (( ${#large_files} > 0 )); then
  echo "Archiving large files: ${(j:, :)large_files}"
  tar -czf large_logs.tar.gz $large_files
fi

Best Practices

Common Pitfalls

Even experienced shell users stumble on a few Zsh-specific quirks. Being aware of them will save you hours of debugging.

# Pitfall 1: Forgetting 1-based indexing
arr=(a b c)
echo $arr[0]   # Empty! There is no element 0
echo $arr[1]   # Correct: a

# Pitfall 2: Unquoted array loses empty elements
arr=("one" "" "three")
echo ${#arr}        # 3
for x in $arr; do echo "[$x]"; done   # Empty element may vanish in some contexts
for x in "$arr[@]"; do echo "[$x]"; done  # Safe

# Pitfall 3: Confusing scalar and array assignment
var=apple banana    # Error: only "apple" assigned, "banana" runs as command
arr=(apple banana)  # Correct array assignment

Conclusion

Zsh arrays are a versatile and expressive feature that can dramatically improve the quality of your shell scripts. By understanding indexed and associative arrays, mastering parameter expansion flags, and following best practices around quoting and initialization, you can write scripts that are robust, readable, and maintainable. While the 1-based indexing and rich flag syntax may feel unfamiliar at first, they quickly become second nature and unlock a level of data manipulation that is difficult to achieve in other shells. Whether you are writing a quick automation snippet or a full CLI tool, arrays will be one of the most valuable tools in your Zsh scripting toolkit.

— Ad —

Google AdSense will appear here after approval

← Back to all articles