Arrays

Newer versions of Bash support one-dimensional arrays. Array elements may be initialized with the variable[xx] notation. Alternatively, a script may introduce the entire array by an explicit declare -a variable statement. To dereference (retrieve the contents of) an array element, use curly bracket notation, that is, ${element[xx]}.

Simple array usage

#!/bin/bash

area[11]=23 area[13]=37 area[51]=UFOs

# Array members need not be consecutive or contiguous.

# Some members of the array can be left uninitialized. # Gaps in the array are okay. # In fact, arrays with sparse data ("sparse arrays") #+ are useful in spreadsheet-processing software.

echo -n "area[11] = " echo ${area[11]} # {curly brackets} needed.

echo -n "area[13] = " echo ${area[13]}

echo "Contents of area[51] are ${area[51]}."

# Contents of uninitialized array variable print blank (null variable). echo -n "area[43] = " echo ${area[43]} echo "(area[43] unassigned)"

echo

# Sum of two array variables assigned to third area[5]=`expr ${area[11]} + ${area[13]}` echo "area[5] = area[11] + area[13]" echo -n "area[5] = " echo ${area[5]}

area[6]=`expr ${area[11]} + ${area[51]}` echo "area[6] = area[11] + area[51]" echo -n "area[6] = " echo ${area[6]} # This fails because adding an integer to a string is not permitted.

echo; echo; echo

# ----------------------------------------------------------------- # Another array, "area2". # Another way of assigning array variables... # array_name=( XXX YYY ZZZ ... )

area2=( zero one two three four )

echo -n "area2[0] = " echo ${area2[0]} # Aha, zero-based indexing (first element of array is [0], not [1]).

echo -n "area2[1] = " echo ${area2[1]} # [1] is second element of array. # -----------------------------------------------------------------

echo; echo; echo

# ----------------------------------------------- # Yet another array, "area3". # Yet another way of assigning array variables... # array_name=([xx]=XXX [yy]=YYY ...)

area3=([17]=seventeen [24]=twenty-four)

echo -n "area3[17] = " echo ${area3[17]}

echo -n "area3[24] = " echo ${area3[24]} # -----------------------------------------------

exit 0

As we have seen, a convenient way of initializing an entire array is the array=( element1 element2 ... elementN ) notation.

base64_charset=( {A..Z} {a..z} {0..9} + / = )
               #  Using extended brace expansion
               #+ to initialize the elements of the array.
               #  Excerpted from vladz's "base64.sh" script
               #+ in the "Contributed Scripts" appendix.

Bash permits array operations on variables, even if the variables are not explicitly declared as arrays.

string=abcABC123ABCabc
echo ${string[@]}               # abcABC123ABCabc
echo ${string[*]}               # abcABC123ABCabc
echo ${string[0]}               # abcABC123ABCabc
echo ${string[1]}               # No output!
                                # Why?
