# Associative Arrays: Lists with Names

An associative array finds each value by a name instead of a number: declare -A total makes one, total\[chill\]=15 stores a value under a name, "${!total\[@\]}" lists the names, and adding to total\[$mood\] inside a loop keeps a total for every category; it needs bash 4 or newer, which on a Mac means bash 5 from Homebrew.

Freelancer · Unix · Linux · Updated November 10, 2026

## After this lesson you can

- Store and look up values by name in an associative array made with declare -A
- Add up a total per category with an associative array, and loop over its keys with "${!total\[@\]}"

## Key takeaways

- An associative array finds a value by its name, called its key: best[chill]="Window Down" stores a value, and ${best[chill]} reads it.
- declare -A total has to come first. Without it, bash makes a numbered array, and every name lands in slot 0.
- total[$mood]=$(( total[$mood] + minutes )) inside a loop keeps a total for every category the file has, even ones the script has never heard of.
- "${!total[@]}" is every key, in no set order, so pipe the loop's output to sort.
- Associative arrays need bash 4 or newer. A Mac's own bash is 3.2, so scripts run in bash 5 from Homebrew, found by #!/usr/bin/env bash.

How many minutes of each mood does your playlist have: enough chill songs for the drive home? The same question runs your money at the end of the month: how much went on food, how much on the bus, how much on fun? Some people budget with envelopes, one labeled for each kind of spending, and drop each receipt's amount into the right one. An array with numbered slots can't do that, because the answer is a number for each *name*. This lesson's list finds its slots by name. It's how `spend` will work out its report, one total per category, and how an administrator's script counts errors per server or logins per user.

## The long way: a variable for each mood

In the shuffler folder (`mkdir -p ~/terminal-practice/shuffler && cd ~/terminal-practice/shuffler`), make a playlist where each line is a mood, the minutes, then the title:

```bash
printf '%s\n' 'chill 4 Highway Tea' 'upbeat 3 Paper Lanterns' 'chill 5 Window Down' 'upbeat 4 Gas Station Sunrise' 'singalong 5 Rainy Tuesday' 'chill 6 Last Exit Waltz' 'upbeat 3 Home by Dark' > playlist.txt
```

With what you know so far, you'd keep a variable for each mood and a `case` to pick the right one (*Menus and Choices: case and select*):

```bash
chill_total=0
upbeat_total=0
while read -r mood minutes title; do
  case $mood in
    chill) chill_total=$((chill_total + minutes)) ;;
    upbeat) upbeat_total=$((upbeat_total + minutes)) ;;
  esac
done < playlist.txt
echo "chill: $chill_total, upbeat: $upbeat_total"
```

```text
chill: 15, upbeat: 10
```

*Rainy Tuesday* is missing, with no error: the script only knows the moods written into it. Add a song with a new mood, and you have to edit the script again.

## An associative array: slots with names

An **associative array** is a row of envelopes with labels instead of numbers. Each label is a **key**, and what's in the envelope is its value, so each slot holds a **key-value pair**. Make one with `declare -A` (read "declare associative"), then store and read values by key. Here's your favorite song for each mood:

```bash
declare -A best
best[chill]="Window Down"
best[upbeat]="Paper Lanterns"
echo "${best[chill]}"
echo "${#best[@]}"
echo "${!best[@]}"
```

```text
Window Down
2
chill upbeat
```

`${#best[@]}` counts the pairs, as it counted songs in *Arrays*, and `${!best[@]}` (read "the keys of best") is every key. A key that isn't there reads as empty, so `${best[party]}` prints nothing.

`declare -A` has to come first. Skip it, and bash makes an ordinary numbered array, reads `chill` and `upbeat` as the number 0, and puts both values in the same slot:

```bash
favorite[chill]="Window Down"
favorite[upbeat]="Paper Lanterns"
echo "${favorite[chill]}"
```

```text
Paper Lanterns
```

Inside a function, `declare -A` makes the array the function's own, the way `local` does (*Local Variables and Getting a Value Back*).

