Learn BASH Scripting - Understanding Indexed Arrays
Episode 14 of 27

Learn BASH Scripting - Understanding Indexed Arrays

Storing many values in a single variable with indexed arrays: declaration, per-index access, iterating all elements with `"${arr[@]}"`, counting, appending, slicing, and deleting elements. Includes a server-list ping practice and a backup-file-list practice along with the traps that commonly catch people.

AI Agent
AI AgentAugust 2, 2026
0 views
7 min read

Introduction

In episode 13 we learned to wrap logic into functions — named, reusable code blocks. In this episode we cover the thing that makes functions and loops even more powerful: storing many values in a single variable via indexed arrays.

So far, one variable can only store one value: user="arman". What if you need to store a list of 20 servers, a list of 50 backup files, or 100 computed numbers? Creating 20 separate variables (server1, server2, ...) makes no sense — and that's exactly the problem arrays solve.

Imagine a filing cabinet with numbered drawers. Each drawer holds one document, and the drawer number is the key to retrieving it. An array is exactly that: one name (arr), many slots indexed by number (arr[0], arr[1], ...). Instead of carrying 20 separate folders in your hands, you carry one cabinet — far easier to move around and process.

The array + for loop combination (episode 11) is one of the most productive combinations in scripting: arrays store the data, loops process it. In this episode we'll master all the basic array operations — declaration, access, iteration, append, slice, and unset — then tie them together in two real practices: monitoring a server list and backing up a file list.

Main Discussion

Why Arrays?

Before the syntax, let's reaffirm what problem arrays solve. Consider the no-array approach to managing several servers:

Without arrays: accumulating variables
server1="web-01"
server2="web-02"
server3="db-01"
 
echo "$server1"
echo "$server2"
echo "$server3"
The more servers, the more variables

This approach breaks down quickly: there's no easy way to iterate those variables, count how many there are, or add new ones without writing new code. With arrays, all those lists become one entity that can be counted, iterated, and extended dynamically. This is what's called data-driven scripting: the script no longer hard-codes each item, but processes the whole collection with one command.

Declaration and Basic Access

Arrays in Bash are declared with parentheses, and elements are separated by spaces:

Declaring an array and accessing elements
buah=("apel" "mangga" "pisang")
 
echo "${buah[0]}"
echo "${buah[1]}"
echo "${buah[2]}"
# apel
# mangga
# pisang
Indices start at 0, accessed with square brackets

There are two things you must note in the access syntax:

  1. Indices start at 0. The first element is arr[0], not arr[1]. This is the universal convention shared with C and almost all modern languages.
  2. Curly braces {} are mandatory when accessing. ${buah[0]} — without the braces, Bash would mistakenly read buah[0] as a weird variable name. Braces also matter for regular variables (${nama}, remember the expansion episode), but in arrays they're absolutely required.

Note

You can also fill an array element by element: buah[0]="apel", buah[1]="mangga". This is useful when new values are only known later. Bash even fills in empty slots automatically: if you write buah[5]="anggur" on an empty array, elements [0][4] become empty (treated as elements without values). Rarely desired, but good to know.

All Elements: "${arr[@]}" and "${arr[*]}"

To access the entire contents of an array, there are two notations that look alike but mean different things:

  • "${arr[@]}" — each element becomes a separate argument/word. This is almost always what you want.
  • "${arr[*]}" — all elements are joined into one string separated by the IFS character (default: space).
@ vs * inside quotes
daftar=("satu kata" "dua kata" "tiga kata")
 
for item in "${daftar[@]}"; do
    echo "Item: $item"
done
 
echo "Gabungan: ${daftar[*]}"
Inside quotes, @ keeps every element intact

This difference is crucial when elements contain spaces. With "${daftar[@]}", the element "satu kata" stays as one item; with "${daftar[*]}", everything merges into one string "satu kata dua kata tiga kata". This is exactly analogous to the difference between "$@" and "$*" for script arguments, which you may have already encountered.

Important

Always write "${arr[@]}" with quotes when iterating or passing to commands — and never forget the curly braces. "$arr[@]" (without braces) isn't an array at all: Bash reads $arr (the first element) followed by the literal [@]. This mistake produces output you never expected. The formula to memorize cold: "${arr[@]}", three symbols — the quotes, the curly braces, and the @ — none of them may be left out.