echo ${#string[@]}              # 1
                                # One element in the array.
                                # The string itself.

Once again this demonstrates that Bash variables are untyped.

Formatting a poem

#!/bin/bash # poem.sh: Pretty-prints one of the ABS Guide author's favorite poems.

# Lines of the poem (single stanza). Line[1]="I do not know which to prefer," Line[2]="The beauty of inflections" Line[3]="Or the beauty of innuendoes," Line[4]="The blackbird whistling" Line[5]="Or just after." # Note that quoting permits embedding whitespace.

# Attribution. Attrib[1]=" Wallace Stevens" Attrib[2]="\"Thirteen Ways of Looking at a Blackbird\"" # This poem is in the Public Domain (copyright expired).

echo

tput bold # Bold print.

for index in 1 2 3 4 5 # Five lines. do printf " %s\n" "${Line[index]}" done

for index in 1 2 # Two attribution lines. do printf " %s\n" "${Attrib[index]}" done

tput sgr0 # Reset terminal. # See 'tput' docs.

echo

exit 0

# Exercise: # -------- # Modify this script to pretty-print a poem from a text data file.

Array variables have a syntax all their own, and even standard Bash commands and operators have special options adapted for array use.

Various array operations

#!/bin/bash # array-ops.sh: More fun with arrays.

array=( zero one two three four five ) # Element 0 1 2 3 4 5

echo ${array[0]} # zero echo ${array:0} # zero # Parameter expansion of first element, #+ starting at position # 0 (1st character). echo ${array:1} # ero # Parameter expansion of first element, #+ starting at position # 1 (2nd character).

echo "--------------"

echo ${#array[0]} # 4 # Length of first element of array. echo ${#array} # 4 # Length of first element of array. # (Alternate notation)

echo ${#array[1]} # 3 # Length of second element of array. # Arrays in Bash have zero-based indexing.

echo ${#array[*]} # 6 # Number of elements in array. echo ${#array[@]} # 6 # Number of elements in array.

echo "--------------"

array2=( [0]="first element" [1]="second element" [3]="fourth element" ) # ^ ^ ^ ^ ^ ^ ^ ^ ^ # Quoting permits embedding whitespace within individual array elements.

echo ${array2[0]} # first element echo ${array2[1]} # second element echo ${array2[2]} # # Skipped in initialization, and therefore null. echo ${array2[3]} # fourth element echo ${#array2[0]} # 13 (length of first element) echo ${#array2[*]} # 3 (number of elements in array)

exit

Many of the standard string operations work on arrays.

String operations on arrays

#!/bin/bash # array-strops.sh: String operations on arrays.

# Script by Michael Zick. # Used in ABS Guide with permission. # Fixups: 05 May 08, 04 Aug 08.

# In general, any string operation using the ${name ... } notation #+ can be applied to all string elements in an array, #+ with the ${name[@] ... } or ${name[*] ...} notation.

arrayZ=( one two three four five five )

echo

# Trailing Substring Extraction echo ${arrayZ[@]:0} # one two three four five five # ^ All elements.

echo ${arrayZ[@]:1} # two three four five five # ^ All elements following element[0].

echo ${arrayZ[@]:1:2} # two three # ^ Only the two elements after element[0].

echo "---------"

# Substring Removal

# Removes shortest match from front of string(s).

echo ${arrayZ[@]#f*r} # one two three five five # ^ # Applied to all elements of the array. # Matches "four" and removes it.

# Longest match from front of string(s) echo ${arrayZ[@]##t*e} # one two four five five # ^^ # Applied to all elements of the array. # Matches "three" and removes it.

# Shortest match from back of string(s) echo ${arrayZ[@]%h*e} # one two t four five five # ^ # Applied to all elements of the array. # Matches "hree" and removes it.

# Longest match from back of string(s) echo ${arrayZ[@]%%t*e} # one two four five five # ^^ # Applied to all elements of the array. # Matches "three" and removes it.

echo "----------------------"

# Substring Replacement

# Replace first occurrence of substring with replacement. echo ${arrayZ[@]/fiv/XYZ} # one two three four XYZe XYZe # ^ # Applied to all elements of the array.

# Replace all occurrences of substring. echo ${arrayZ[@]//iv/YY} # one two three four fYYe fYYe # Applied to all elements of the array.

# Delete all occurrences of substring. # Not specifing a replacement defaults to 'delete' ... echo ${arrayZ[@]//fi/} # one two three four ve ve # ^^ # Applied to all elements of the array.

# Replace front-end occurrences of substring. echo ${arrayZ[@]/#fi/XY} # one two three four XYve XYve # ^ # Applied to all elements of the array.

# Replace back-end occurrences of substring. echo ${arrayZ[@]/%ve/ZZ} # one two three four fiZZ fiZZ # ^ # Applied to all elements of the array.

echo ${arrayZ[@]/%o/XX} # one twXX three four five five # ^ # Why?

echo "-----------------------------"

replacement() { echo -n "!!!" }

echo ${arrayZ[@]/%e/$(replacement)} # ^ ^^^^^^^^^^^^^^ # on!!! two thre!!! four fiv!!! fiv!!! # The stdout of replacement() is the replacement string. # Q.E.D: The replacement action is, in effect, an 'assignment.'

echo "------------------------------------"

# Accessing the "for-each": echo ${arrayZ[@]//*/$(replacement optional_arguments)} # ^^ ^^^^^^^^^^^^^ # !!! !!! !!! !!! !!! !!!

# Now, if Bash would only pass the matched string #+ to the function being called . . .

echo

exit 0

# Before reaching for a Big Hammer -- Perl, Python, or all the rest -- # recall: # $( ... ) is command substitution. # A function runs as a sub-process. # A function writes its output (if echo-ed) to stdout. # Assignment, in conjunction with "echo" and command substitution, #+ can read a function's stdout. # The name[@] notation specifies (the equivalent of) a "for-each" #+ operation. # Bash is more powerful than you think!

Command substitution can construct the individual elements of an array.

Loading the contents of a script into an array

#!/bin/bash # script-array.sh: Loads this script into an array. # Inspired by an e-mail from Chris Martin (thanks!).

script_contents=( $(cat "$0") ) # Stores contents of this script ($0) #+ in an array.

for element in $(seq 0 $((${#script_contents[@]} - 1))) do # ${#script_contents[@]} #+ gives number of elements in the array. # # Question: # Why is seq 0 necessary? # Try changing it to seq 1. echo -n "${script_contents[$element]}" # List each field of this script on a single line. # echo -n "${script_contents[element]}" also works because of ${ ... }. echo -n " -- " # Use " -- " as a field separator. done

echo

exit 0

# Exercise: # -------- # Modify this script so it lists itself #+ in its original format, #+ complete with whitespace, line breaks, etc.

In an array context, some Bash builtins have a slightly altered meaning. For example, unset deletes array elements, or even an entire array.

Some special properties of arrays

#!/bin/bash

declare -a colors # All subsequent commands in this script will treat #+ the variable "colors" as an array.

echo "Enter your favorite colors (separated from each other by a space)."

read -a colors # Enter at least 3 colors to demonstrate features below. # Special option to 'read' command, #+ allowing assignment of elements in an array.

echo

element_count=${#colors[@]} # Special syntax to extract number of elements in array. # element_count=${#colors[*]} works also. # # The "@" variable allows word splitting within quotes #+ (extracts variables separated by whitespace). # # This corresponds to the behavior of "$@" and "$*" #+ in positional parameters.

index=0

while [ "$index" -lt "$element_count" ] do # List all the elements in the array. echo ${colors[$index]} # ${colors[index]} also works because it's within ${ ... } brackets. let "index = $index + 1" # Or: # ((index++)) done # Each array element listed on a separate line. # If this is not desired, use echo -n "${colors[$index]} " # # Doing it with a "for" loop instead: # for i in "${colors[@]}" # do # echo "$i" # done # (Thanks, S.C.)

echo

# Again, list all the elements in the array, but using a more elegant method. echo ${colors[@]} # echo ${colors[*]} also works.

echo

# The "unset" command deletes elements of an array, or entire array. unset colors[1] # Remove 2nd element of array. # Same effect as colors[1]= echo ${colors[@]} # List array again, missing 2nd element.

unset colors # Delete entire array. # unset colors[*] and #+ unset colors[@] also work. echo; echo -n "Colors gone." echo ${colors[@]} # List array again, now empty.

exit 0

As seen in the previous example, either ${array_name[@]} or ${array_name[*]} refers to all the elements of the array. Similarly, to get a count of the number of elements in an array, use either ${#array_name[@]} or ${#array_name[*]}. ${#array_name} is the length (number of characters) of ${array_name[0]}, the first element of the array.

Of empty arrays and empty elements

#!/bin/bash # empty-array.sh

# Thanks to Stephane Chazelas for the original example, #+ and to Michael Zick and Omair Eshkenazi, for extending it. # And to Nathan Coulter for clarifications and corrections.

# An empty array is not the same as an array with empty elements.

array0=( first second third ) array1=( '' ) # "array1" consists of one empty element. array2=( ) # No elements . . . "array2" is empty. array3=( ) # What about this array?

echo ListArray() { echo echo "Elements in array0: ${array0[@]}" echo "Elements in array1: ${array1[@]}" echo "Elements in array2: ${array2[@]}" echo "Elements in array3: ${array3[@]}" echo echo "Length of first element in array0 = ${#array0}" echo "Length of first element in array1 = ${#array1}" echo "Length of first element in array2 = ${#array2}" echo "Length of first element in array3 = ${#array3}" echo echo "Number of elements in array0 = ${#array0[*]}" # 3 echo "Number of elements in array1 = ${#array1[*]}" # 1 (Surprise!) echo "Number of elements in array2 = ${#array2[*]}" # 0 echo "Number of elements in array3 = ${#array3[*]}" # 0 }

# ===================================================================

ListArray

# Try extending those arrays.

# Adding an element to an array. array0=( "${array0[@]}" "new1" ) array1=( "${array1[@]}" "new1" ) array2=( "${array2[@]}" "new1" ) array3=( "${array3[@]}" "new1" )

ListArray

# or array0[${#array0[*]}]="new2" array1[${#array1[*]}]="new2" array2[${#array2[*]}]="new2" array3[${#array3[*]}]="new2"

ListArray

# When extended as above, arrays are 'stacks' ... # Above is the 'push' ... # The stack 'height' is: height=${#array2[@]} echo echo "Stack height for array2 = $height"

# The 'pop' is: unset array2[${#array2[@]}-1] # Arrays are zero-based, height=${#array2[@]} #+ which means first element has index 0. echo echo "POP" echo "New stack height for array2 = $height"

ListArray

# List only 2nd and 3rd elements of array0. from=1 # Zero-based numbering. to=2 array3=( ${array0[@]:1:2} ) echo echo "Elements in array3: ${array3[@]}"

# Works like a string (array of characters). # Try some other "string" forms.

# Replacement: array4=( ${array0[@]/second/2nd} ) echo echo "Elements in array4: ${array4[@]}"

# Replace all matching wildcarded string. array5=( ${array0[@]//new?/old} ) echo echo "Elements in array5: ${array5[@]}"

# Just when you are getting the feel for this . . . array6=( ${array0[@]#*new} ) echo # This one might surprise you. echo "Elements in array6: ${array6[@]}"

array7=( ${array0[@]#new1} ) echo # After array6 this should not be a surprise. echo "Elements in array7: ${array7[@]}"

# Which looks a lot like . . . array8=( ${array0[@]/new1/} ) echo echo "Elements in array8: ${array8[@]}"

# So what can one say about this?

# The string operations are performed on #+ each of the elements in var[@] in succession. # Therefore : Bash supports string vector operations. # If the result is a zero length string, #+ that element disappears in the resulting assignment. # However, if the expansion is in quotes, the null elements remain.

# Michael Zick: Question, are those strings hard or soft quotes? # Nathan Coulter: There is no such thing as "soft quotes." #! What's really happening is that #!+ the pattern matching happens after #!+ all the other expansions of [word] #!+ in cases like ${parameter#word}.

zap='new*' array9=( ${array0[@]/$zap/} ) echo echo "Number of elements in array9: ${#array9[@]}" array9=( "${array0[@]/$zap/}" ) echo "Elements in array9: ${array9[@]}" # This time the null elements remain. echo "Number of elements in array9: ${#array9[@]}"

# Just when you thought you were still in Kansas . . . array10=( ${array0[@]#$zap} ) echo echo "Elements in array10: ${array10[@]}" # But, the asterisk in zap won't be interpreted if quoted. array10=( ${array0[@]#"$zap"} ) echo echo "Elements in array10: ${array10[@]}" # Well, maybe we _are_ still in Kansas . . . # (Revisions to above code block by Nathan Coulter.)

# Compare array7 with array10. # Compare array8 with array9.

# Reiterating: No such thing as soft quotes! # Nathan Coulter explains: # Pattern matching of 'word' in ${parameter#word} is done after #+ parameter expansion and *before* quote removal. # In the normal case, pattern matching is done *after* quote removal.

exit

The relationship of ${array_name[@]} and ${array_name[*]} is analogous to that between $@ and $*. This powerful array notation has a number of uses.

# Copying an array. array2=( "${array1[@]}" ) # or array2="${array1[@]}" # # However, this fails with "sparse" arrays, #+ arrays with holes (missing elements) in them, #+ as Jochen DeSmet points out. # ------------------------------------------ array1[0]=0 # array1[1] not assigned array1[2]=2 array2=( "${array1[@]}" ) # Copy it?

echo ${array2[0]} # 0 echo ${array2[2]} # (null), should be 2 # ------------------------------------------

# Adding an element to an array. array=( "${array[@]}" "new element" ) # or array[${#array[*]}]="new element"

Tip: The array=( element1 element2 ... elementN ) initialization operation, with the help of command substitution, makes it possible to load the contents of a text file into an array.

#!/bin/bash

filename=sample_file

# cat sample_file # # 1 a b c # 2 d e fg

declare -a array1

array1=( `cat "$filename"`) # Loads contents # List file to stdout #+ of $filename into array1. # # array1=( `cat "$filename" | tr '\n' ' '`) # change linefeeds in file to spaces. # Not necessary because Bash does word splitting, #+ changing linefeeds to spaces.

echo ${array1[@]} # List the array. # 1 a b c 2 d e fg # # Each whitespace-separated "word" in the file #+ has been assigned to an element of the array.

element_count=${#array1[*]} echo $element_count # 8

Clever scripting makes it possible to add array operations.

Initializing arrays

#! /bin/bash # array-assign.bash

# Array operations are Bash-specific, #+ hence the ".bash" in the script name.

# Copyright (c) Michael S. Zick, 2003, All rights reserved. # License: Unrestricted reuse in any form, for any purpose. # Version: $ID$ # # Clarification and additional comments by William Park.

# Based on an example provided by Stephane Chazelas #+ which appeared in an earlier version of the #+ Advanced Bash Scripting Guide.

# Output format of the 'times' command: # User CPU <space> System CPU # User CPU of dead children <space> System CPU of dead children

# Bash has two versions of assigning all elements of an array #+ to a new array variable. # Both drop 'null reference' elements #+ in Bash versions 2.04 and later. # An additional array assignment that maintains the relationship of #+ [subscript]=value for arrays may be added to newer versions.

# Constructs a large array using an internal command, #+ but anything creating an array of several thousand elements #+ will do just fine.

declare -a bigOne=( /dev/* ) # All the files in /dev . . . echo echo 'Conditions: Unquoted, default IFS, All-Elements-Of' echo "Number of elements in array is ${#bigOne[@]}"

# set -vx

echo echo '- - testing: =( ${array[@]} ) - -' times declare -a bigTwo=( ${bigOne[@]} ) # Note parens: ^ ^ times

echo echo '- - testing: =${array[@]} - -' times declare -a bigThree=${bigOne[@]} # No parentheses this time. times

# Comparing the numbers shows that the second form, pointed out #+ by Stephane Chazelas, is faster. # # As William Park explains: #+ The bigTwo array assigned element by element (because of parentheses), #+ whereas bigThree assigned as a single string. # So, in essence, you have: # bigTwo=( [0]="..." [1]="..." [2]="..." ... ) # bigThree=( [0]="... ... ..." ) # # Verify this by: echo ${bigTwo[0]} # echo ${bigThree[0]}

# I will continue to use the first form in my example descriptions #+ because I think it is a better illustration of what is happening.

# The reusable portions of my examples will actual contain #+ the second form where appropriate because of the speedup.

# MSZ: Sorry about that earlier oversight folks.

# Note: # ---- # The "declare -a" statements in lines 32 and 44 #+ are not strictly necessary, since it is implicit #+ in the Array=( ... ) assignment form. # However, eliminating these declarations slows down #+ the execution of the following sections of the script. # Try it, and see.

exit 0

Note: Adding a superfluous declare -a statement to an array declaration may speed up execution of subsequent operations on the array.

Copying and concatenating arrays

#! /bin/bash # CopyArray.sh # # This script written by Michael Zick. # Used here with permission.

# How-To "Pass by Name & Return by Name" #+ or "Building your own assignment statement".

CpArray_Mac() {

# Assignment Command Statement Builder

echo -n 'eval ' echo -n "$2" # Destination name echo -n '=( ${' echo -n "$1" # Source name echo -n '[@]} )'

# That could all be a single command. # Matter of style only. }

declare -f CopyArray # Function "Pointer" CopyArray=CpArray_Mac # Statement Builder

Hype() {

# Hype the array named $1. # (Splice it together with array containing "Really Rocks".) # Return in array named $2.

local -a TMP local -a hype=( Really Rocks )

$($CopyArray $1 TMP) TMP=( ${TMP[@]} ${hype[@]} ) $($CopyArray TMP $2) }

declare -a before=( Advanced Bash Scripting ) declare -a after

echo "Array Before = ${before[@]}"

Hype before after

echo "Array After = ${after[@]}"

# Too much hype?

echo "What ${after[@]:3:2}?"

declare -a modest=( ${after[@]:2:1} ${after[@]:3:2} ) # ---- substring extraction ----

echo "Array Modest = ${modest[@]}"

# What happened to 'before' ?

echo "Array Before = ${before[@]}"

exit 0

More on concatenating arrays

#! /bin/bash # array-append.bash

# Copyright (c) Michael S. Zick, 2003, All rights reserved. # License: Unrestricted reuse in any form, for any purpose. # Version: $ID$ # # Slightly modified in formatting by M.C.

# Array operations are Bash-specific. # Legacy UNIX /bin/sh lacks equivalents.

# Pipe the output of this script to 'more' #+ so it doesn't scroll off the terminal. # Or, redirect output to a file.

declare -a array1=( zero1 one1 two1 ) # Subscript packed. declare -a array2=( [0]=zero2 [2]=two2 [3]=three2 ) # Subscript sparse -- [1] is not defined.

echo echo '- Confirm that the array is really subscript sparse. -' echo "Number of elements: 4" # Hard-coded for illustration. for (( i = 0 ; i < 4 ; i++ )) do echo "Element [$i]: ${array2[$i]}" done # See also the more general code example in basics-reviewed.bash.

declare -a dest

# Combine (append) two arrays into a third array. echo echo 'Conditions: Unquoted, default IFS, All-Elements-Of operator' echo '- Undefined elements not present, subscripts not maintained. -' # # The undefined elements do not exist; they are not being dropped.

dest=( ${array1[@]} ${array2[@]} ) # dest=${array1[@]}${array2[@]} # Strange results, possibly a bug.

# Now, list the result. echo echo '- - Testing Array Append - -' cnt=${#dest[@]}

echo "Number of elements: $cnt" for (( i = 0 ; i < cnt ; i++ )) do echo "Element [$i]: ${dest[$i]}" done

# Assign an array to a single array element (twice). dest[0]=${array1[@]} dest[1]=${array2[@]}

# List the result. echo echo '- - Testing modified array - -' cnt=${#dest[@]}

echo "Number of elements: $cnt" for (( i = 0 ; i < cnt ; i++ )) do echo "Element [$i]: ${dest[$i]}" done

# Examine the modified second element. echo echo '- - Reassign and list second element - -'

declare -a subArray=${dest[1]} cnt=${#subArray[@]}

echo "Number of elements: $cnt" for (( i = 0 ; i < cnt ; i++ )) do echo "Element [$i]: ${subArray[$i]}" done

# The assignment of an entire array to a single element #+ of another array using the '=${ ... }' array assignment #+ has converted the array being assigned into a string, #+ with the elements separated by a space (the first character of IFS).

# If the original elements didn't contain whitespace . . . # If the original array isn't subscript sparse . . . # Then we could get the original array structure back again.

# Restore from the modified second element. echo echo '- - Listing restored element - -'

declare -a subArray=( ${dest[1]} ) cnt=${#subArray[@]}

echo "Number of elements: $cnt" for (( i = 0 ; i < cnt ; i++ )) do echo "Element [$i]: ${subArray[$i]}" done echo '- - Do not depend on this behavior. - -' echo '- - This behavior is subject to change - -' echo '- - in versions of Bash newer than version 2.05b - -'

# MSZ: Sorry about any earlier confusion folks.

exit 0

Arrays permit deploying old familiar algorithms as shell scripts. Whether this is necessarily a good idea is left for the reader to decide.

The Bubble Sort

#!/bin/bash # bubble.sh: Bubble sort, of sorts.

# Recall the algorithm for a bubble sort. In this particular version...

# With each successive pass through the array to be sorted, #+ compare two adjacent elements, and swap them if out of order. # At the end of the first pass, the "heaviest" element has sunk to bottom. # At the end of the second pass, the next "heaviest" one has sunk next to bottom. # And so forth. # This means that each successive pass needs to traverse less of the array. # You will therefore notice a speeding up in the printing of the later passes.

exchange() { # Swaps two members of the array. local temp=${Countries[$1]} # Temporary storage #+ for element getting swapped out. Countries[$1]=${Countries[$2]} Countries[$2]=$temp

return }

declare -a Countries # Declare array, #+ optional here since it's initialized below.

# Is it permissable to split an array variable over multiple lines #+ using an escape (\)? # Yes.

Countries=(Netherlands Ukraine Zaire Turkey Russia Yemen Syria \ Brazil Argentina Nicaragua Japan Mexico Venezuela Greece England \ Israel Peru Canada Oman Denmark Wales France Kenya \ Xanadu Qatar Liechtenstein Hungary)

# "Xanadu" is the mythical place where, according to Coleridge, #+ Kubla Khan did a pleasure dome decree.

clear # Clear the screen to start with.

echo "0: ${Countries[*]}" # List entire array at pass 0.

number_of_elements=${#Countries[@]} let "comparisons = $number_of_elements - 1"

count=1 # Pass number.

while [ "$comparisons" -gt 0 ] # Beginning of outer loop do

index=0 # Reset index to start of array after each pass.

while [ "$index" -lt "$comparisons" ] # Beginning of inner loop do if [ ${Countries[$index]} \> ${Countries[`expr $index + 1`]} ] # If out of order... # Recalling that \> is ASCII comparison operator #+ within single brackets.

# if [[ ${Countries[$index]} > ${Countries[`expr $index + 1`]} ]] #+ also works. then exchange $index `expr $index + 1` # Swap. fi let "index += 1" # Or, index+=1 on Bash, ver. 3.1 or newer. done # End of inner loop

# ---------------------------------------------------------------------- # Paulo Marcel Coelho Aragao suggests for-loops as a simpler altenative. # # for (( last = $number_of_elements - 1 ; last > 0 ; last-- )) ## Fix by C.Y. Hunt ^ (Thanks!) # do # for (( i = 0 ; i < last ; i++ )) # do # [[ "${Countries[$i]}" > "${Countries[$((i+1))]}" ]] \ # && exchange $i $((i+1)) # done # done # ----------------------------------------------------------------------

let "comparisons -= 1" # Since "heaviest" element bubbles to bottom, #+ we need do one less comparison each pass.

echo echo "$count: ${Countries[@]}" # Print resultant array at end of each pass. echo let "count += 1" # Increment pass count.

done # End of outer loop # All done.

exit 0

Is it possible to nest arrays within arrays?

#!/bin/bash # "Nested" array.

# Michael Zick provided this example, #+ with corrections and clarifications by William Park.

AnArray=( $(ls --inode --ignore-backups --almost-all \ --directory --full-time --color=none --time=status \ --sort=time -l ${PWD} ) ) # Commands and options.

# Spaces are significant . . . and don't quote anything in the above.

SubArray=( ${AnArray[@]:11:1} ${AnArray[@]:6:5} ) # This array has six elements: #+ SubArray=( [0]=${AnArray[11]} [1]=${AnArray[6]} [2]=${AnArray[7]} # [3]=${AnArray[8]} [4]=${AnArray[9]} [5]=${AnArray[10]} ) # # Arrays in Bash are (circularly) linked lists #+ of type string (char *). # So, this isn't actually a nested array, #+ but it's functionally similar.

echo "Current directory and date of last status change:" echo "${SubArray[@]}"

exit 0

Embedded arrays in combination with indirect references create some fascinating possibilities

Embedded arrays and indirect references

#!/bin/bash # embedded-arrays.sh # Embedded arrays and indirect references.

# This script by Dennis Leeuw. # Used with permission. # Modified by document author.

ARRAY1=( VAR1_1=value11 VAR1_2=value12 VAR1_3=value13 )

ARRAY2=( VARIABLE="test" STRING="VAR1=value1 VAR2=value2 VAR3=value3" ARRAY21=${ARRAY1[*]} ) # Embed ARRAY1 within this second array.

function print () { OLD_IFS="$IFS" IFS=$'\n' # To print each array element #+ on a separate line. TEST1="ARRAY2[*]" local ${!TEST1} # See what happens if you delete this line. # Indirect reference. # This makes the components of $TEST1 #+ accessible to this function.

# Let's see what we've got so far. echo echo "\$TEST1 = $TEST1" # Just the name of the variable. echo; echo echo "{\$TEST1} = ${!TEST1}" # Contents of the variable. # That's what an indirect #+ reference does. echo echo "-------------------------------------------"; echo echo

# Print variable echo "Variable VARIABLE: $VARIABLE"

# Print a string element IFS="$OLD_IFS" TEST2="STRING[*]" local ${!TEST2} # Indirect reference (as above). echo "String element VAR2: $VAR2 from STRING"

# Print an array element TEST2="ARRAY21[*]" local ${!TEST2} # Indirect reference (as above). echo "Array element VAR1_1: $VAR1_1 from ARRAY21" }

print echo

exit 0

# As the author of the script notes, #+ "you can easily expand it to create named-hashes in bash." # (Difficult) exercise for the reader: implement this.

Arrays enable implementing a shell script version of the Sieve of Eratosthenes. Of course, a resource-intensive application of this nature should really be written in a compiled language, such as C. It runs excruciatingly slowly as a script.

The Sieve of Eratosthenes

#!/bin/bash # sieve.sh (ex68.sh)

# Sieve of Eratosthenes # Ancient algorithm for finding prime numbers.

# This runs a couple of orders of magnitude slower #+ than the equivalent program written in C.

LOWER_LIMIT=1 # Starting with 1. UPPER_LIMIT=1000 # Up to 1000. # (You may set this higher . . . if you have time on your hands.)

PRIME=1 NON_PRIME=0

let SPLIT=UPPER_LIMIT/2 # Optimization: # Need to test numbers only halfway to upper limit. Why?

declare -a Primes # Primes[] is an array.

initialize () { # Initialize the array.

i=$LOWER_LIMIT until [ "$i" -gt "$UPPER_LIMIT" ] do Primes[i]=$PRIME let "i += 1" done # Assume all array members guilty (prime) #+ until proven innocent. }

print_primes () { # Print out the members of the Primes[] array tagged as prime.

i=$LOWER_LIMIT

until [ "$i" -gt "$UPPER_LIMIT" ] do

if [ "${Primes[i]}" -eq "$PRIME" ] then printf "%8d" $i # 8 spaces per number gives nice, even columns. fi

let "i += 1"

done

}

sift () # Sift out the non-primes. {

let i=$LOWER_LIMIT+1 # Let's start with 2.

until [ "$i" -gt "$UPPER_LIMIT" ] do

if [ "${Primes[i]}" -eq "$PRIME" ] # Don't bother sieving numbers already sieved (tagged as non-prime). then

t=$i

while [ "$t" -le "$UPPER_LIMIT" ] do let "t += $i " Primes[t]=$NON_PRIME # Tag as non-prime all multiples. done

fi

let "i += 1" done

}

# ============================================== # main () # Invoke the functions sequentially. initialize sift print_primes # This is what they call structured programming. # ==============================================

echo

exit 0

# -------------------------------------------------------- # # Code below line will not execute, because of 'exit.'

# This improved version of the Sieve, by Stephane Chazelas, #+ executes somewhat faster.

# Must invoke with command-line argument (limit of primes).

UPPER_LIMIT=$1 # From command-line. let SPLIT=UPPER_LIMIT/2 # Halfway to max number.

Primes=( '' $(seq $UPPER_LIMIT) )

i=1 until (( ( i += 1 ) > SPLIT )) # Need check only halfway. do if [[ -n ${Primes[i]} ]] then t=$i until (( ( t += i ) > UPPER_LIMIT )) do Primes[t]= done fi done echo ${Primes[*]}

exit $?

The Sieve of Eratosthenes, Optimized

#!/bin/bash # Optimized Sieve of Eratosthenes # Script by Jared Martin, with very minor changes by ABS Guide author. # Used in ABS Guide with permission (thanks!).

# Based on script in Advanced Bash Scripting Guide. # http://tldp.org/LDP/abs/html/arrays.html#PRIMES0 (ex68.sh).

# http://www.cs.hmc.edu/~oneill/papers/Sieve-JFP.pdf (reference) # Check results against http://primes.utm.edu/lists/small/1000.txt

# Necessary but not sufficient would be, e.g., # (($(sieve 7919 | wc -w) == 1000)) && echo "7919 is the 1000th prime"

UPPER_LIMIT=${1:?"Need an upper limit of primes to search."}

Primes=( '' $(seq ${UPPER_LIMIT}) )

typeset -i i t Primes[i=1]='' # 1 is not a prime. until (( ( i += 1 ) > (${UPPER_LIMIT}/i) )) # Need check only ith-way. do # Why? if ((${Primes[t=i*(i-1), i]})) # Obscure, but instructive, use of arithmetic expansion in subscript. then until (( ( t += i ) > ${UPPER_LIMIT} )) do Primes[t]=; done fi done

# echo ${Primes[*]} echo # Change to original script for pretty-printing (80-col. display). printf "%8d" ${Primes[*]} echo; echo

exit $?

Compare these array-based prime number generators with alternatives that do not use arrays, TODO Example A-15, and Example 16-46.

Arrays lend themselves, to some extent, to emulating data structures for which Bash has no native support.

Emulating a push-down stac

#!/bin/bash # stack.sh: push-down stack simulation

# Similar to the CPU stack, a push-down stack stores data items #+ sequentially, but releases them in reverse order, last-in first-out.

BP=100 # Base Pointer of stack array. # Begin at element 100.

SP=$BP # Stack Pointer. # Initialize it to "base" (bottom) of stack.

Data= # Contents of stack location. # Must use global variable, #+ because of limitation on function return range.

# 100 Base pointer <-- Base Pointer # 99 First data item # 98 Second data item # ... More data # Last data item <-- Stack pointer

declare -a stack

push() # Push item on stack. { if [ -z "$1" ] # Nothing to push? then return fi

let "SP -= 1" # Bump stack pointer. stack[$SP]=$1

return }

pop() # Pop item off stack. { Data= # Empty out data item.

if [ "$SP" -eq "$BP" ] # Stack empty? then return fi # This also keeps SP from getting past 100, #+ i.e., prevents a runaway stack.

Data=${stack[$SP]} let "SP += 1" # Bump stack pointer. return }

status_report() # Find out what's happening. { echo "-------------------------------------" echo "REPORT" echo "Stack Pointer = $SP" echo "Just popped \""$Data"\" off the stack." echo "-------------------------------------" echo }

# ======================================================= # Now, for some fun.

echo

# See if you can pop anything off empty stack. pop status_report

echo

push garbage pop status_report # Garbage in, garbage out.

value1=23; push $value1 value2=skidoo; push $value2 value3=LAST; push $value3

pop # LAST status_report pop # skidoo status_report pop # 23 status_report # Last-in, first-out!

# Notice how the stack pointer decrements with each push, #+ and increments with each pop.

echo

exit 0

# =======================================================

# Exercises: # ---------

# 1) Modify the "push()" function to permit pushing # + multiple element on the stack with a single function call.

# 2) Modify the "pop()" function to permit popping # + multiple element from the stack with a single function call.

# 3) Add error checking to the critical functions. # That is, return an error code, depending on # + successful or unsuccessful completion of the operation, # + and take appropriate action.

# 4) Using this script as a starting point, # + write a stack-based 4-function calculator.

Fancy manipulation of array "subscripts" may require intermediate variables. For projects involving this, again consider using a more powerful programming language, such as Perl or C.

Complex array application: Exploring a weird mathematical series

#!/bin/bash

# Douglas Hofstadter's notorious "Q-series":

# Q(1) = Q(2) = 1 # Q(n) = Q(n - Q(n-1)) + Q(n - Q(n-2)), for n>2

# This is a "chaotic" integer series with strange #+ and unpredictable behavior. # The first 20 terms of the series are: # 1 1 2 3 3 4 5 5 6 6 6 8 8 8 10 9 10 11 11 12

# See Hofstadter's book, _Goedel, Escher, Bach: An Eternal Golden Braid_, #+ p. 137, ff.

LIMIT=100 # Number of terms to calculate. LINEWIDTH=20 # Number of terms printed per line.

Q[1]=1 # First two terms of series are 1. Q[2]=1

echo echo "Q-series [$LIMIT terms]:" echo -n "${Q[1]} " # Output first two terms. echo -n "${Q[2]} "

for ((n=3; n <= $LIMIT; n++)) # C-like loop expression. do # Q[n] = Q[n - Q[n-1]] + Q[n - Q[n-2]] for n>2 # Need to break the expression into intermediate terms, #+ since Bash doesn't handle complex array arithmetic very well.

let "n1 = $n - 1" # n-1 let "n2 = $n - 2" # n-2

t0=`expr $n - ${Q[n1]}` # n - Q[n-1] t1=`expr $n - ${Q[n2]}` # n - Q[n-2]

T0=${Q[t0]} # Q[n - Q[n-1]] T1=${Q[t1]} # Q[n - Q[n-2]]

Q[n]=`expr $T0 + $T1` # Q[n - Q[n-1]] + Q[n - Q[n-2]] echo -n "${Q[n]} "

if [ `expr $n % $LINEWIDTH` -eq 0 ] # Format output. then # ^ modulo echo # Break lines into neat chunks. fi

done

echo

exit 0

# This is an iterative implementation of the Q-series. # The more intuitive recursive implementation is left as an exercise. # Warning: calculating this series recursively takes a VERY long time #+ via a script. C/C++ would be orders of magnitude faster.

Bash supports only one-dimensional arrays, though a little trickery permits simulating multi-dimensional ones.

Simulating a two-dimensional array, then tilting it

#!/bin/bash # twodim.sh: Simulating a two-dimensional array.

# A one-dimensional array consists of a single row. # A two-dimensional array stores rows sequentially.

Rows=5 Columns=5 # 5 X 5 Array.

declare -a alpha # char alpha [Rows] [Columns]; # Unnecessary declaration. Why?

load_alpha () { local rc=0 local index

for i in A B C D E F G H I J K L M N O P Q R S T U V W X Y do # Use different symbols if you like. local row=`expr $rc / $Columns` local column=`expr $rc % $Rows` let "index = $row * $Rows + $column" alpha[$index]=$i # alpha[$row][$column] let "rc += 1" done

# Simpler would be #+ declare -a alpha=( A B C D E F G H I J K L M N O P Q R S T U V W X Y ) #+ but this somehow lacks the "flavor" of a two-dimensional array. }

print_alpha () { local row=0 local index

echo

while [ "$row" -lt "$Rows" ] # Print out in "row major" order: do #+ columns vary, #+ while row (outer loop) remains the same. local column=0

echo -n " " # Lines up "square" array with rotated one.

while [ "$column" -lt "$Columns" ] do let "index = $row * $Rows + $column" echo -n "${alpha[index]} " # alpha[$row][$column] let "column += 1" done

let "row += 1" echo

done

# The simpler equivalent is # echo ${alpha[*]} | xargs -n $Columns

echo }

filter () # Filter out negative array indices. {

echo -n " " # Provides the tilt. # Explain how.

if [[ "$1" -ge 0 && "$1" -lt "$Rows" && "$2" -ge 0 && "$2" -lt "$Columns" ]] then let "index = $1 * $Rows + $2" # Now, print it rotated. echo -n " ${alpha[index]}" # alpha[$row][$column] fi

}

rotate () # Rotate the array 45 degrees -- { #+ "balance" it on its lower lefthand corner. local row local column

for (( row = Rows; row > -Rows; row-- )) do # Step through the array backwards. Why?

for (( column = 0; column < Columns; column++ )) do

if [ "$row" -ge 0 ] then let "t1 = $column - $row" let "t2 = $column" else let "t1 = $column" let "t2 = $column + $row" fi

filter $t1 $t2 # Filter out negative array indices. # What happens if you don't do this? done

echo; echo

done

# Array rotation inspired by examples (pp. 143-146) in #+ "Advanced C Programming on the IBM PC," by Herbert Mayer #+ (see bibliography). # This just goes to show that much of what can be done in C #+ can also be done in shell scripting.

}

#--------------- Now, let the show begin. ------------# load_alpha # Load the array. print_alpha # Print it out. rotate # Rotate it 45 degrees counterclockwise. #-----------------------------------------------------#

exit 0

# This is a rather contrived, not to mention inelegant simulation.

# Exercises: # --------- # 1) Rewrite the array loading and printing functions # in a more intuitive and less kludgy fashion. # # 2) Figure out how the array rotation functions work. # Hint: think about the implications of backwards-indexing an array. # # 3) Rewrite this script to handle a non-square array, # such as a 6 X 4 one. # Try to minimize "distortion" when the array is rotated.

A two-dimensional array is essentially equivalent to a one-dimensional one, but with additional addressing modes for referencing and manipulating the individual elements by row and column position.

For an even more elaborate example of simulating a two-dimensional array, see TODO Example A-10.

For more interesting scripts using arrays, see: TODO Example 12-3 Example 16-46 Example A-22 Example A-44 Example A-41 Example A-42