第二十六章 数组
较新的Bash版本支持一维数组。数组元素可以用符号variable[xx]来初始化。另外,脚本可以用declare -a variable 语句来清楚地指定一个数组。要访问一个数组元素,可以使用花括号来访问,即${variable[xx]}。
Example 26-1 简单的数组用法
#!/bin/bash
#
area[11]=23
area[13]=37
area[51]=UFOs
#数组成员不必一定要连贯或连续的
#数组的一部分成员允许不被初始化
#数组中空缺元素是允许的
#实际上,保存着稀疏数据的数组("稀疏数组")在电子表格处理软件中非常有用。
echo -n "area[11] = "
echo ${area[11]}
echo -n "area[13] = "
echo ${area[13]}
echo "Contents of area[51] are ${area[51]}"
#没有初始化内容的数组元素打印空值(NULL 值)
echo -n "area[43] = "
echo ${area[43]}
echo "(area[43] unassigned)"
echo
#两个数组元素的和被赋值给另一个数组元素
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]}
#这里会失败是因为整数和字符串相加是不允许的.
echo;echo;echo
# -----------------------------------------------
#另一个数组,"area2"
area2=(zero one two three four)
echo -n "area2[0] = "
echo ${area2[0]}
echo -n "area2[1] = "
echo ${area2[1]}
echo;echo;echo
# -----------------------------------------------
#第三种数组,"area2"
area3=([17]=seventeen [24]=twenty-four)
echo -n "area3[17] = "
echo ${area3[17]}
echo -n "area3[24] = "
echo ${area3[24]}
exit 0
注意:Bash允许把变量当成数组来操作,即使这个变量没有明确的被声明为数组
string=abcABC123ABCabc
echo ${string[@]}
echo ${string[*]}
echo ${string[0]}
echo ${string[1]} #无输出,因为数组中只有一个元素,且只是字符串本身
echo ${#string[@]}
Example 26-2 格式化一首诗
#!/bin/bash
#
#诗的行数
Line[1]="天门中断楚江开,"
Line[2]="碧水东流至此回。"
Line[3]="两岸青山相对出,"
Line[4]="孤帆一片日边来。"
#出处
Author[1]="《望天门山》"
Author[2]=" --李白"
Author[3]=" --唐代"
echo
for index in 1 2 3
do
printf " %s\n" "${Author[index]}"
done
for index in 1 2 3 4
do
printf " %s\n" "${Line[index]}"
done
echo
exit 0
数组元素有它们独有的语法,并且甚至Bash命令和操作符有特殊的选项可以支持数组使用
Example 26-3 多数组操作
#!/bin/bash
#
array=(zero one two three four five)
echo ${array[0]} #zero
echo ${array:0} #zero
#对数组的第一个元素"zero"进行字符串截取0位(从左到右)
echo ${array:1} #ero
#对数组的第一个元素"zero"进行字符串截取1位(从左到右)
echo "-----------------------"
echo ${#array[0]} #4
#数组第一个元素"zero"的长度
echo ${#array} #4
#数组第一个元素"zero"的长度(另一种写法)
echo ${#array[1]} #3
#数组第二个元素"one"的长度
echo ${#array[*]} #6
#数组元素的个数
echo ${#array[@]} #6
#数组元素的个数
echo "-----------------------"
array2=([0]="first element" [1]="second element" [3]="fourth element")
echo ${array2[0]} # 第一个元素
echo ${array2[1]} # 第二个元素
echo ${array2[2]} # 因为初始化时没有指定,因此值为空(null).
echo ${array2[3]} # 第四个元素
exit 0
大部分标准的字符串操作符可以用于数组操作
Example 26-4 用于数组的字符串操作符
#!/bin/bash
#
arrayZ=(one two three four five five)
echo
echo ${arrayZ[@]:0} #one two three four five five
echo ${arrayZ[@]:1} #two three four five five
echo ${arrayZ[@]:1:2} #two three
echo "----------------------"
echo ${arrayZ[@]#f*r} #one two three five five
echo ${arrayZ[@]##t*e} #one two four five five
echo ${arrayZ[@]%h*e} #one two t four five five
echo ${arrayZ[@]%%t*e} #one two four five five
echo "----------------------"
echo ${arrayZ[@]/fiv/XYZ} #one two three four XYZe XYZe
echo ${arrayZ[@]//iv/YY} #one two three four fYYe fYYe
echo ${arrayZ[@]//fi/} #one two three four ve ve
echo ${arrayZ[@]/#fi/XY} #one two three four XYve XYve
echo ${arrayZ[@]/%ve/ZZ} #one two three four fiZZ fiZZ
echo ${arrayZ[@]/%o/XX} #one twXX three four five five
#为什么"one"没有替换为"XXne"?
echo "----------------------"
newstr(){
echo -n "!!!"
}
echo ${arrayZ[@]/%e/$(newstr)} #on!!! two thre!!! four fiv!!! fiv!!!
echo ${arrayZ[@]//*/$(newstr optional_arguments)} #!!! !!! !!! !!! !!! !!!
echo
exit 0
命令替换能创建数组的新的单个元素
Example 26-5 将脚本的内容传进数组
#!/bin/bash
#
script_contents=($(cat "$0"))
for element in $(seq 0 $((${#script_contents[@]} - 1)))
do
echo -n "${script_contents[$element]}"
echo -n "--"
done
echo
exit 0
在数组的环境里,一些Bash内建命令含义有一些轻微的改变。例如:unset会删除数组元素,或者甚至删除整个数组
Example 26-6 一些数组专用的工具
#!/bin/bash
#
declare -a colors
#所有脚本后面的命令都会把变量"colors"作为数组对待
echo "输入您最喜欢的颜色(用空格隔开)."
read -a colors #键入至少 3 种颜色以用于下面的示例.
echo
element_count=${#colors[@]}
index=0
while [ "$index" -lt "$element_count" ]
do
echo ${colors[$index]}
let "index = $index + 1"
done
echo
#再次列出数组中所有的元素
echo ${colors[@]}
echo
#"unset"命令删除一个数组元素或是整个数组.
unset colors[1] #删除数组的第二个元素 :作用等同于colors[1]=
echo ${colors[@]}
unset colors #删除整个数组.
#
#unset colors[*] 或 unset colors[@] 都可以
echo;echo -n "Colors gone."
echo ${colors[@]}
exit 0
正如在前面的例子中看到的,${array_name[@]}和${array_name[*]}都与数组的所有元素相关。同样的,为了计算数组的元素个数,可以用${#array_name[@]}或${#array_name[*]}。
${#array_name}是数组第一个元素${array_name[0]}的长度(字符数)。
Example 26-7 关于数组和空数组元素
#!/bin/bash
#
array0=(first second third)
array1=("")
array2=()
echo
ListArray()
{
echo
echo "Elements in array0: ${array0[@]}" #first second third
echo "Elements in array1: ${array1[@]}" #空
echo "Elements in array2: ${array2[@]}" #空
echo
echo "Length of first element in array0 = ${#array0}" #5
echo "Length of first element in array1 = ${#array1}" #0
echo "Length of first element in array2 = ${#array2}" #0
echo
echo "Number of elements in array0 = ${#array0[*]}" #3
echo "Number of elements in array1 = ${#array1[*]}" #1
echo "Number of elements in array2 = ${#array2[*]}" #0
}
ListArray
#增加一个元素到数组
array0=("${array0[@]}" "new1")
array1=("${array1[@]}" "new1")
array2=("${array2[@]}" "new1")
ListArray
#或
array0[${#array0[*]}]="new2"
array1[${#array1[*]}]="new2"
array2[${#array2[*]}]="new2"
ListArray
#当像上面的做法增加数组时,数组像"栈",上面的做法是"push(压栈)"
#栈高是:
height=${#array2[@]}
echo
echo "array2的堆栈高度 = $height"
#"pop(出栈)"是:
unset array2[${#array2[@]}-1]
helght=${#array2[@]}
echo
echo "POP"
echo "array2的新堆栈高度 = $helght"
ListArray
#只列出数组array0的第二和第三个元素
from=1
to=2
array3=(${array0[@]:1:2})
echo
echo "Elements in array3: ${array3[@]}"
#像一个字符串一样处理(字符的数组)
#替换:
array4=(${array0[@]/second/2nd})
echo
echo "Elements in array4: ${array4[@]}"
#替换所有匹配通配符的字符串
array5=(${array0[@]//new?/old})
echo
echo "Elements in array5: ${array5[@]}"
#当你开始觉得对此有把握的时候
array6=(${array0[@]#*new})
echo
echo "Elements in array6: ${array6[@]}"
array7=(${array0[@]#new1})
echo
echo "Elements in array7: ${array7[@]}"
#这看起来非常像
array8=(${array0[@]/new1/})
echo
echo "Elements in attay8: ${array8[@]}"
zap='new*'
array9=(${array0[@]/$zap/})
echo
echo "Elements in array9: ${array9[@]}"
array10=(${array0[@]#$azp})
echo
echo "Elements in array10: ${array10[@]}"
#把 array7 和 array10 比较.
# 把 array8 和 array9 比较.
#答案: 必须用弱引用.
exit 0
${array_name[@]}和${array_name[*]}的关系类似于 “$@” 和 “$*” 。这种数组用法非常有用
#复制一个数组
array2=("${array1[@]}")
#或
array2="${array1[@]}"
#给数组增加一个元素
array=("${array[@]}" "new element")
#或
array[${#array[*]}]="new element"
注意:array=(element1 element2 … elementN)初始化操作依赖于命令替换(command substitution)使将一个文本内容加载进数组成为可能。
#!/bin/bash
#
filename=sample_file
#cat sample_file
#1 a b c
#2 d e fg
declare -a array1
array1=(`cat "$filename"`)
#array1=(`cat "$filename" | tr '\n' ''`)
#把文件里的换行变为空格. :这是没必要的,因为 Bash 做单词分割时会把换行变为空格.
echo ${array1[@]} #1 a b c 2 d e fg
element_count=${#array1[*]}
echo $element_count #8
Example 26-8 初始化数组
#!/bin/bash
#array-assign.bash
#数组操作是Bash特有的,因此脚本名用".bash"结尾。
declare -a bigOne=(/dev/*)
echo
echo "条件:未加引号、默认IFS、所有元素"
echo "One数组中的元素数为 ${#bigOne[@]}" #153
echo
echo '- - testing: =(${array[@]}) - -'
times
declare -a bigTwo=(${bigOne[@]})
echo "Two数组中的元素数为 ${#bigTwo[@]}" #153
times
echo
echo '- - testing: =${array[@]}'
times
declare -a bigThree=${bigOne[@]} #这次没有括号
echo "Three数组中的元素数为 ${#bigThree[@]}" #1
times
#第二种格式的赋值比第三和第四的 times 的更快
exit 0
注意:对变量增加"declare -a"语句声明可以加速后面的数组操作速度
Example 26-9 复制和连接数组(有点绕,勉强看懂)
#!/bin/bash
#
CpArray_Mac()
{
echo -n 'eval '
echo -n "$2" #目的变量名
echo -n '=(${'
echo -n "$1" #源名字
echo -n '[@]})'
}
declare -f CopyArray
CopyArray=CpArray_Mac
Hype()
{
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[@]}"
echo "What ${after[@]:3:2}?"
declare -a modest=(${after[@]:2:1} ${after[@]:3:2})
echo "Array Modest = ${modest[@]}"
echo "Array Before = ${before[@]}"
exit 0
Example 26-10 关于连接数组的更多信息
#!/bin/bash
#
declare -a array1=(zero1 one1 two1)
declare -a array2=([0]=zero2 [2]=two2 [3]=three2)
echo
echo "确认数组确实是下标稀疏的"
echo "Number of elements: 4"
for ((i=0;i<4;i++))
do
echo "Element [$i]: ${array2[$i]}"
done
declare -a dest
echo
echo "条件:无引号,默认IFS,运算符的所有元素"
echo "不存在未定义的元素,未维护下标"
dest=(${array1[@]} ${array2[@]})
echo
echo "- - Testing Array Append - -"
cnt=${#dest[@]}
echo "Number of elements: $cnt" #6
for ((i=0;i<cnt;i++))
do
echo "Element[$i]: ${dest[$i]}"
done
#把一个数组赋值给另一个数组的单个元素(两次)
dest[0]=${array1[@]}
dest[1]=${array2[@]}
#列出结果 :#奇怪的结果
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
#检测第二个元素的改变
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
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 "不要依赖这种行为"
echo "这种行为可能会改变"
echo "在比2.05b版本更新的Bash版本中"
exit 0
数组允许在脚本中实现一些常见的熟悉算法。
Example 26-11 一位老朋友:冒泡排序
#!/bin/bash
#
exchange()
{
#交换数组的两个元素
local temp=${Countries[$1]} #临时保存要交换的一个元素
Countries[$1]=${Countries[$2]}
Countries[$2]=$temp
return
}
declare -a Countries
Countries=(Netherlands Ukraine Zaire Turkey Russia Yemen Syria \
Xanadu Qatar Liechtenstein Hungary)
clear
echo "0:${Countries[*]}" #从0索引的元素开始列出整个数组
number_of_elements=${#Countries[@]}
let "comparisons = $number_of_elements - 1"
count=1
while [ "$comparisons" -gt 0 ]
do
index=0
while [ "$index" -lt "$comparisons" ]
do
if [ ${Countries[$index]} \> ${Countries[`expr $index + 1`]} ];then
# "\>" 在单方括号里是is ASCII码的比较操作符
# if [[ ${Countries[$index]} > ${Countries[`expr $index + 1`]} ]] 也可以
exchange $index `expr $index + 1`
fi
let "index += 1"
done
let "comparisons -= 1"
echo
echo "$count: ${Countries[@]}"
echo
let "count += 1"
done
exit 0
在数组内嵌一个数组有可能做到吗?
#!/bin/bash
#
AnArray=($(ls --inode --ignore-backups --almost-all \
--directory --full-time --color=none --time=status \
--sort=time -l ${PWD}))
SubArray=(${AnArray[@]:11:1} ${AnArray[@]:6:5})
#这个数组有6个元素:
#SubArray=( [0]=${AnArray[11]} [1]=${AnArray[6]} [2]=${AnArray[7]}
# [3]=${AnArray[8]} [4]=${AnArray[9]} [5]=${AnArray[10]} )
echo "当前目录和上次状态更改的日期:"
echo "${SubArray[@]}"
exit 0
内嵌数组和间接引用的组合使用产生了一些有趣的用法
Example 26-12 内嵌数组和间接引用
#!/bin/bash
#
ARRAY1=(VAR1_1=value11 VAR1_2=value12 VAR1_3=value13)
ARRAY2=(VARIABLE="test" STRING="VAR1=value1 VAR2=value2 VAR3=value3" ARRAY21=${ARRAY1[*]})
function print()
{
OLD_IFS="$IFS"
IFS=$'\n' #这是为了在每行打印一个数组元素
TEST1="ARRAY2[*]"
local ${!TEST1} #间接引用
echo
echo "\$TEST1 = $TEST1"
echo;echo
echo "{\$TEST1} = ${!TEST1}"
echo
echo "-------------------------------"
echo
echo "Variable VARIABLE: $VARIABLE"
IFS="$OLD_IFS"
TEST2="STRING[*]"
local ${!TEST2}
echo "String element VAR2:$VAR2 from STRING"
TEST2="ARRAY21[@]"
local ${!TEST2}
echo "Array element VAR1_1:$VAR1_1 from ARRAY21"
}
print
echo
exit 0
Example 26-13 复杂数组应用:埃拉托色尼素数筛子
#!/bin/bash
#
LOWER_LIMIT=1
UPPER_LIMIT=1000
PRIME=1
NON_PRIME=0
let SPLIT=UPPER_LIMIT/2
declare -a Primes
initialize()
{
i=$LOWER_LIMIT
until [ "$i" -gt "$UPPER_LIMIT" ]
do
Primes[i]=$PRIME
let "i += 1"
done
}
print_primes()
{
i=$LOWER_LIMIT
until [ "$i" -gt "$UPPER_LIMIT" ]
do
if [ "${Primes[i]}" -eq "$PRIME" ];then
printf "%8d" $i
fi
let "i += 1"
done
}
sift()
{
let i=$LOWER_LIMIT+1
until [ "$i" -gt "$UPPER_LIMIT" ]
do
if [ "${Primes[i]}" -eq "$PRIME" ];then
t=$i
while [ "$t" -le "$UPPER_LIMIT" ]
do
let "t += $i"
Primes[t]=$NON_PRIME
done
fi
let "i += 1"
done
}
#==================================
#main ()
#继续调用函数
initialize
sift
print_primes
#这就是被称为结构化编程的东西了
#==================================
echo
exit 0
#
#-----------------------------------
#下面是一个改进版,需要位置参数
UPPER_LIMIT=$1
let SPLIT=UPPER_LIMIT/2
Primes=('' $(seq $UPPER_LIMIT))
i=1
until (( (i += 1) > SPLIT ))
do
if [[ -n $Primes[i] ]];then
t=$i
until (( (t += i) > UPPER_LIMIT ))
do
Primes[t]=
done
fi
done
echo ${Primes[*]}
exit 0
比较这个用数组的素数产生器和另一种不用数组的例子A-16
数组可以做一定程度的扩展,以模拟支持Bash原本不支持的数据结构
Example 26-14 模拟下推的堆栈
#!/bin/bash
#
BP=100
SP=$BP
Data=
declare -a stack
push()
{
if [ -z "$1" ];then
return
fi
let "SP -= 1"
stack[$SP]=$1
return
}
pop ()
{
Data=
if [ "$SP" -eq "$BP" ];then
return
fi
Data=${stack[$SP]}
let "SP += 1"
return
}
status_report()
{
echo "--------------------------"
echo "REPORT"
echo "Stack Pointer = $SP"
echo "Just popped \""$Data"\" off the stack"
echo "--------------------------"
echo;echo;echo;echo
}
echo
pop
status_report
echo
push garbage
pop
status_report
value1=23;push $value1
value2=skidoo;push $value2
value3=FINAL;push $value3
pop
status_report #FINAL
pop
status_report #skidoo
pop
status_report #23
#后进的先出
#注意堆栈指针:每次压栈时减,每次弹出是加。
echo
exit 0
Example 26-15 复杂的数组应用:列出一种怪异的数学序列
#!/bin/bash
#
LIMIT=100
LINEWIDTH=20
Q[1]=1
Q[2]=1
echo
echo "Q-series [$LIMIT terms]:"
echo -n "${Q[1]}"
echo -n "${Q[2]}"
for (( n=3;n<=$LIMIT;n++))
do
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 ];then
echo
fi
done
echo
exit 0
Bash只支持一维数组,但有些技巧可用来模拟多维数组
Example 26-16 模拟二维数组,并使它倾斜(没完全看明白)
#!/bin/bash
#
#一维数组由单行组成.
#二维数组由连续的行组成.
Rows=5
Columns=5
declare -a alpha
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
local row=`expr $rc / $Columns`
local column=`expr $rc % $Rows`
let "index = $row * $Rows + $column"
alpha[$index]=$i
let "rc += 1"
done
}
#更简单的办法
#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 )
#但这就缺少了二维数组的感觉了
print_alpha ()
{
local row=0
local index
echo
while [ "$row" -lt "$Rows" ] #以行顺序为索引打印行的各元素
#:即数组列值变化快,行值变化慢
do
local column=0
echo -n " " #依行倾斜打印正方形的数组
while [ "$column" -lt "$Columns" ]
do
let "index = $row *$Rows + $column"
echo -n "${alpha[index]}"
let "column += 1"
done
let "row += 1"
echo
done
#等同于:echo ${alpha[*]} | xargs -n $Columns
echo
}
filter () #过滤出负数的数组索引.
{
echo -n " " #产生倾斜角度
if [[ "$1" -ge 0 && "$1" -lt "$Rows" && "$2" -ge 0 && "$2" -lt "$Columns" ]]
then
let "index = $1 * $Rows + $2"
#现在打印旋转角度.
echo -n "${alpha[index]}"
fi
}
rotate () #旋转数组45度 :在左下角"平衡"图形
{
local row
local column
for ((row = Rows; row > -Rows; row--))
do
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
done
echo;echo
done
}
load_alpha #加载数组
print_alpha #打印数组
rotate #逆时针旋转数组45度
exit 0
二维数组本质上等同于一维数组,而只增加了使用行和列的位置来引用和操作元素的寻址模式。
关于二维数组更好的例子,请参考例子A-10
另一个有趣的使用数组的脚本:例子14-3