| Advanced Bash-Scripting Guide: A complete guide to shell scripting, using Bash | ||
|---|---|---|
| Prev | Chapter 3. Tutorial / Reference | Next |
3.24. Arrays
Newer versions of bash support one-dimensional arrays. Arrays may be declared with the variable[xx] notation or explicitly by a declare -a variable statement. To dereference (find the contents of) an array variable, use curly bracket notation, that is, ${variable[xx]}.
Example 3-149. Simple array usage
1 #!/bin/bash
2
3
4 area[11]=23
5 area[13]=37
6 area[51]=UFOs
7
8 # Note that array members need not be consecutive
9 # or contiguous.
10
11 # Some members of the array can be left uninitialized.
12 # Gaps in the array are o.k.
13
14
15 echo -n "area[11] = "
16 echo ${area[11]}
17 echo -n "area[13] = "
18 echo ${area[13]}
19 # Note that {curly brackets} needed
20 echo "Contents of area[51] are ${area[51]}."
21
22 # Contents of uninitialized array variable print blank.
23 echo -n "area[43] = "
24 echo ${area[43]}
25 echo "(area[43] unassigned)"
26
27 echo
28
29 # Sum of two array variables assigned to third
30 area[5]=`expr ${area[11]} + ${area[13]}`
31 echo "area[5] = area[11] + area[13]"
32 echo -n "area[5] = "
33 echo ${area[5]}
34
35 area[6]=`expr ${area[11]} + ${area[51]}`
36 echo "area[6] = area[11] + area[51]"
37 echo -n "area[6] = "
38 echo ${area[6]}
39 # This doesn't work because
40 # adding an integer to a string is not permitted.
41
42 echo
43 echo
44 echo
45
46 # -----------------------------------------------------------------
47 # Another array, "area2".
48 # Another way of assigning array variables...
49 # array_name=( XXX YYY ZZZ ... )
50
51 area2=( zero one two three four )
52
53 echo -n "area2[0] = "
54 echo ${area2[0]}
55 # Aha, zero-based indexing (first element of array is [0], not [1]).
56
57 echo -n "area2[1] = "
58 echo ${area2[1]} # [1] is second element of array.
59 # -----------------------------------------------------------------
60
61
62 echo
63 echo
64 echo
65
66 # -----------------------------------------------
67 # Yet another array, "area3".
68 # Yet another way of assigning array variables...
69 # array_name=([xx]=XXX [yy]=YYY ...)
70
71 area3=([17]=seventeen [24]=twenty-four)
72
73 echo -n "area3[17] = "
74 echo ${area3[17]}
75
76 echo -n "area3[24] = "
77 echo ${area3[24]}
78 # -----------------------------------------------
79
80
81 exit 0 |
Arrays variables have a syntax all their own, and even standard Bash operators have special options adapted for array use.
Example 3-150. Some special properties of arrays
1 #!/bin/bash
2
3 declare -a colors
4 # Permits declaring an array without specifying size.
5
6 echo "Enter your favorite colors (separated from each other by a space)."
7
8 read -a colors
9 # Special option to 'read' command,
10 # allowing it to assign elements in an array.
11
12 echo
13
14 element_count=${#colors[@]} # Special syntax to extract number of elements in array.
15 # element_count=${#colors[*]} works also.
16 #
17 # The "@" variable allows word splitting within quotes
18 # (extracts variables separated by whitespace).
19 index=0
20
21 # List all the elements in the array.
22 while [ "$index" -lt "$element_count" ]
23 do
24 echo ${colors[$index]}
25 let "index = $index + 1"
26 done
27 # Each array element listed on a separate line.
28 # If this is not desired, use echo -n "${colors[$index]} "
29 #
30 # Doing it with a "for" loop instead:
31 # for i in "${colors[@]}"
32 # do echo "$i"
33 # done
34 # (Thanks, S.C.)
35
36 echo
37
38 # Again, list all the elements in the array, but using a more elegant method.
39 echo ${colors[@]}
40 # echo ${colors[*]} works also.
41
42
43 echo
44
45 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 3-151. Of empty arrays and empty elements
1 #!/bin/bash
2 # empty-array.sh
3
4 # An empty array is not the same as an array with empty elements.
5
6 array0=( first second third )
7 array1=( '' ) # "array1" has one empty element.
8 array2=( ) # No elements... "array2" is empty.
9
10 echo
11
12 echo "Elements in array0: ${array0[@]}"
13 echo "Elements in array1: ${array1[@]}"
14 echo "Elements in array2: ${array2[@]}"
15 echo
16 echo "Length of first element in array0 = ${#array0}"
17 echo "Length of first element in array1 = ${#array1}"
18 echo "Length of first element in array2 = ${#array2}"
19 echo
20 echo "Number of elements in array0 = ${#array0[*]}" # 3
21 echo "Number of elements in array1 = ${#array1[*]}" # 1 (surprise!)
22 echo "Number of elements in array2 = ${#array2[*]}" # 0
23
24 echo
25
26 # Thanks, S.C.
27
28 exit 0 |
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
4 # Adding an element to an array.
5 array=( "${array[@]}" "new element" )
6 # or
7 array[${#array[*]}]="new element"
8
9 # Thanks, S.C. |
--
Arrays permit deploying old familiar algorithms as shell scripts. Whether this is necessarily a good idea is left to the reader to decide.
Example 3-152. An old friend: The Bubble Sort
1 #!/bin/bash
2
3 # Bubble sort, of sorts.
4
5 # Recall the algorithm for a bubble sort. In this particular version...
6
7 # With each successive pass through the array to be sorted,
8 # compare two adjacent elements, and swap them if out of order.
9 # At the end of the first pass, the "heaviest" element has sunk to bottom.
10 # At the end of the second pass, the next "heaviest" one has sunk next to bottom.
11 # And so forth.
12 # This means that each successive pass needs to traverse less of the array.
13 # You will therefore notice a speeding up in the printing of the later passes.
14
15
16 exchange()
17 {
18 # Swaps two members of the array.
19 local temp=${Countries[$1]} # Temporary storage 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, optional here since it's initialized below.
27
28 Countries=(Netherlands Ukraine Zaire Turkey Russia Yemen Syria Brazil Argentina Nicaragua Japan Mexico Venezuela Greece England Israel Peru Canada Oman Denmark Wales France Kenya Qatar Liechtenstein Hungary)
29 # Couldn't think of one starting with X (darn).
30
31 clear # Clear the screen to start with.
32
33 echo "0: ${Countries[*]}" # List entire array at pass 0.
34
35 number_of_elements=${#Countries[@]}
36 let "comparisons = $number_of_elements - 1"
37
38 count=1 # Pass number.
39
40 while [ $comparisons -gt 0 ] # Beginning of outer loop
41 do
42
43 index=0 # Reset index to start of array after each pass.
44
45 while [ $index -lt $comparisons ] # Beginning of inner loop
46 do
47 if [ ${Countries[$index]} \> ${Countries[`expr $index + 1`]} ]
48 # If out of order...
49 # Recalling that \> is ASCII comparison operator.
50 then
51 exchange $index `expr $index + 1` # Swap.
52 fi
53 let "index += 1"
54 done # End of inner loop
55
56
57 let "comparisons -= 1"
58 # Since "heaviest" element bubbles to bottom, we need do one less comparison each pass.
59
60 echo
61 echo "$count: ${Countries[@]}"
62 # Print resultant array at end of each pass.
63 echo
64 let "count += 1" # Increment pass count.
65
66 done # End of outer loop
67
68 # All done.
69
70 exit 0 |
--
Arrays enable implementing a shell script version of the Sieve of Erastosthenes. 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 3-153. Complex array application: Sieve of Erastosthenes
1 #!/bin/bash
2
3 # sieve.sh
4 # Sieve of Erastosthenes
5 # Ancient algorithm for finding prime numbers.
6
7 # This runs a couple of orders of magnitude
8 # slower than equivalent C program.
9
10 LOWER_LIMIT=1
11 # Starting with 1.
12 UPPER_LIMIT=1000
13 # Up to 1000.
14 # (You may set this higher...
15 # if you have time on your hands.)
16
17 PRIME=1
18 NON_PRIME=0
19
20 let SPLIT=UPPER_LIMIT/2
21 # Optimization:
22 # Need to test numbers only
23 # halfway to upper limit.
24
25
26 declare -a Primes
27 # Primes[] is an array.
28
29
30 initialize ()
31 {
32 # Initialize the array.
33
34 i=$LOWER_LIMIT
35 until [ "$i" -gt "$UPPER_LIMIT" ]
36 do
37 Primes[i]=$PRIME
38 let "i += 1"
39 done
40 # Assume all array members guilty (prime)
41 # until proven innocent.
42 }
43
44 print_primes ()
45 {
46 # Print out the members of the Primes[] array
47 # tagged as prime.
48
49 i=$LOWER_LIMIT
50
51 until [ "$i" -gt "$UPPER_LIMIT" ]
52 do
53
54 if [ "${Primes[i]}" -eq "$PRIME" ]
55 then
56 printf "%8d" $i
57 # 8 spaces per number
58 # gives nice, even columns.
59 fi
60
61 let "i += 1"
62
63 done
64
65 }
66
67 sift ()
68 {
69 # Sift out the non-primes.
70
71 let i=$LOWER_LIMIT+1
72 # We know 1 is prime, so
73 # let's start with 2.
74
75 until [ "$i" -gt "$UPPER_LIMIT" ]
76 do
77
78 if [ "${Primes[i]}" -eq "$PRIME" ]
79 # Don't bother sieving numbers
80 # already sieved (tagged as non-prime).
81 then
82
83 t=$i
84
85 while [ "$t" -le "$UPPER_LIMIT" ]
86 do
87 let "t += $i "
88 Primes[t]=$NON_PRIME
89 # Tag as non-prime
90 # all multiples.
91 done
92
93 fi
94
95 let "i += 1"
96 done
97
98
99 }
100
101
102 # Invoke the functions sequentially.
103 initialize
104 sift
105 print_primes
106 echo
107 # This is what they call structured programming.
108
109 exit 0
110
111
112
113 # ----------------------------------------------- #
114 # Code below line will not execute.
115
116 # This improved version of the Sieve, by Stephane Chazelas,
117 # executes somewhat faster.
118
119 # Must invoke with command-line argument (limit of primes).
120
121 UPPER_LIMIT=$1 # From command line.
122 let SPLIT=UPPER_LIMIT/2 # Halfway to max number.
123
124 Primes=( '' $(seq $UPPER_LIMIT) )
125
126 i=1
127 until (( ( i += 1 ) > SPLIT )) # Need check only halfway.
128 do
129 if [[ -n $Primes[i] ]]
130 then
131 t=$i
132 until (( ( t += i ) > UPPER_LIMIT ))
133 do
134 Primes[t]=
135 done
136 fi
137 done
138 echo ${Primes[*]} |
Compare this array-based prime number generator with with an alternative that does not use arrays, Example A-10.
--
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 3-154. 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 and unpredictable behavior.
9 # The first 20 terms of the series are:
10 # 1 1 2 3 3 4 5 5 6 6 6 8 8 8 10 9 10 11 11 12
11
12 # See Hofstadter's book, "Goedel, Escher, Bach: An Eternal Golden Braid", p. 137, ff.
13
14
15 LIMIT=100 # Number of terms to calculate
16 LINEWIDTH=20 # Number of terms printed per line
17
18 Q[1]=1 # First two terms of series are 1.
19 Q[2]=1
20
21 echo
22 echo "Q-series [$LIMIT terms]:"
23 echo -n "${Q[1]} " # Output first two terms.
24 echo -n "${Q[2]} "
25
26 for ((n=3; n <= $LIMIT; n++)) # C-like loop conditions.
27 do # Q[n] = Q[n - Q[n-1]] + Q[n - Q[n-2]] for n>2
28 # Need to break the expression into intermediate terms,
29 # since Bash doesn't handle complex array arithmetic very well.
30
31 let "n1 = $n - 1" # n-1
32 let "n2 = $n - 2" # n-2
33
34 t0=`expr $n - ${Q[n1]}` # n - Q[n-1]
35 t1=`expr $n - ${Q[n2]}` # n - Q[n-2]
36
37 T0=${Q[t0]} # Q[n - Q[n-1]]
38 T1=${Q[t1]} # Q[n - Q[n-2]]
39
40 Q[n]=`expr $T0 + $T1` # Q[n - Q[n-1]] + Q[n - ![n-2]]
41 echo -n "${Q[n]} "
42
43 if [ `expr $n % $LINEWIDTH` -eq 0 ] # Format output.
44 then # mod
45 echo # Break lines into neat chunks.
46 fi
47
48 done
49
50 echo
51
52 exit 0
53
54 # This is an iterative implementation of the Q-series.
55 # The more intuitive recursive implementation is left as an exercise for the reader.
56 # Warning: calculating this series recursively takes a *very* long time. |
--
Bash supports only one-dimensional arrays, however a little trickery permits simulating multi-dimensional ones.
Example 3-155. Simulating a two-dimensional array, then tilting it
1 #!/bin/bash
2 # Simulating a two-dimensional array.
3
4 # A two-dimensional array stores rows sequentially.
5
6 Rows=5
7 Columns=5
8
9 declare -a alpha # char alpha [Rows] [Columns];
10 # Unnecessary declaration.
11
12 load_alpha ()
13 {
14 local rc=0
15 local index
16
17
18 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
19 do
20 local row=`expr $rc / $Columns`
21 local column=`expr $rc % $Rows`
22 let "index = $row * $Rows + $column"
23 alpha[$index]=$i # alpha[$row][$column]
24 let "rc += 1"
25 done
26
27 # Simpler would be
28 # 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 )
29 # but this somehow lacks the "flavor" of a two-dimensional array.
30 }
31
32 print_alpha ()
33 {
34 local row=0
35 local index
36
37 echo
38
39 while [ "$row" -lt "$Rows" ] # Print out in "row major" order -
40 do # columns vary while row (outer loop) remains the same.
41
42 local column=0
43
44 while [ "$column" -lt "$Columns" ]
45 do
46 let "index = $row * $Rows + $column"
47 echo -n "${alpha[index]} " # alpha[$row][$column]
48 let "column += 1"
49 done
50
51 let "row += 1"
52 echo
53
54 done
55
56 # The simpler equivalent is
57 # echo ${alpha[*]} | xargs -n $Columns
58
59 echo
60 }
61
62 filter () # Filter out negative array indices.
63 {
64
65 echo -n " " # Provides the tilt.
66
67 if [[ "$1" -ge 0 && "$1" -lt "$Rows" && "$2" -ge 0 && "$2" -lt "$Columns" ]]
68 then
69 let "index = $1 * $Rows + $2"
70 # Now, print it rotated.
71 echo -n " ${alpha[index]}" # alpha[$row][$column]
72 fi
73
74 }
75
76
77
78
79 rotate () # Rotate the array 45 degrees ("balance" it on its lower lefthand corner).
80 {
81 local row
82 local column
83
84 for (( row = Rows; row > -Rows; row-- )) # Step through the array backwards.
85 do
86
87 for (( column = 0; column < Columns; column++ ))
88 do
89
90 if [ "$row" -ge 0 ]
91 then
92 let "t1 = $column - $row"
93 let "t2 = $column"
94 else
95 let "t1 = $column"
96 let "t2 = $column + $row"
97 fi
98
99 filter $t1 $t2 # Filter out negative array indices.
100 done
101
102 echo; echo
103
104 done
105
106 # Array rotation inspired by examples (pp. 143-146) in
107 # "Advanced C Programming on the IBM PC", by Herbert Mayer
108 # (see bibliography).
109
110 }
111
112
113 #-----------------------------------------------------#
114 load_alpha # Load the array.
115 print_alpha # Print it out.
116 rotate # Rotate it 45 degrees counterclockwise.
117 #-----------------------------------------------------#
118
119
120 # This is a rather contrived, not to mention kludgy simulation.
121 #
122 # Exercise #1 for the reader:
123 # Rewrite the array loading and printing functions
124 # in a more intuitive and elegant fashion.
125 #
126 # Exercise #2:
127 # Figure out how the array rotation functions work.
128 # Hint: think about the implications of backwards-indexing an array.
129
130 exit 0 |
