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]}.
Example 27-1. Simple array usage
1 #!/bin/bash
2
3
4 area[11]=23
5 area[13]=37
6 area[51]=UFOs
7
8 # Array members need not be consecutive or contiguous.
9
10 # Some members of the array can be left uninitialized.
11 # Gaps in the array are okay.
12 # In fact, arrays with sparse data ("sparse arrays")
13 #+ are useful in spreadsheet-processing software.
14
15
16 echo -n "area[11] = "
17 echo ${area[11]} # {curly brackets} needed.
18
19 echo -n "area[13] = "
20 echo ${area[13]}
21
22 echo "Contents of area[51] are ${area[51]}."
23
24 # Contents of uninitialized array variable print blank (null variable).
25 echo -n "area[43] = "
26 echo ${area[43]}
27 echo "(area[43] unassigned)"
28
29 echo
30
31 # Sum of two array variables assigned to third
32 area[5]=`expr ${area[11]} + ${area[13]}`
33 echo "area[5] = area[11] + area[13]"
34 echo -n "area[5] = "
35 echo ${area[5]}
36
37 area[6]=`expr ${area[11]} + ${area[51]}`
38 echo "area[6] = area[11] + area[51]"
39 echo -n "area[6] = "
40 echo ${area[6]}
41 # This fails because adding an integer to a string is not permitted.
42
43 echo; echo; echo
44
45 # -----------------------------------------------------------------
46 # Another array, "area2".
47 # Another way of assigning array variables...
48 # array_name=( XXX YYY ZZZ ... )
49
50 area2=( zero one two three four )
51
52 echo -n "area2[0] = "
53 echo ${area2[0]}
54 # Aha, zero-based indexing (first element of array is [0], not [1]).
55
56 echo -n "area2[1] = "
57 echo ${area2[1]} # [1] is second element of array.
58 # -----------------------------------------------------------------
59
60 echo; echo; echo
61
62 # -----------------------------------------------
63 # Yet another array, "area3".
64 # Yet another way of assigning array variables...
65 # array_name=([xx]=XXX [yy]=YYY ...)
66
67 area3=([17]=seventeen [24]=twenty-four)
68
69 echo -n "area3[17] = "
70 echo ${area3[17]}
71
72 echo -n "area3[24] = "
73 echo ${area3[24]}
74 # -----------------------------------------------
75
76 exit 0 |
As we have seen, a convenient way of initializing an entire array is the array=( element1 element2 ... elementN ) notation.
1 base64_charset=( {A..Z} {a..z} {0..9} + / = )
2 # Using extended brace expansion
3 #+ to initialize the elements of the array.
4 # Excerpted from vladz's "base64.sh" script
5 #+ in the "Contributed Scripts" appendix. |
Bash permits array operations on variables, even if the variables are not explicitly declared as arrays.
|
Example 27-2. Formatting a poem
1 #!/bin/bash
2 # poem.sh: Pretty-prints one of the ABS Guide author's favorite poems.
3
4 # Lines of the poem (single stanza).
5 Line[1]="I do not know which to prefer,"
6 Line[2]="The beauty of inflections"
7 Line[3]="Or the beauty of innuendoes,"
8 Line[4]="The blackbird whistling"
9 Line[5]="Or just after."
10 # Note that quoting permits embedding whitespace.
11
12 # Attribution.
13 Attrib[1]=" Wallace Stevens"
14 Attrib[2]="\"Thirteen Ways of Looking at a Blackbird\""
15 # This poem is in the Public Domain (copyright expired).
16
17 echo
18
19 tput bold # Bold print.
20
21 for index in 1 2 3 4 5 # Five lines.
22 do
23 printf " %s\n" "${Line[index]}"
24 done
25
26 for index in 1 2 # Two attribution lines.
27 do
28 printf " %s\n" "${Attrib[index]}"
29 done
30
31 tput sgr0 # Reset terminal.
32 # See 'tput' docs.
33
34 echo
35
36 exit 0
37
38 # Exercise:
39 # --------
40 # 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.
Example 27-3. Various array operations
1 #!/bin/bash
2 # array-ops.sh: More fun with arrays.
3
4
5 array=( zero one two three four five )
6 # Element 0 1 2 3 4 5
7
8 echo ${array[0]} # zero
9 echo ${array:0} # zero
10 # Parameter expansion of first element,
11 #+ starting at position # 0 (1st character).
12 echo ${array:1} # ero
13 # Parameter expansion of first element,
14 #+ starting at position # 1 (2nd character).
15
16 echo "--------------"
17
18 echo ${#array[0]} # 4
19 # Length of first element of array.
20 echo ${#array} # 4
21 # Length of first element of array.
22 # (Alternate notation)
23
24 echo ${#array[1]} # 3
25 # Length of second element of array.
26 # Arrays in Bash have zero-based indexing.
27
28 echo ${#array[*]} # 6
29 # Number of elements in array.
30 echo ${#array[@]} # 6
31 # Number of elements in array.
32
33 echo "--------------"
34
35 array2=( [0]="first element" [1]="second element" [3]="fourth element" )
36 # ^ ^ ^ ^ ^ ^ ^ ^ ^
37 # Quoting permits embedding whitespace within individual array elements.
38
39 echo ${array2[0]} # first element
40 echo ${array2[1]} # second element
41 echo ${array2[2]} #
42 # Skipped in initialization, and therefore null.
43 echo ${array2[3]} # fourth element
44 echo ${#array2[0]} # 13 (length of first element)
45 echo ${#array2[*]} # 3 (number of elements in array)
46
47 exit |
Many of the standard string operations work on arrays.
Example 27-4. String operations on arrays
1 #!/bin/bash
2 # array-strops.sh: String operations on arrays.
3
4 # Script by Michael Zick.
5 # Used in ABS Guide with permission.
6 # Fixups: 05 May 08, 04 Aug 08.
7
8 # In general, any string operation using the ${name ... } notation
9 #+ can be applied to all string elements in an array,
10 #+ with the ${name[@] ... } or ${name[*] ...} notation.
11
12
13 arrayZ=( one two three four five five )
14
15 echo
16
17 # Trailing Substring Extraction
18 echo ${arrayZ[@]:0} # one two three four five five
19 # ^ All elements.
20
21 echo ${arrayZ[@]:1} # two three four five five
22 # ^ All elements following element[0].
23
24 echo ${arrayZ[@]:1:2} # two three
25 # ^ Only the two elements after element[0].
26
27 echo "---------"
28
29
30 # Substring Removal
31
32 # Removes shortest match from front of string(s).
33
34 echo ${arrayZ[@]#f*r} # one two three five five
35 # ^ # Applied to all elements of the array.
36 # Matches "four" and removes it.
37
38 # Longest match from front of string(s)
39 echo ${arrayZ[@]##t*e} # one two four five five
40 # ^^ # Applied to all elements of the array.
41 # Matches "three" and removes it.
42
43 # Shortest match from back of string(s)
44 echo ${arrayZ[@]%h*e} # one two t four five five
45 # ^ # Applied to all elements of the array.
46 # Matches "hree" and removes it.
47
48 # Longest match from back of string(s)
49 echo ${arrayZ[@]%%t*e} # one two four five five
50 # ^^ # Applied to all elements of the array.
51 # Matches "three" and removes it.
52
53 echo "----------------------"
54
55
56 # Substring Replacement
57
58 # Replace first occurrence of substring with replacement.
59 echo ${arrayZ[@]/fiv/XYZ} # one two three four XYZe XYZe
60 # ^ # Applied to all elements of the array.
61
62 # Replace all occurrences of substring.
63 echo ${arrayZ[@]//iv/YY} # one two three four fYYe fYYe
64 # Applied to all elements of the array.
65
66 # Delete all occurrences of substring.
67 # Not specifing a replacement defaults to 'delete' ...
68 echo ${arrayZ[@]//fi/} # one two three four ve ve
69 # ^^ # Applied to all elements of the array.
70
71 # Replace front-end occurrences of substring.
72 echo ${arrayZ[@]/#fi/XY} # one two three four XYve XYve
73 # ^ # Applied to all elements of the array.
74
75 # Replace back-end occurrences of substring.
76 echo ${arrayZ[@]/%ve/ZZ} # one two three four fiZZ fiZZ
77 # ^ # Applied to all elements of the array.
78
79 echo ${arrayZ[@]/%o/XX} # one twXX three four five five
80 # ^ # Why?
81
82 echo "-----------------------------"
83
84
85 replacement() {
86 echo -n "!!!"
87 }
88
89 echo ${arrayZ[@]/%e/$(replacement)}
90 # ^ ^^^^^^^^^^^^^^
91 # on!!! two thre!!! four fiv!!! fiv!!!
92 # The stdout of replacement() is the replacement string.
93 # Q.E.D: The replacement action is, in effect, an 'assignment.'
94
95 echo "------------------------------------"
96
97 # Accessing the "for-each":
98 echo ${arrayZ[@]//*/$(replacement optional_arguments)}
99 # ^^ ^^^^^^^^^^^^^
100 # !!! !!! !!! !!! !!! !!!
101
102 # Now, if Bash would only pass the matched string
103 #+ to the function being called . . .
104
105 echo
106
107 exit 0
108
109 # Before reaching for a Big Hammer -- Perl, Python, or all the rest --
110 # recall:
111 # $( ... ) is command substitution.
112 # A function runs as a sub-process.
113 # A function writes its output (if echo-ed) to stdout.
114 # Assignment, in conjunction with "echo" and command substitution,
115 #+ can read a function's stdout.
116 # The name[@] notation specifies (the equivalent of) a "for-each"
117 #+ operation.
118 # Bash is more powerful than you think! |
Command substitution can construct the individual elements of an array.
Example 27-5. Loading the contents of a script into an array
1 #!/bin/bash
2 # script-array.sh: Loads this script into an array.
3 # Inspired by an e-mail from Chris Martin (thanks!).
4
5 script_contents=( $(cat "$0") ) # Stores contents of this script ($0)
6 #+ in an array.
7
8 for element in $(seq 0 $((${#script_contents[@]} - 1)))
9 do # ${#script_contents[@]}
10 #+ gives number of elements in the array.
11 #
12 # Question:
13 # Why is seq 0 necessary?
14 # Try changing it to seq 1.
15 echo -n "${script_contents[$element]}"
16 # List each field of this script on a single line.
17 # echo -n "${script_contents[element]}" also works because of ${ ... }.
18 echo -n " -- " # Use " -- " as a field separator.
19 done
20
21 echo
22
23 exit 0
24
25 # Exercise:
26 # --------
27 # Modify this script so it lists itself
28 #+ in its original format,
29 #+ 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.
Example 27-6. Some special properties of arrays
1 #!/bin/bash
2
3 declare -a colors
4 # All subsequent commands in this script will treat
5 #+ the variable "colors" as an array.
6
7 echo "Enter your favorite colors (separated from each other by a space)."
8
9 read -a colors # Enter at least 3 colors to demonstrate features below.
10 # Special option to 'read' command,
11 #+ allowing assignment of elements in an array.
12
13 echo
14
15 element_count=${#colors[@]}
16 # Special syntax to extract number of elements in array.
17 # element_count=${#colors[*]} works also.
18 #
19 # The "@" variable allows word splitting within quotes
20 #+ (extracts variables separated by whitespace).
21 #
22 # This corresponds to the behavior of "$@" and "$*"
23 #+ in positional parameters.
24
25 index=0
26
27 while [ "$index" -lt "$element_count" ]
28 do # List all the elements in the array.
29 echo ${colors[$index]}
30 # ${colors[index]} also works because it's within ${ ... } brackets.
31 let "index = $index + 1"
32 # Or:
33 # ((index++))
34 done
35 # Each array element listed on a separate line.
36 # If this is not desired, use echo -n "${colors[$index]} "
37 #
38 # Doing it with a "for" loop instead:
39 # for i in "${colors[@]}"
40 # do
41 # echo "$i"
42 # done
43 # (Thanks, S.C.)
44
45 echo
46
47 # Again, list all the elements in the array, but using a more elegant method.
48 echo ${colors[@]} # echo ${colors[*]} also works.
49
50 echo
51
52 # The "unset" command deletes elements of an array, or entire array.
53 unset colors[1] # Remove 2nd element of array.
54 # Same effect as colors[1]=
55 echo ${colors[@]} # List array again, missing 2nd element.
56
57 unset colors # Delete entire array.
58 # unset colors[*] and
59 #+ unset colors[@] also work.
60 echo; echo -n "Colors gone."
61 echo ${colors[@]} # List array again, now empty.
62
63 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.
Example 27-7. Of empty arrays and empty elements
1 #!/bin/bash
2 # empty-array.sh
3
4 # Thanks to Stephane Chazelas for the original example,
5 #+ and to Michael Zick and Omair Eshkenazi, for extending it.
6 # And to Nathan Coulter for clarifications and corrections.
7
8
9 # An empty array is not the same as an array with empty elements.
10
11 array0=( first second third )
12 array1=( '' ) # "array1" consists of one empty element.
13 array2=( ) # No elements . . . "array2" is empty.
14 array3=( ) # What about this array?
15
16
17 echo
18 ListArray()
19 {
20 echo
21 echo "Elements in array0: ${array0[@]}"
22 echo "Elements in array1: ${array1[@]}"
23 echo "Elements in array2: ${array2[@]}"
24 echo "Elements in array3: ${array3[@]}"
25 echo
26 echo "Length of first element in array0 = ${#array0}"
27 echo "Length of first element in array1 = ${#array1}"
28 echo "Length of first element in array2 = ${#array2}"
29 echo "Length of first element in array3 = ${#array3}"
30 echo
31 echo "Number of elements in array0 = ${#array0[*]}" # 3
32 echo "Number of elements in array1 = ${#array1[*]}" # 1 (Surprise!)
33 echo "Number of elements in array2 = ${#array2[*]}" # 0
34 echo "Number of elements in array3 = ${#array3[*]}" # 0
35 }
36
37 # ===================================================================
38
39 ListArray
40
41 # Try extending those arrays.
42
43 # Adding an element to an array.
44 array0=( "${array0[@]}" "new1" )
45 array1=( "${array1[@]}" "new1" )
46 array2=( "${array2[@]}" "new1" )
47 array3=( "${array3[@]}" "new1" )
48
49 ListArray
50
51 # or
52 array0[${#array0[*]}]="new2"
53 array1[${#array1[*]}]="new2"
54 array2[${#array2[*]}]="new2"
55 array3[${#array3[*]}]="new2"
56
57 ListArray
58
59 # When extended as above, arrays are 'stacks' ...
60 # Above is the 'push' ...
61 # The stack 'height' is:
62 height=${#array2[@]}
63 echo
64 echo "Stack height for array2 = $height"
65
66 # The 'pop' is:
67 unset array2[${#array2[@]}-1] # Arrays are zero-based,
68 height=${#array2[@]} #+ which means first element has index 0.
69 echo
70 echo "POP"
71 echo "New stack height for array2 = $height"
72
73 ListArray
74
75 # List only 2nd and 3rd elements of array0.
76 from=1 # Zero-based numbering.
77 to=2
78 array3=( ${array0[@]:1:2} )
79 echo
80 echo "Elements in array3: ${array3[@]}"
81
82 # Works like a string (array of characters).
83 # Try some other "string" forms.
84
85 # Replacement:
86 array4=( ${array0[@]/second/2nd} )
87 echo
88 echo "Elements in array4: ${array4[@]}"
89
90 # Replace all matching wildcarded string.
91 array5=( ${array0[@]//new?/old} )
92 echo
93 echo "Elements in array5: ${array5[@]}"
94
95 # Just when you are getting the feel for this . . .
96 array6=( ${array0[@]#*new} )
97 echo # This one might surprise you.
98 echo "Elements in array6: ${array6[@]}"
99
100 array7=( ${array0[@]#new1} )
101 echo # After array6 this should not be a surprise.
102 echo "Elements in array7: ${array7[@]}"
103
104 # Which looks a lot like . . .
105 array8=( ${array0[@]/new1/} )
106 echo
107 echo "Elements in array8: ${array8[@]}"
108
109 # So what can one say about this?
110
111 # The string operations are performed on
112 #+ each of the elements in var[@] in succession.
113 # Therefore : Bash supports string vector operations.
114 # If the result is a zero length string,
115 #+ that element disappears in the resulting assignment.
116 # However, if the expansion is in quotes, the null elements remain.
117
118 # Michael Zick: Question, are those strings hard or soft quotes?
119 # Nathan Coulter: There is no such thing as "soft quotes."
120 #! What's really happening is that
121 #!+ the pattern matching happens after
122 #!+ all the other expansions of [word]
123 #!+ in cases like ${parameter#word}.
124
125
126 zap='new*'
127 array9=( ${array0[@]/$zap/} )
128 echo
129 echo "Number of elements in array9: ${#array9[@]}"
130 array9=( "${array0[@]/$zap/}" )
131 echo "Elements in array9: ${array9[@]}"
132 # This time the null elements remain.
133 echo "Number of elements in array9: ${#array9[@]}"
134
135
136 # Just when you thought you were still in Kansas . . .
137 array10=( ${array0[@]#$zap} )
138 echo
139 echo "Elements in array10: ${array10[@]}"
140 # But, the asterisk in zap won't be interpreted if quoted.
141 array10=( ${array0[@]#"$zap"} )
142 echo
143 echo "Elements in array10: ${array10[@]}"
144 # Well, maybe we _are_ still in Kansas . . .
145 # (Revisions to above code block by Nathan Coulter.)
146
147
148 # Compare array7 with array10.
149 # Compare array8 with array9.
150
151 # Reiterating: No such thing as soft quotes!
152 # Nathan Coulter explains:
153 # Pattern matching of 'word' in ${parameter#word} is done after
154 #+ parameter expansion and *before* quote removal.
155 # In the normal case, pattern matching is done *after* quote removal.
156
157 exit |
The relationship of ${array_name[@]} and ${array_name[*]} is analogous to that between $@ and $*. This powerful array notation has a number of uses.
1 # Copying an array.
2 array2=( "${array1[@]}" )
3 # or
4 array2="${array1[@]}"
5 #
6 # However, this fails with "sparse" arrays,
7 #+ arrays with holes (missing elements) in them,
8 #+ as Jochen DeSmet points out.
9 # ------------------------------------------
10 array1[0]=0
11 # array1[1] not assigned
12 array1[2]=2
13 array2=( "${array1[@]}" ) # Copy it?
14
15 echo ${array2[0]} # 0
16 echo ${array2[2]} # (null), should be 2
17 # ------------------------------------------
18
19
20
21 # Adding an element to an array.
22 array=( "${array[@]}" "new element" )
23 # or
24 array[${#array[*]}]="new element"
25
26 # Thanks, S.C. |
![]() | 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.
|
Clever scripting makes it possible to add array operations.
Example 27-8. Initializing arrays
1 #! /bin/bash
2 # array-assign.bash
3
4 # Array operations are Bash-specific,
5 #+ hence the ".bash" in the script name.
6
7 # Copyright (c) Michael S. Zick, 2003, All rights reserved.
8 # License: Unrestricted reuse in any form, for any purpose.
9 # Version: $ID$
10 #
11 # Clarification and additional comments by William Park.
12
13 # Based on an example provided by Stephane Chazelas
14 #+ which appeared in an earlier version of the
15 #+ Advanced Bash Scripting Guide.
16
17 # Output format of the 'times' command:
18 # User CPU <space> System CPU
19 # User CPU of dead children <space> System CPU of dead children
20
21 # Bash has two versions of assigning all elements of an array
22 #+ to a new array variable.
23 # Both drop 'null reference' elements
24 #+ in Bash versions 2.04 and later.
25 # An additional array assignment that maintains the relationship of
26 #+ [subscript]=value for arrays may be added to newer versions.
27
28 # Constructs a large array using an internal command,
29 #+ but anything creating an array of several thousand elements
30 #+ will do just fine.
31
32 declare -a bigOne=( /dev/* ) # All the files in /dev . . .
33 echo
34 echo 'Conditions: Unquoted, default IFS, All-Elements-Of'
35 echo "Number of elements in array is ${#bigOne[@]}"
36
37 # set -vx
38
39
40
41 echo
42 echo '- - testing: =( ${array[@]} ) - -'
43 times
44 declare -a bigTwo=( ${bigOne[@]} )
45 # Note parens: ^ ^
46 times
47
48
49 echo
50 echo '- - testing: =${array[@]} - -'
51 times
52 declare -a bigThree=${bigOne[@]}
53 # No parentheses this time.
54 times
55
56 # Comparing the numbers shows that the second form, pointed out
57 #+ by Stephane Chazelas, is faster.
58 #
59 # As William Park explains:
60 #+ The bigTwo array assigned element by element (because of parentheses),
61 #+ whereas bigThree assigned as a single string.
62 # So, in essence, you have:
63 # bigTwo=( [0]="..." [1]="..." [2]="..." ... )
64 # bigThree=( [0]="... ... ..." )
65 #
66 # Verify this by: echo ${bigTwo[0]}
67 # echo ${bigThree[0]}
68
69
70 # I will continue to use the first form in my example descriptions
71 #+ because I think it is a better illustration of what is happening.
72
73 # The reusable portions of my examples will actual contain
74 #+ the second form where appropriate because of the speedup.
75
76 # MSZ: Sorry about that earlier oversight folks.
77
78
79 # Note:
80 # ----
81 # The "declare -a" statements in lines 32 and 44
82 #+ are not strictly necessary, since it is implicit
83 #+ in the Array=( ... ) assignment form.
84 # However, eliminating these declarations slows down
85 #+ the execution of the following sections of the script.
86 # Try it, and see.
87
88 exit 0 |
![]() | Adding a superfluous declare -a statement to an array declaration may speed up execution of subsequent operations on the array. |
Example 27-9. Copying and concatenating arrays
1 #! /bin/bash
2 # CopyArray.sh
3 #
4 # This script written by Michael Zick.
5 # Used here with permission.
6
7 # How-To "Pass by Name & Return by Name"
8 #+ or "Building your own assignment statement".
9
10
11 CpArray_Mac() {
12
13 # Assignment Command Statement Builder
14
15 echo -n 'eval '
16 echo -n "$2" # Destination name
17 echo -n '=( ${'
18 echo -n "$1" # Source name
19 echo -n '[@]} )'
20
21 # That could all be a single command.
22 # Matter of style only.
23 }
24
25 declare -f CopyArray # Function "Pointer"
26 CopyArray=CpArray_Mac # Statement Builder
27
28 Hype()
29 {
30
31 # Hype the array named $1.
32 # (Splice it together with array containing "Really Rocks".)
33 # Return in array named $2.
34
35 local -a TMP
36 local -a hype=( Really Rocks )
37
38 $($CopyArray $1 TMP)
39 TMP=( ${TMP[@]} ${hype[@]} )
40 $($CopyArray TMP $2)
41 }
42
43 declare -a before=( Advanced Bash Scripting )
44 declare -a after
45
46 echo "Array Before = ${before[@]}"
47
48 Hype before after
49
50 echo "Array After = ${after[@]}"
51
52 # Too much hype?
53
54 echo "What ${after[@]:3:2}?"
55
56 declare -a modest=( ${after[@]:2:1} ${after[@]:3:2} )
57 # ---- substring extraction ----
58
59 echo "Array Modest = ${modest[@]}"
60
61 # What happened to 'before' ?
62
63 echo "Array Before = ${before[@]}"
64
65 exit 0 |
Example 27-10. More on concatenating arrays
1 #! /bin/bash
2 # array-append.bash
3
4 # Copyright (c) Michael S. Zick, 2003, All rights reserved.
5 # License: Unrestricted reuse in any form, for any purpose.
6 # Version: $ID$
7 #
8 # Slightly modified in formatting by M.C.
9
10
11 # Array operations are Bash-specific.
12 # Legacy UNIX /bin/sh lacks equivalents.
13
14
15 # Pipe the output of this script to 'more'
16 #+ so it doesn't scroll off the terminal.
17 # Or, redirect output to a file.
18
19
20 declare -a array1=( zero1 one1 two1 )
21 # Subscript packed.
22 declare -a array2=( [0]=zero2 [2]=two2 [3]=three2 )
23 # Subscript sparse -- [1] is not defined.
24
25 echo
26 echo '- Confirm that the array is really subscript sparse. -'
27 echo "Number of elements: 4" # Hard-coded for illustration.
28 for (( i = 0 ; i < 4 ; i++ ))
29 do
30 echo "Element [$i]: ${array2[$i]}"
31 done
32 # See also the more general code example in basics-reviewed.bash.
33
34
35 declare -a dest
36
37 # Combine (append) two arrays into a third array.
38 echo
39 echo 'Conditions: Unquoted, default IFS, All-Elements-Of operator'
40 echo '- Undefined elements not present, subscripts not maintained. -'
41 # # The undefined elements do not exist; they are not being dropped.
42
43 dest=( ${array1[@]} ${array2[@]} )
44 # dest=${array1[@]}${array2[@]} # Strange results, possibly a bug.
45
46 # Now, list the result.
47 echo
48 echo '- - Testing Array Append - -'
49 cnt=${#dest[@]}
50
51 echo "Number of elements: $cnt"
52 for (( i = 0 ; i < cnt ; i++ ))
53 do
54 echo "Element [$i]: ${dest[$i]}"
55 done
56
57 # Assign an array to a single array element (twice).
58 dest[0]=${array1[@]}
59 dest[1]=${array2[@]}
60
61 # List the result.
62 echo
63 echo '- - Testing modified array - -'
64 cnt=${#dest[@]}
65
66 echo "Number of elements: $cnt"
67 for (( i = 0 ; i < cnt ; i++ ))
68 do
69 echo "Element [$i]: ${dest[$i]}"
70 done
71
72 # Examine the modified second element.
73 echo
74 echo '- - Reassign and list second element - -'
75
76 declare -a subArray=${dest[1]}
77 cnt=${#subArray[@]}
78
79 echo "Number of elements: $cnt"
80 for (( i = 0 ; i < cnt ; i++ ))
81 do
82 echo "Element [$i]: ${subArray[$i]}"
83 done
84
85 # The assignment of an entire array to a single element
86 #+ of another array using the '=${ ... }' array assignment
87 #+ has converted the array being assigned into a string,
88 #+ with the elements separated by a space (the first character of IFS).
89
90 # If the original elements didn't contain whitespace . . .
91 # If the original array isn't subscript sparse . . .
92 # Then we could get the original array structure back again.
93
94 # Restore from the modified second element.
95 echo
96 echo '- - Listing restored element - -'
97
98 declare -a subArray=( ${dest[1]} )
99 cnt=${#subArray[@]}
100
101 echo "Number of elements: $cnt"
102 for (( i = 0 ; i < cnt ; i++ ))
103 do
104 echo "Element [$i]: ${subArray[$i]}"
105 done
106 echo '- - Do not depend on this behavior. - -'
107 echo '- - This behavior is subject to change - -'
108 echo '- - in versions of Bash newer than version 2.05b - -'
109
110 # MSZ: Sorry about any earlier confusion folks.
111
112 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.
Example 27-11. The Bubble Sort
1 #!/bin/bash
2 # bubble.sh: Bubble sort, of sorts.
3
4 # Recall the algorithm for a bubble sort. In this particular version...
5
6 # With each successive pass through the array to be sorted,
7 #+ compare two adjacent elements, and swap them if out of order.
8 # At the end of the first pass, the "heaviest" element has sunk to bottom.
9 # At the end of the second pass, the next "heaviest" one has sunk next to bottom.
10 # And so forth.
11 # This means that each successive pass needs to traverse less of the array.
12 # You will therefore notice a speeding up in the printing of the later passes.
13
14
15 exchange()
16 {
17 # Swaps two members of the array.
18 local temp=${Countries[$1]} # Temporary storage
19 #+ for element getting swapped out.
20 Countries[$1]=${Countries[$2]}
21 Countries[$2]=$temp
22
23 return
24 }
25
26 declare -a Countries # Declare array,
27 #+ optional here since it's initialized below.
28
29 # Is it permissable to split an array variable over multiple lines
30 #+ using an escape (\)?
31 # Yes.
32
33 Countries=(Netherlands Ukraine Zaire Turkey Russia Yemen Syria \
34 Brazil Argentina Nicaragua Japan Mexico Venezuela Greece England \
35 Israel Peru Canada Oman Denmark Wales France Kenya \
36 Xanadu Qatar Liechtenstein Hungary)
37
38 # "Xanadu" is the mythical place where, according to Coleridge,
39 #+ Kubla Khan did a pleasure dome decree.
40
41
42 clear # Clear the screen to start with.
43
44 echo "0: ${Countries[*]}" # List entire array at pass 0.
45
46 number_of_elements=${#Countries[@]}
47 let "comparisons = $number_of_elements - 1"
48
49 count=1 # Pass number.
50
51 while [ "$comparisons" -gt 0 ] # Beginning of outer loop
52 do
53
54 index=0 # Reset index to start of array after each pass.
55
56 while [ "$index" -lt "$comparisons" ] # Beginning of inner loop
57 do
58 if [ ${Countries[$index]} \> ${Countries[`expr $index + 1`]} ]
59 # If out of order...
60 # Recalling that \> is ASCII comparison operator
61 #+ within single brackets.
62
63 # if [[ ${Countries[$index]} > ${Countries[`expr $index + 1`]} ]]
64 #+ also works.
65 then
66 exchange $index `expr $index + 1` # Swap.
67 fi
68 let "index += 1" # Or, index+=1 on Bash, ver. 3.1 or newer.
69 done # End of inner loop
70
71 # ----------------------------------------------------------------------
72 # Paulo Marcel Coelho Aragao suggests for-loops as a simpler altenative.
73 #
74 # for (( last = $number_of_elements - 1 ; last > 0 ; last-- ))
75 ## Fix by C.Y. Hunt ^ (Thanks!)
76 # do
77 # for (( i = 0 ; i < last ; i++ ))
78 # do
79 # [[ "${Countries[$i]}" > "${Countries[$((i+1))]}" ]] \
80 # && exchange $i $((i+1))
81 # done
82 # done
83 # ----------------------------------------------------------------------
84
85
86 let "comparisons -= 1" # Since "heaviest" element bubbles to bottom,
87 #+ we need do one less comparison each pass.
88
89 echo
90 echo "$count: ${Countries[@]}" # Print resultant array at end of each pass.
91 echo
92 let "count += 1" # Increment pass count.
93
94 done # End of outer loop
95 # All done.
96
97 exit 0 |
--
Is it possible to nest arrays within arrays?
1 #!/bin/bash
2 # "Nested" array.
3
4 # Michael Zick provided this example,
5 #+ with corrections and clarifications by William Park.
6
7 AnArray=( $(ls --inode --ignore-backups --almost-all \
8 --directory --full-time --color=none --time=status \
9 --sort=time -l ${PWD} ) ) # Commands and options.
10
11 # Spaces are significant . . . and don't quote anything in the above.
12
13 SubArray=( ${AnArray[@]:11:1} ${AnArray[@]:6:5} )
14 # This array has six elements:
15 #+ SubArray=( [0]=${AnArray[11]} [1]=${AnArray[6]} [2]=${AnArray[7]}
16 # [3]=${AnArray[8]} [4]=${AnArray[9]} [5]=${AnArray[10]} )
17 #
18 # Arrays in Bash are (circularly) linked lists
19 #+ of type string (char *).
20 # So, this isn't actually a nested array,
21 #+ but it's functionally similar.
22
23 echo "Current directory and date of last status change:"
24 echo "${SubArray[@]}"
25
26 exit 0 |
--
Embedded arrays in combination with indirect references create some fascinating possibilities
Example 27-12. Embedded arrays and indirect references
1 #!/bin/bash
2 # embedded-arrays.sh
3 # Embedded arrays and indirect references.
4
5 # This script by Dennis Leeuw.
6 # Used with permission.
7 # Modified by document author.
8
9
10 ARRAY1=(
11 VAR1_1=value11
12 VAR1_2=value12
13 VAR1_3=value13
14 )
15
16 ARRAY2=(
17 VARIABLE="test"
18 STRING="VAR1=value1 VAR2=value2 VAR3=value3"
19 ARRAY21=${ARRAY1[*]}
20 ) # Embed ARRAY1 within this second array.
21
22 function print () {
23 OLD_IFS="$IFS"
24 IFS=$'\n' # To print each array element
25 #+ on a separate line.
26 TEST1="ARRAY2[*]"
27 local ${!TEST1} # See what happens if you delete this line.
28 # Indirect reference.
29 # This makes the components of $TEST1
30 #+ accessible to this function.
31
32
33 # Let's see what we've got so far.
34 echo
35 echo "\$TEST1 = $TEST1" # Just the name of the variable.
36 echo; echo
37 echo "{\$TEST1} = ${!TEST1}" # Contents of the variable.
38 # That's what an indirect
39 #+ reference does.
40 echo
41 echo "-------------------------------------------"; echo
42 echo
43
44
45 # Print variable
46 echo "Variable VARIABLE: $VARIABLE"
47
48 # Print a string element
49 IFS="$OLD_IFS"
50 TEST2="STRING[*]"
51 local ${!TEST2} # Indirect reference (as above).
52 echo "String element VAR2: $VAR2 from STRING"
53
54 # Print an array element
55 TEST2="ARRAY21[*]"
56 local ${!TEST2} # Indirect reference (as above).
57 echo "Array element VAR1_1: $VAR1_1 from ARRAY21"
58 }
59
60 print
61 echo
62
63 exit 0
64
65 # As the author of the script notes,
66 #+ "you can easily expand it to create named-hashes in bash."
67 # (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.
Example 27-13. The Sieve of Eratosthenes
1 #!/bin/bash
2 # sieve.sh (ex68.sh)
3
4 # Sieve of Eratosthenes
5 # Ancient algorithm for finding prime numbers.
6
7 # This runs a couple of orders of magnitude slower
8 #+ than the equivalent program written in C.
9
10 LOWER_LIMIT=1 # Starting with 1.
11 UPPER_LIMIT=1000 # Up to 1000.
12 # (You may set this higher . . . if you have time on your hands.)
13
14 PRIME=1
15 NON_PRIME=0
16
17 let SPLIT=UPPER_LIMIT/2
18 # Optimization:
19 # Need to test numbers only halfway to upper limit. Why?
20
21
22 declare -a Primes
23 # Primes[] is an array.
24
25
26 initialize ()
27 {
28 # Initialize the array.
29
30 i=$LOWER_LIMIT
31 until [ "$i" -gt "$UPPER_LIMIT" ]
32 do
33 Primes[i]=$PRIME
34 let "i += 1"
35 done
36 # Assume all array members guilty (prime)
37 #+ until proven innocent.
38 }
39
40 print_primes ()
41 {
42 # Print out the members of the Primes[] array tagged as prime.
43
44 i=$LOWER_LIMIT
45
46 until [ "$i" -gt "$UPPER_LIMIT" ]
47 do
48
49 if [ "${Primes[i]}" -eq "$PRIME" ]
50 then
51 printf "%8d" $i
52 # 8 spaces per number gives nice, even columns.
53 fi
54
55 let "i += 1"
56
57 done
58
59 }
60
61 sift () # Sift out the non-primes.
62 {
63
64 let i=$LOWER_LIMIT+1
65 # Let's start with 2.
66
67 until [ "$i" -gt "$UPPER_LIMIT" ]
68 do
69
70 if [ "${Primes[i]}" -eq "$PRIME" ]
71 # Don't bother sieving numbers already sieved (tagged as non-prime).
72 then
73
74 t=$i
75
76 while [ "$t" -le "$UPPER_LIMIT" ]
77 do
78 let "t += $i "
79 Primes[t]=$NON_PRIME
80 # Tag as non-prime all multiples.
81 done
82
83 fi
84
85 let "i += 1"
86 done
87
88
89 }
90
91
92 # ==============================================
93 # main ()
94 # Invoke the functions sequentially.
95 initialize
96 sift
97 print_primes
98 # This is what they call structured programming.
99 # ==============================================
100
101 echo
102
103 exit 0
104
105
106
107 # -------------------------------------------------------- #
108 # Code below line will not execute, because of 'exit.'
109
110 # This improved version of the Sieve, by Stephane Chazelas,
111 #+ executes somewhat faster.
112
113 # Must invoke with command-line argument (limit of primes).
114
115 UPPER_LIMIT=$1 # From command-line.
116 let SPLIT=UPPER_LIMIT/2 # Halfway to max number.
117
118 Primes=( '' $(seq $UPPER_LIMIT) )
119
120 i=1
121 until (( ( i += 1 ) > SPLIT )) # Need check only halfway.
122 do
123 if [[ -n ${Primes[i]} ]]
124 then
125 t=$i
126 until (( ( t += i ) > UPPER_LIMIT ))
127 do
128 Primes[t]=
129 done
130 fi
131 done
132 echo ${Primes[*]}
133
134 exit $? |
Example 27-14. The Sieve of Eratosthenes, Optimized
1 #!/bin/bash
2 # Optimized Sieve of Eratosthenes
3 # Script by Jared Martin, with very minor changes by ABS Guide author.
4 # Used in ABS Guide with permission (thanks!).
5
6 # Based on script in Advanced Bash Scripting Guide.
7 # http://tldp.org/LDP/abs/html/arrays.html#PRIMES0 (ex68.sh).
8
9 # http://www.cs.hmc.edu/~oneill/papers/Sieve-JFP.pdf (reference)
10 # Check results against http://primes.utm.edu/lists/small/1000.txt
11
12 # Necessary but not sufficient would be, e.g.,
13 # (($(sieve 7919 | wc -w) == 1000)) && echo "7919 is the 1000th prime"
14
15 UPPER_LIMIT=${1:?"Need an upper limit of primes to search."}
16
17 Primes=( '' $(seq ${UPPER_LIMIT}) )
18
19 typeset -i i t
20 Primes[i=1]='' # 1 is not a prime.
21 until (( ( i += 1 ) > (${UPPER_LIMIT}/i) )) # Need check only ith-way.
22 do # Why?
23 if ((${Primes[t=i*(i-1), i]}))
24 # Obscure, but instructive, use of arithmetic expansion in subscript.
25 then
26 until (( ( t += i ) > ${UPPER_LIMIT} ))
27 do Primes[t]=; done
28 fi
29 done
30
31 # echo ${Primes[*]}
32 echo # Change to original script for pretty-printing (80-col. display).
33 printf "%8d" ${Primes[*]}
34 echo; echo
35
36 exit $? |
Compare these array-based prime number generators with alternatives that do not use arrays, Example A-15, and Example 16-46.
--
Arrays lend themselves, to some extent, to emulating data structures for which Bash has no native support.
Example 27-15. Emulating a push-down stack
1 #!/bin/bash
2 # stack.sh: push-down stack simulation
3
4 # Similar to the CPU stack, a push-down stack stores data items
5 #+ sequentially, but releases them in reverse order, last-in first-out.
6
7
8 BP=100 # Base Pointer of stack array.
9 # Begin at element 100.
10
11 SP=$BP # Stack Pointer.
12 # Initialize it to "base" (bottom) of stack.
13
14 Data= # Contents of stack location.
15 # Must use global variable,
16 #+ because of limitation on function return range.
17
18
19 # 100 Base pointer <-- Base Pointer
20 # 99 First data item
21 # 98 Second data item
22 # ... More data
23 # Last data item <-- Stack pointer
24
25
26 declare -a stack
27
28
29 push() # Push item on stack.
30 {
31 if [ -z "$1" ] # Nothing to push?
32 then
33 return
34 fi
35
36 let "SP -= 1" # Bump stack pointer.
37 stack[$SP]=$1
38
39 return
40 }
41
42 pop() # Pop item off stack.
43 {
44 Data= # Empty out data item.
45
46 if [ "$SP" -eq "$BP" ] # Stack empty?
47 then
48 return
49 fi # This also keeps SP from getting past 100,
50 #+ i.e., prevents a runaway stack.
51
52 Data=${stack[$SP]}
53 let "SP += 1" # Bump stack pointer.
54 return
55 }
56
57 status_report() # Find out what's happening.
58 {
59 echo "-------------------------------------"
60 echo "REPORT"
61 echo "Stack Pointer = $SP"
62 echo "Just popped \""$Data"\" off the stack."
63 echo "-------------------------------------"
64 echo
65 }
66
67
68 # =======================================================
69 # Now, for some fun.
70
71 echo
72
73 # See if you can pop anything off empty stack.
74 pop
75 status_report
76
77 echo
78
79 push garbage
80 pop
81 status_report # Garbage in, garbage out.
82
83 value1=23; push $value1
84 value2=skidoo; push $value2
85 value3=LAST; push $value3
86
87 pop # LAST
88 status_report
89 pop # skidoo
90 status_report
91 pop # 23
92 status_report # Last-in, first-out!
93
94 # Notice how the stack pointer decrements with each push,
95 #+ and increments with each pop.
96
97 echo
98
99 exit 0
100
101 # =======================================================
102
103
104 # Exercises:
105 # ---------
106
107 # 1) Modify the "push()" function to permit pushing
108 # + multiple element on the stack with a single function call.
109
110 # 2) Modify the "pop()" function to permit popping
111 # + multiple element from the stack with a single function call.
112
113 # 3) Add error checking to the critical functions.
114 # That is, return an error code, depending on
115 # + successful or unsuccessful completion of the operation,
116 # + and take appropriate action.
117
118 # 4) Using this script as a starting point,
119 # + 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.
Example 27-16. Complex array application: Exploring a weird mathematical series
1 #!/bin/bash
2
3 # Douglas Hofstadter's notorious "Q-series":
4
5 # Q(1) = Q(2) = 1
6 # Q(n) = Q(n - Q(n-1)) + Q(n - Q(n-2)), for n>2
7
8 # This is a "chaotic" integer series with strange
9 #+ and unpredictable behavior.
10 # The first 20 terms of the series are:
11 # 1 1 2 3 3 4 5 5 6 6 6 8 8 8 10 9 10 11 11 12
12
13 # See Hofstadter's book, _Goedel, Escher, Bach: An Eternal Golden Braid_,
14 #+ p. 137, ff.
15
16
17 LIMIT=100 # Number of terms to calculate.
18 LINEWIDTH=20 # Number of terms printed per line.
19
20 Q[1]=1 # First two terms of series are 1.
21 Q[2]=1
22
23 echo
24 echo "Q-series [$LIMIT terms]:"
25 echo -n "${Q[1]} " # Output first two terms.
26 echo -n "${Q[2]} "
27
28 for ((n=3; n <= $LIMIT; n++)) # C-like loop expression.
29 do # Q[n] = Q[n - Q[n-1]] + Q[n - Q[n-2]] for n>2
30 # Need to break the expression into intermediate terms,
31 #+ since Bash doesn't handle complex array arithmetic very well.
32
33 let "n1 = $n - 1" # n-1
34 let "n2 = $n - 2" # n-2
35
36 t0=`expr $n - ${Q[n1]}` # n - Q[n-1]
37 t1=`expr $n - ${Q[n2]}` # n - Q[n-2]
38
39 T0=${Q[t0]} # Q[n - Q[n-1]]
40 T1=${Q[t1]} # Q[n - Q[n-2]]
41
42 Q[n]=`expr $T0 + $T1` # Q[n - Q[n-1]] + Q[n - Q[n-2]]
43 echo -n "${Q[n]} "
44
45 if [ `expr $n % $LINEWIDTH` -eq 0 ] # Format output.
46 then # ^ modulo
47 echo # Break lines into neat chunks.
48 fi
49
50 done
51
52 echo
53
54 exit 0
55
56 # This is an iterative implementation of the Q-series.
57 # The more intuitive recursive implementation is left as an exercise.
58 # Warning: calculating this series recursively takes a VERY long time
59 #+ 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.
Example 27-17. Simulating a two-dimensional array, then tilting it
1 #!/bin/bash
2 # twodim.sh: Simulating a two-dimensional array.
3
4 # A one-dimensional array consists of a single row.
5 # A two-dimensional array stores rows sequentially.
6
7 Rows=5
8 Columns=5
9 # 5 X 5 Array.
10
11 declare -a alpha # char alpha [Rows] [Columns];
12 # Unnecessary declaration. Why?
13
14 load_alpha ()
15 {
16 local rc=0
17 local index
18
19 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
20 do # Use different symbols if you like.
21 local row=`expr $rc / $Columns`
22 local column=`expr $rc % $Rows`
23 let "index = $row * $Rows + $column"
24 alpha[$index]=$i
25 # alpha[$row][$column]
26 let "rc += 1"
27 done
28
29 # Simpler would be
30 #+ 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 )
31 #+ but this somehow lacks the "flavor" of a two-dimensional array.
32 }
33
34 print_alpha ()
35 {
36 local row=0
37 local index
38
39 echo
40
41 while [ "$row" -lt "$Rows" ] # Print out in "row major" order:
42 do #+ columns vary,
43 #+ while row (outer loop) remains the same.
44 local column=0
45
46 echo -n " " # Lines up "square" array with rotated one.
47
48 while [ "$column" -lt "$Columns" ]
49 do
50 let "index = $row * $Rows + $column"
51 echo -n "${alpha[index]} " # alpha[$row][$column]
52 let "column += 1"
53 done
54
55 let "row += 1"
56 echo
57
58 done
59
60 # The simpler equivalent is
61 # echo ${alpha[*]} | xargs -n $Columns
62
63 echo
64 }
65
66 filter () # Filter out negative array indices.
67 {
68
69 echo -n " " # Provides the tilt.
70 # Explain how.
71
72 if [[ "$1" -ge 0 && "$1" -lt "$Rows" && "$2" -ge 0 && "$2" -lt "$Columns" ]]
73 then
74 let "index = $1 * $Rows + $2"
75 # Now, print it rotated.
76 echo -n " ${alpha[index]}"
77 # alpha[$row][$column]
78 fi
79
80 }
81
82
83
84
85 rotate () # Rotate the array 45 degrees --
86 { #+ "balance" it on its lower lefthand corner.
87 local row
88 local column
89
90 for (( row = Rows; row > -Rows; row-- ))
91 do # Step through the array backwards. Why?
92
93 for (( column = 0; column < Columns; column++ ))
94 do
95
96 if [ "$row" -ge 0 ]
97 then
98 let "t1 = $column - $row"
99 let "t2 = $column"
100 else
101 let "t1 = $column"
102 let "t2 = $column + $row"
103 fi
104
105 filter $t1 $t2 # Filter out negative array indices.
106 # What happens if you don't do this?
107 done
108
109 echo; echo
110
111 done
112
113 # Array rotation inspired by examples (pp. 143-146) in
114 #+ "Advanced C Programming on the IBM PC," by Herbert Mayer
115 #+ (see bibliography).
116 # This just goes to show that much of what can be done in C
117 #+ can also be done in shell scripting.
118
119 }
120
121
122 #--------------- Now, let the show begin. ------------#
123 load_alpha # Load the array.
124 print_alpha # Print it out.
125 rotate # Rotate it 45 degrees counterclockwise.
126 #-----------------------------------------------------#
127
128 exit 0
129
130 # This is a rather contrived, not to mention inelegant simulation.
131
132 # Exercises:
133 # ---------
134 # 1) Rewrite the array loading and printing functions
135 # in a more intuitive and less kludgy fashion.
136 #
137 # 2) Figure out how the array rotation functions work.
138 # Hint: think about the implications of backwards-indexing an array.
139 #
140 # 3) Rewrite this script to handle a non-square array,
141 # such as a 6 X 4 one.
142 # 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 Example A-10.
--
For more interesting scripts using arrays, see: