Arrays of values
One name for a list of values: reading items by their position, counting them, adding one, and the quoting that keeps each item whole.
A name for a list
An array is one name holding several values. It is written with parentheses and spaces between the items: tools=(grep sort uniq). Items are counted from zero, so the first is at position zero and the last of three is at position two. The spaces separate the items; the parentheses bound the list.
To read the whole array, write ${tools[@]}, and to count the items, write ${#tools[@]}. The braces are needed because the shell has to know where the name ends — a bare dollar followed by a bracket would mean something else. The braces are part of the expansion, not the name.
The quote that keeps items whole
A loop over an array should almost always be written with quotes: for item in "${files[@]}". Written that way, an item containing a space stays one item. Written without quotes, the shell splits each item on its spaces first, and a list of two file names turns into four words. The quotes keep an item together after the array expands.
Adding an item is done with a plus sign in front of the equals: tools+=(awk) appends to the end rather than replacing the list. To print every item on its own line, pass the quoted form to printf, which repeats its pattern once per value. The plus sign is the difference between appending and replacing.
Reading, adding and looping
tools=(grep sort uniq)
echo "count: ${#tools[@]}" # count: 3
echo "first: ${tools[0]}" # first: grep
echo "all: ${tools[@]}" # all: grep sort uniq
# Append one item to the end.
tools+=(awk)
# One item per line, spaces and all.
names=("my notes.txt" "read me.md")
printf '%s\n' "${names[@]}"
# The same array, looped over with quotes.
for name in "${names[@]}"; do
echo "file: $name"
donePositions and slices
An array is a set of values under one name, and each value has an index starting at zero. ${tools[0]} is the first, ${tools[1]} the second, and ${tools[@]} is all of them. Negative indices count from the end in modern Bash, so ${tools[-1]} is the last item.
A slice takes a run of items: ${tools[@]:1:2} starts at index one and takes two values. The last number is a count of items, not an end index, which is the detail most people get wrong the first time.
Passed to printf, the whole list uses the pattern once per item, so printf '%s\n' "${tools[@]}" prints one item per line. Passed to echo, the same expansion prints them all on one line. Unquoted, the same expansion can split an item on spaces; the quoted form keeps a file name with a space in one piece.
Sparse lists and honest counts
An array can have gaps. tools[5]=awk on a three-item array sets index five and leaves three and four unset; ${#tools[@]} then counts the set items, not the highest index plus one. Looping over ${!tools[@]} visits only the indices that exist, which is usually what a sparse list wants. An index that was never set is absent, not zero.
${#tools} without the [@] is a different question: it asks for the length of the first item. That single missing piece of syntax is the most common array bug in shell scripts, and it produces a plausible number rather than an error. The two questions look similar and answer different things.
An array is a list, not a string
An array is not a string with spaces in it. Each item is a separate value, and the shell keeps the boundaries until an expansion decides what to do with them. "${arr[@]}" hands the boundaries on; ${arr[@]} lets the next step split them. The difference is invisible until an item contains a space, which is exactly when it matters.
${arr} without a subscript is the first item, not the whole array. That is the single most common array mistake, and it produces a plausible value rather than an error, so the script keeps running with the wrong data. When a count looks like a word length, check whether the [@] went missing.
A list with one name
Run it and follow the counts and the positions. Then add a fourth tool to the array before the loop that prints the count, and print the last item by its position.
The two forms ${files[@]} and "${files[@]}" look almost the same and behave differently as soon as an item contains a space. When in doubt, quote it: quoting never splits something you wanted kept together.
Three array habits: quote "${arr[@]}" when looping, because an item with a space otherwise splits; remember that ${arr} means the first item, not the whole list; and use ${#arr[@]} for a count, keeping ${#arr} for the length of one value.
Try these next
- Build an array of four words, print its count, then print a slice of two.
- Append a value with
+=(...)and print the last item by index. - Set index five on a short array and loop over
${!arr[@]}to see which positions exist.
Positions, a slice and a numbered list
Run it, then add a fifth tool and change the slice so it starts at the second item. Print the last item by computing its index from the count.
Check what you learned
One question at a time. A wrong answer explains itself — take another go.
Part 1 of 5
In tools=(grep sort uniq), what is the value of ${tools[1]}?