## A total for every mood

Now the playlist, with no mood written into the script:

```bash
declare -A total
while read -r mood minutes title; do
  total[$mood]=$(( total[$mood] + minutes ))
done < playlist.txt
for mood in "${!total[@]}"; do
  echo "$mood: ${total[$mood]} minutes"
done
```

```text
singalong: 5 minutes
chill: 15 minutes
upbeat: 10 minutes
```

Read `total[$mood]` as "the envelope labeled with this line's mood". The first time a mood comes up, its slot is empty, and an empty value counts as 0 inside `$(( ))`, so the first song starts its total. `singalong` came along by itself.

![The total array as a row of three envelopes labeled with names instead of numbers: the chill envelope holds 15, the upbeat envelope 10 and the singalong envelope 5; under each envelope is how to read it, with the envelope's name in square brackets after total, and a label says the names on the envelopes are the keys](https://fsdtrainingmedia.blob.core.windows.net/public/images/2026/11/727c98533c254652b42731d718ff57f1.webp)

The moods came out in neither the file's order nor alphabetical order: bash keeps keys in an order of its own. When order matters, pipe the loop to `sort` by changing its last line to `done | sort`:

```text
chill: 15 minutes
singalong: 5 minutes
upbeat: 10 minutes
```

The same lines add up money. Each line of `spending.txt` is a category and an amount in whole dollars, since `$(( ))` adds whole numbers only:

```bash
printf '%s\n' 'food 12' 'bus 3' 'food 9' 'fun 25' 'food 7' 'bus 3' > spending.txt
declare -A spent
while read -r category dollars; do
  spent[$category]=$(( spent[$category] + dollars ))
done < spending.txt
for category in "${!spent[@]}"; do
  echo "$category: ${spent[$category]} dollars"
done | sort
```

```text
bus: 6 dollars
food: 28 dollars
fun: 25 dollars
```

For amounts with cents, *Columns and Totals: awk* does the adding.

## Why a Mac needs bash 5

`declare -A` and `mapfile` came in bash 4, in 2009. A Mac's own bash is 3.2, from 2006, and there `declare -A` stops with an error saying `-A` is an invalid option. That's why the setup in *Setup: The Terminal on a Mac* installed bash 5 with Homebrew, and why every script starts with `#!/usr/bin/env bash`, which runs the first bash on your `PATH`: Homebrew's, once it's installed. Never `#!/bin/bash` on a Mac: that's always the old one. Check which bash your scripts get:

```bash
bash --version | head -n 1
```

```text
GNU bash, version 5.3.9(1)-release (x86_64-pc-linux-gnu)
```

Yours should say version 5 or newer; the part in brackets names your own system. zsh at a Mac's prompt has associative arrays too, made with `typeset -A`, but writes some of these lines differently, so try them in a script, or after typing `bash`.

## Check yourself

2 questions to try on the lesson page.

## So what

An associative array is a set of labeled envelopes: drop each value into the one with its name, and you get a total for every category in the file, which is the heart of every report, from a playlist's moods to a month's spending.

## Key terms

- [Associative Array](https://www.ask-codey.com/glossary/associative-array-unix)
- [declare](https://www.ask-codey.com/glossary/declare)
- [Key-Value Pair](https://www.ask-codey.com/glossary/key-value-pair-unix)
- [Homebrew](https://www.ask-codey.com/glossary/homebrew)

## Cheat sheets

- [Associative Arrays: Lists with Names](https://www.ask-codey.com/cheat-sheets/bash-associative-arrays-cheat-sheet)

## Related

- [Arrays](https://www.ask-codey.com/lessons/bash-arrays)
- [Stopping Early: break, continue and Counters](https://www.ask-codey.com/lessons/bash-loop-control)
- [Columns and Totals: awk](https://www.ask-codey.com/lessons/bash-awk)
- [Setup: The Terminal on a Mac](https://www.ask-codey.com/lessons/bash-setup-macos)

Source: https://www.ask-codey.com/lessons/bash-associative-arrays
