#!/bin/bash
# array-strops.sh: 用于数组的字符串操作。
# 本脚本由Michael Zick 所编写.
# 通过了授权在本书中使用。
# 修复: 05 May 08, 04 Aug 08.
# 一般来说,任何类似于 ${name ... }(这种形式)的字符串操作
#+ 都能够应用于数组中的所有字符串元素,
#+ 比如说${name[@] ... } 或者 ${name[*] ...} 这两种形式。
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
# ^ element[0]后边的所有元素.
echo ${arrayZ[@]:1:2} # two three
# ^ 只提取element[0]后边的两个元素.
echo "---------"
# 子串删除
# 从字符串的开头删除最短的匹配。
echo ${arrayZ[@]#f*r} # one two three five five
# ^ # 匹配将应用于数组的所有元素。
# 匹配到了"four",并且将它删除。
# 从字符串的开头删除最长的匹配
echo ${arrayZ[@]##t*e} # one two four five five
# ^^ # 匹配将应用于数组的所有元素
# 匹配到了 "three" ,并且将它删除。
# 从字符串的结尾删除最短的匹配
echo ${arrayZ[@]%h*e} # one two t four five five
# ^ # 匹配将应用于数组的所有元素
# 匹配到了 "hree" ,并且将它删除。
# 从字符串的结尾删除最长的匹配
echo ${arrayZ[@]%%t*e} # one two four five five
# ^^ # 匹配将应用于数组的所有元素
# 匹配到了 "three" ,并且将它删除。
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
# ^ # 为什么?
echo "-----------------------------"
replacement() {
echo -n "!!!"
}
echo ${arrayZ[@]/%e/$(replacement)}
# ^ ^^^^^^^^^^^^^^
# on!!! two thre!!! four fiv!!! fiv!!!
# replacement()的标准输出就是那个替代字符串.
# Q.E.D: 替换动作实际上是一个‘赋值’。
echo "------------------------------------"
# 使用"for-each"之前:
echo ${arrayZ[@]//*/$(replacement optional_arguments)}
# ^^ ^^^^^^^^^^^^^
# !!! !!! !!! !!! !!! !!!
# 现在,如果Bash只将匹配到的字符串
#+ 传递给被调用的函数...
echo
exit 0
# 在将处理后的结果发送到大工具之前,比如-- Perl, Python, 或者其它工具
# 回忆一下:
# $( ... ) 是命令替换。
# 一个函数作为子进程运行。
# 一个函数将结果输出到stdout。
# 赋值,结合"echo"和命令替换,
#+ 可以读取函数的stdout.
# 使用name[@]表示法指定了一个 "for-each"
#+ 操作。
# Bash比你想象的更加强力.
#!/bin/bash
# script-array.sh: 将脚本中的内容赋值给数组。
# 这个脚本的灵感来自于 Chris Martii 的邮件 (感谢!).
script_contents=( $(cat "$0") ) # 将这个脚本的内容($0)
#+ 赋值给数组
for element in $(seq 0 $((${#script_contents[@]} - 1)))
do # ${#script_contents[@]}
#+ 表示数组元素的个数
#
# 问题:
# 为什么必须使用seq 0 ?
# 用seq 1来试一下.
echo -n "${script_contents[$element]}"
# 在同一行上显示脚本中每个域的内容。
# echo -n "${script_contents[element]}" also works because of ${ ... }.
echo -n " -- " # 使用 " -- " 作为域分隔符。
done
echo
exit 0
# 练习:
# --------
# 修改这个脚本,
#+ 让这个脚本能够按照它原本的格式输出,
#+ 连同空格,换行,等等。
#!/bin/bash
declare -a colors
# 脚本中所有的后续命令都会把
#+ "colors" 当做数组
echo "Enter your favorite colors (separated from each other by a space)."
read -a colors # 至少需要键入3种颜色,以便于后边的演示。
# 'read'命令的特殊选项 ,
#+ 允许给数组元素赋值。
echo
element_count=${#colors[@]}
# 提取数组元素个数的特殊语法
# 用element_count=${#colors[*]} 也可以。
#
# "@" 变量允许在引用中存在单次分割,
#+ (依靠空白字符来分割变量).
#
# 这就好像"$@" 和 "$*"
#+ 在位置参数中所表现出来的行为一样。
index=0
while [ "$index" -lt "$element_count" ]
do # 列出数组中的所有元素
echo ${colors[$index]}
# ${colors[index]} 也可以工作,因为它${ ... }之中.
let "index = $index + 1"
# Or:
# ((index++))
done
# 每个数组元素被列为单独的一行
# 如果没有这种要求的话,可以使用echo -n "${colors[$index]} "
#
# 也可以使用“for”循环来做:
# for i in "${colors[@]}"
# do
# echo "$i"
# done
# (Thanks, S.C.)
echo
# 再次列出数组中的所有元素,不过这次的做法更为优雅。
echo ${colors[@]} # echo ${colors[*]} 也可以工作.
echo
# "unset"命令既可以删除数组数据,也可以删除整个数组。
unset colors[1] # 删除数组的第2个元素。
# 作用等同于colors[1]=
echo ${colors[@]} # 再次列出数组内容,第2个元素没了。
unset colors # 删除整个数组。
# unset colors[*] 以及
#+ unset colors[@] 都可以.
echo; echo -n "Colors gone."
echo ${colors[@]} # 再次列出数组内容,内容为空。
exit 0
#!/bin/bash
# empty-array.sh
# 感谢Stephane Chazelas制作这个例子的原始版本。
#+ 同时感谢Michael Zick 和 Omair Eshkenazi 对这个例子所作的扩展。
# 以及感谢Nathan Coulter 作的声明和感谢。
# 空数组与包含有空元素的数组,这两个概念不同。
array0=( first second third )
array1=( '' ) # "array1" 包含一个空元素.
array2=( ) # 没有元素. . . "array2"为空
array3=() # 这个数组呢?
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
# 尝试扩展这些数组。
# 添加一个元素到这个数组。
array0=( "${array0[@]}" "new1" )
array1=( "${array1[@]}" "new1" )
array2=( "${array2[@]}" "new1" )
array3=( "${array3[@]}" "new1" )
ListArray
# 或者
array0[${#array0[*]}]="new2"
array1[${#array1[*]}]="new2"
array2[${#array2[*]}]="new2"
array3[${#array3[*]}]="new2"
ListArray
# 如果你按照上边的方法对数组进行扩展的话,数组比较像‘栈’
# 上边的操作就是‘压栈’
# ‘栈’的高度为:
height=${#array2[@]}
echo
echo "Stack height for array2 = $height"
# '出栈’就是:
unset array2[${#array2[@]}-1] # 数组从0开始索引
height=${#array2[@]} #+ 这就意味着数组的第一个下标是0
echo
echo "POP"
echo "New stack height for array2 = $height"
ListArray
# 只列出数组array0的第二个和第三个元素。
from=1 # 从0开始索引。
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 # This one might surprise you.
echo "Elements in array6: ${array6[@]}"
array7=( ${array0[@]#new1} )
echo # 数组array6之后就没有惊奇了。
echo "Elements in array7: ${array7[@]}"
# 看起来非常像...
array8=( ${array0[@]/new1/} )
echo
echo "Elements in array8: ${array8[@]}"
# 所以,让我们怎么形容呢?
# 对数组var[@]中的每个元素The string operations are performed on
#+ 进行连续的字符串操作。each of the elements in var[@] in succession.
# 因此:Bash支持支持字符串向量操作,
# 如果结果是长度为0的字符串
#+ 元素会在结果赋值中消失不见。
# 然而,如果扩展在引用中,那个空元素会仍然存在。
# Michael Zick: 问题--这些字符串是强引用还是弱引用?
# Nathan Coulter: 没有像弱引用的东西
#! 真正发生的事情是
#!+ 匹配的格式发生在
#!+ [word]的所有其它扩展之后
#!+ 比如像${parameter#word}.
zap='new*'
array9=( ${array0[@]/$zap/} )
echo
echo "Number of elements in array9: ${#array9[@]}"
array9=( "${array0[@]/$zap/}" )
echo "Elements in array9: ${array9[@]}"
# 此时,空元素仍然存在
echo "Number of elements in array9: ${#array9[@]}"
# 当你还在认为你身在Kansas州时...
array10=( ${array0[@]#$zap} )
echo
echo "Elements in array10: ${array10[@]}"
# 但是,如果被引用的话,*号将不会被解释。
array10=( ${array0[@]#"$zap"} )
echo
echo "Elements in array10: ${array10[@]}"
# 可能,我们仍然在Kansas...
# (上面的代码块Nathan Coulter所修改.)
# 比较 array7 和array10.
# 比较array8 和array9.
# 重申: 所有所谓弱引用的东西
# Nathan Coulter 这样解释:
# word在${parameter#word}中的匹配格式在
#+ 参数扩展之后和引用移除之前已经完成了。
# 在通常情况下,格式匹配在引用移除之后完成。
exit
#!/bin/bash
# twodim.sh: 模拟一个二维数组.
# 一维数组由单行组成.
# 二维数组由连续的多行组成.
Rows=5
Columns=5
# 5 X 5 的数组.
declare -a alpha # char alpha [Rows] [Columns];
# 没必要声明. 为什么?
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
# alpha[$row][$column]
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]} " # alpha[$row][$column]
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]}"
# alpha[$row][$column]
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
# 数组旋转的灵感来源于Herbert Mayer 所著的
#+ "Advanced C Programming on the IBM PC"的例子(第143-146页)
#+ (参见参考书目).
# 由此可见, C语言能够做到的好多事情,
#+ 用shell 脚本一样能够做到.
}
#--------------- 现在, 让我们开始吧. ------------#
load_alpha # 加载数组
print_alpha # 打印数组.
rotate # 逆时钟旋转45度打印.
#-----------------------------------------------------#
exit 0
# 这有点做作, 不是那么优雅.
# 练习:
# -----
# 1) 重新实现数组加载和打印函数,
# 让其更直观, 可读性更强.
#
# 2) 详细地描述旋转函数的原理.
# 提示: 思考一下倒序索引数组的实现.
#
# 3) 重写这个脚本, 扩展它, 让不仅仅能够支持非正方形的数组.
# 比如6 X 4的数组.
# 尝试一下, 在数组旋转时, 做到最小"失真".