CrackOps
Interview PrepTracksLinuxAWS LabSoonInterview RoomSoon
Sign inStart free
CrackOps

Interview prep for the people who keep production up.

[email protected]
ProductInterview PrepTracksDashboardInterview Room
RolesDevOpsSREPlatformCloud
CompanyAboutContactPrivacyTerms
© CrackOps
Linux / Shell Scripting / bash: arrays

bash: arrays

Arrays: lists and key-value maps in bash

Intermediatebashscriptingarrays

An indexed array holds a list (hosts, files, command arguments) and an associative array maps keys to values (service to port). Arrays are the safe way to keep items that contain spaces, and to build a command's argument list piece by piece. Expand them as "${arr[@]}" and they stay intact.

Syntaxa=(x y z) a+=(w) "${a[@]}" ${a[0]} ${#a[@]} ${!a[@]} declare -A m=([k]=v) mapfile -t a < file

Watch it run

Keep a list of hosts (one name has a space in it) and loop over it correctly.

devops@devops-lab: ~/scripts — zsh — 96×28
➜ ~/scripts
Step 1 of 2
cat arrays.sh

Elements are separated by spaces inside ( ), so the name with a space must be quoted. += appends. The two loops differ only in the quotes around ${hosts[@]}.

Anatomy of a command

$ "; do ; done
loopfor h in

Loop over the words produced by the expansion that follows.

How it works

Bash arrays live only in the shell's memory: they are not strings, and they can't be exported to child processes. An indexed array is sparse: indexes are integers, need not be contiguous, and unset a[1] leaves a hole (${!a[@]} lists the indexes that exist). An associative array, created only with declare -A, is a hash table from string keys to string values and keeps no particular order. The key detail is expansion: "${a[@]}" produces one word per element with no word splitting or globbing, which is why arrays are the correct way to hold file names and argument lists. "${a[*]}" joins all elements into one word with the first character of IFS, and unquoted ${a[@]} is split again on spaces. Inside [ ] of an indexed array the subscript is evaluated as arithmetic, which is why a word like orders-api silently becomes index 0 if you forget declare -A. mapfile (readarray) reads a file or stream into an array, one element per line.

Options you'll actually use

a=(x "y z" w)Create an indexed array with three elements
a+=(v)Append one or more elements
"${a[@]}"Every element, each as its own word: the form to use 95% of the time
${a[0]} ${a[-1]}First element / last element (bash 4.3+)
${#a[@]}Number of elements
${!a[@]}List of indexes (or keys for an associative array)
"${a[@]:1:2}"Slice: 2 elements starting at index 1
"${a[*]}"All elements joined into one string with the first char of IFS
unset 'a[2]'Remove one element (the index stays as a gap)
declare -A mCreate an associative array (required before string keys)
[[ -v m[key] ]]True if the key exists (bash 4.3+)
mapfile -t a < fileRead lines into an array; -t strips the newlines
declare -p aPrint the array with its indexes: the best debug view

Common mistakes

for h in ${hosts[@]} or for h in ${hosts[*]}

Unquoted, elements are split on spaces and globbed again, so 'db primary' becomes two items.

for h in "${hosts[@]}"
echo $hosts to print the array

$hosts on its own is just ${hosts[0]}, the first element.

declare -p hosts # or: printf '%s\n' "${hosts[@]}"
port=([orders-api]=8080) without declare -A

It becomes an indexed array; string subscripts are evaluated as arithmetic and silently collapse to index 0.

declare -A port=([orders-api]=8080)
files=($(ls *.log)) or hosts=($(cat hosts.txt))

Command output is word-split and glob-expanded: names with spaces break and a * in the data expands to file names.

files=(*.log) mapfile -t hosts < hosts.txt
export hosts and expecting a child script to see the array

The environment can only carry strings. Arrays can't be exported.

Pass the elements as arguments: ./child.sh "${hosts[@]}"

Interview questions

Practice

Try these on a Linux machine of your own. Each task has a hint and a solution.

  1. 1

    Create an array with the names web-01, web-02 and 'db primary', then print how many elements it has.

  2. 2

    Read ~/data/hosts.txt into an array with mapfile and print the last host.

  3. 3

    Make an associative array with orders-api=8080 and payments=8081 and print every key with its value.

  4. 4

    Count how many requests each status code has in ~/logs/access.log using an associative array (status is field 9).

Related commands

bash: loopsLoops: for, while read, until and retrybash: argumentsScript arguments: $1, $#, "$@", shift and getoptsbash: variablesShell variables: assign, quote, default and exportxargsBuild and run commands from standard input
Previousbash: functionsNextbash: exit codes
Linux / Shell Scripting / bash: arrays

bash: arrays

Arrays: lists and key-value maps in bash

Intermediatebashscriptingarrays

An indexed array holds a list (hosts, files, command arguments) and an associative array maps keys to values (service to port). Arrays are the safe way to keep items that contain spaces, and to build a command's argument list piece by piece. Expand them as "${arr[@]}" and they stay intact.

Syntaxa=(x y z) a+=(w) "${a[@]}" ${a[0]} ${#a[@]} ${!a[@]} declare -A m=([k]=v) mapfile -t a < file

Watch it run

Keep a list of hosts (one name has a space in it) and loop over it correctly.

devops@devops-lab: ~/scripts — zsh — 96×28
➜ ~/scripts
Step 1 of 2
cat arrays.sh

Elements are separated by spaces inside ( ), so the name with a space must be quoted. += appends. The two loops differ only in the quotes around ${hosts[@]}.

Anatomy of a command

$ "; do ; done
loopfor h in

Loop over the words produced by the expansion that follows.

How it works

Bash arrays live only in the shell's memory: they are not strings, and they can't be exported to child processes. An indexed array is sparse: indexes are integers, need not be contiguous, and unset a[1] leaves a hole (${!a[@]} lists the indexes that exist). An associative array, created only with declare -A, is a hash table from string keys to string values and keeps no particular order. The key detail is expansion: "${a[@]}" produces one word per element with no word splitting or globbing, which is why arrays are the correct way to hold file names and argument lists. "${a[*]}" joins all elements into one word with the first character of IFS, and unquoted ${a[@]} is split again on spaces. Inside [ ] of an indexed array the subscript is evaluated as arithmetic, which is why a word like orders-api silently becomes index 0 if you forget declare -A. mapfile (readarray) reads a file or stream into an array, one element per line.

Options you'll actually use

a=(x "y z" w)Create an indexed array with three elements
a+=(v)Append one or more elements
"${a[@]}"Every element, each as its own word: the form to use 95% of the time
${a[0]} ${a[-1]}First element / last element (bash 4.3+)
${#a[@]}Number of elements
${!a[@]}List of indexes (or keys for an associative array)
"${a[@]:1:2}"Slice: 2 elements starting at index 1
"${a[*]}"All elements joined into one string with the first char of IFS
unset 'a[2]'Remove one element (the index stays as a gap)
declare -A mCreate an associative array (required before string keys)
[[ -v m[key] ]]True if the key exists (bash 4.3+)
mapfile -t a < fileRead lines into an array; -t strips the newlines
declare -p aPrint the array with its indexes: the best debug view

Common mistakes

for h in ${hosts[@]} or for h in ${hosts[*]}

Unquoted, elements are split on spaces and globbed again, so 'db primary' becomes two items.

for h in "${hosts[@]}"
echo $hosts to print the array

$hosts on its own is just ${hosts[0]}, the first element.

declare -p hosts # or: printf '%s\n' "${hosts[@]}"
port=([orders-api]=8080) without declare -A

It becomes an indexed array; string subscripts are evaluated as arithmetic and silently collapse to index 0.

declare -A port=([orders-api]=8080)
files=($(ls *.log)) or hosts=($(cat hosts.txt))

Command output is word-split and glob-expanded: names with spaces break and a * in the data expands to file names.

files=(*.log) mapfile -t hosts < hosts.txt
export hosts and expecting a child script to see the array

The environment can only carry strings. Arrays can't be exported.

Pass the elements as arguments: ./child.sh "${hosts[@]}"

Interview questions

Practice

Try these on a Linux machine of your own. Each task has a hint and a solution.

  1. 1

    Create an array with the names web-01, web-02 and 'db primary', then print how many elements it has.

  2. 2

    Read ~/data/hosts.txt into an array with mapfile and print the last host.

  3. 3

    Make an associative array with orders-api=8080 and payments=8081 and print every key with its value.

  4. 4

    Count how many requests each status code has in ~/logs/access.log using an associative array (status is field 9).

Related commands

bash: loopsLoops: for, while read, until and retrybash: argumentsScript arguments: $1, $#, "$@", shift and getoptsbash: variablesShell variables: assign, quote, default and exportxargsBuild and run commands from standard input
Previousbash: functionsNextbash: exit codes