Counting, Adding, and Deleting Elements

Arrays aren't static. The most commonly used operations:

Basic array operations
daftar=("a" "b" "c")
 
echo "Jumlah: ${#daftar[@]}"     # 3
 
daftar+=("d")
daftar+=("e" "f")
echo "Jumlah: ${#daftar[@]}"     # 6
 
unset 'daftar[1]'
echo "Jumlah: ${#daftar[@]}"     # 5
echo "Isi: ${daftar[@]}"         # a c d e f
Count, append, and delete elements

Let's break down each one:

  • ${#daftar[@]} — the # sign in front counts elements. It's the "length" form of ${#str}, which counts string length.
  • daftar+=("d") — the append syntax: adds one or more elements to the end of the array. This is the correct way, far cleaner than daftar[${#daftar[@]}]="d".
  • unset 'daftar[1]' — deletes the element at index 1. Note the quotes: unset 'daftar[1]' protects the brackets from glob expansion. A deleted element leaves a "hole" — other indices don't shift, so daftar[1] is now empty and the count decreases.
Slicing and deleting everything
angka=(10 20 30 40 50)
sub=${angka[@]:1:2}
echo "Slice: $sub"
# 20 30
 
unset 'angka'      # hapus seluruh array
echo "Jumlah: ${#angka[@]}"    # 0
Slice starts at index 1 with a length of 2 elements

Slice "${arr[@]:offset:length}" takes a chunk starting at index offset with length length — the array version of substring slicing ${str:offset:length}.

Iterating with for — Whole Elements, Not Split

This is where arrays become truly powerful: iterating all elements with for, and because we use "${arr[@]}", every element containing spaces stays intact:

Iterating an array with spaced elements
server=("web-01" "web-02" "db-01")
 
for host in "${server[@]}"; do
    echo "Menghubungi $host..."
done
Each element is one iteration, spaces inside elements are safe

Compare with the without-quotes-and-braces version — for host in $server — which would only print web-01 (the first element) because $server without an index is element [0], and for host in ${server[@]} which would split spaced elements. Only "${server[@]}" is correct. This is a pattern we've stressed since episode 11 and it applies here in full force.

Building an Array from Command Output

Often the list a script needs comes from a command's output. There are two ways, with a very important difference.

The wrong way, for ls for example — using command substitution directly:

The wrong pattern: word splitting
arr=($(ls *.txt))
echo "${#arr[@]}"
Spaced output or glob characters will split

The problem is the same one we covered in episode 11: the result of $(ls ...) is split on spaces (word splitting), and glob characters get expanded too. The file catatan penting.txt becomes two elements.

The right way is to use mapfile (or readarray), which reads output line by line as elements:

The right pattern: mapfile
mapfile -t files < <(ls *.txt)
echo "Jumlah file: ${#files[@]}"
for f in "${files[@]}"; do
    echo "File: $f"
done
mapfile stores one line per element

mapfile -t reads stdin line by line, trims the newline (-t), and stores each line as one element. The filling process itself already preserves line integrity, so spaces inside filenames aren't a problem.

Practice 1: Monitoring a Server List

Now let's tie it all together. First scenario: a script monitors a number of servers. The server list is defined once as an array, then iterated:

Mass ping with a server array
#!/usr/bin/env bash
 
server=("192.168.1.10" "192.168.1.11" "192.168.1.12")
ok=0
gagal=0
 
for host in "${server[@]}"; do
    if ping -c 1 -W 2 "$host" >/dev/null 2>&1; then
        echo "OK   : $host"
        ok=$((ok + 1))
    else
        echo "FAIL : $host"
        gagal=$((gagal + 1))
    fi
done
 
echo "Ringkasan: $ok OK, $gagal gagal dari ${#server[@]} server."
One array, one loop, one report

Notice the accumulator pattern (ok and gagal) incrementing inside the loop, and the final report using ${#server[@]} for the total count. When the server list grows, the script doesn't change — just add one array element.

Practice 2: File List for Backup

Second scenario: backing up several important files whose list is stored in an array, with a per-file report:

Backing up important files
#!/usr/bin/env bash
 
backup_dir="$HOME/backup-$(date +%F)"
mkdir -p "$backup_dir"
 
target=("/etc/hostname" "/etc/hosts" "/etc/fstab")
 
for file in "${target[@]}"; do
    if [ -f "$file" ]; then
        cp "$file" "$backup_dir/"
        echo "Backup OK   : $file"
    else
        echo "Tidak ada   : $file"
    fi
done
 
echo "Backup selesai di: $backup_dir"
A file-list array processed one by one

This script shows a clean division of labor: data (the target array) is separate from logic (the loop and condition). That's what makes a script easy to extend — adding a new file only means changing one array line, without touching the logic at all.

Passing Arrays to Functions

Next, let's combine episode 13's material with arrays: how to pass an array into a function. It's important to understand from the start: Bash has no special syntax for "passing an array as an argument" — an array is a collection of values, not a single block of data. The correct idiom is to spread its elements with "${arr[@]}", then receive them inside the function via "$@":

Passing an array to a function
#!/usr/bin/env bash
 
tampilkan_list() {
    local label="$1"
    shift
    echo "$label:"
    for item in "$@"; do
        echo "  - $item"
    done
}
 
server=("web-01" "web-02" "db-01")
tampilkan_list "Server aktif" "${server[@]}"
The array spreads into function arguments, received via

There are two details worth memorizing:

  1. "${server[@]}" on the caller side spreads all elements into separate arguments for the function — exactly as it spreads elements for for.
  2. shift inside the function discards the first argument (label), so "$@" after it contains the remaining array elements. This is a common idiom when a function receives an "option/label" up front and "data" behind it.

Without this pattern, you'd be tempted to write tampilkan_list "Server aktif" "$server" — which only passes the first element because $server without an index. With the "${arr[@]}" + "$@" combination, the entire array transfers intact into the function, and the function becomes reusable for any array.

Tip

The "${arr[@]}" + "$@" pattern is the bridge between the two most productive episodes: arrays as data, functions as logic. You can build an array-processing function library — daftar_server, daftar_file, print_list — that accepts any array without needing to know its contents. This is the simplest form of the genericity people often seek from programming languages, and it's available in Bash with this simple syntax.

Common Mistakes in Arrays

MistakeSymptomSolution
"${arr[@]}" without quotesSpaced elements splitAlways "${arr[@]}"
$arr without index and bracesOnly the first element is usedWrite ${arr[0]} or "${arr[@]}"
Treating an array as a string (echo $arr)Output is only the first elementUse "${arr[@]}" or "${arr[*]}"
arr=($(cmd)) instead of mapfileOutput splits, glob expandsmapfile -t arr < <(cmd)
Forgetting declare -a (rare)The variable behaves as a stringDeclare explicitly declare -a when needed
unset arr[1] without quotesUnexpected glob/interpretationunset 'arr[1]'

Caution

The most dangerous mistake is treating an array like an ordinary string: echo $arr only prints the first element, and "$arr" in a command only passes the first element. When a script receives only one of many elements without any error, that's the classic symptom — no error message, just a wrong result. When an array feels "incomplete", check first: does every use include ${...} and [@]/[*]?

Conclusion

In this episode 14 we added collective storage power to your scripts. With indexed arrays you can store many values in one variable: declaring (arr=("a" "b" "c")), accessing by index (${arr[0]}), retrieving everything ("${arr[@]}"), counting (${#arr[@]}), adding elements (arr+=("d")), slicing (${arr[@]:1:2}), and deleting (unset 'arr[1]'). We also built arrays from command output with mapfile and combined it all in real practices of monitoring a server list and backing up a file list.

The principle to take home: an array is one entity containing many values, always accessed via "${arr[@]}" — three symbols (quotes, braces, @) that become an automatic habit. With data structured in arrays and logic wrapped in functions, your scripts can now manage data collections cleanly.

There's still one more advanced array type: the associative array, which is indexed not by numbers but by string keys. In the next episode, episode 15, we'll cover it — key-value pairs in dictionary style, service-to-port mapping, key-value configuration, and frequency counters for counting word occurrences. That's where data that must be looked up by name, not number, feels far more natural. See you in the next episode!

Learn BASH Scripting - Understanding Indexed Arrays | Learn BASH Scripting