数组
自我感觉:数组基本操作大部分跟字符串操作差不多
数组遍历
% array1=(a bb ccc dddd)
% array2=(1 2 3)
# 用 for 可以直接遍历数组,小括号不可省略
% for i ($array1) {
> echo $i
> }
a
bb
ccc
dddd
# 小括号里可以放多个数组,依次遍历
% for i ($array1 $array2) {
> echo $i
> }
a
bb
ccc
dddd
1
2
3
记:按照正常的先后顺序,先遍历完前一个,再继续下一个,从左到右;
元素查找
数组的元素查找方法,和字符串的子字符串查找语法一样。
% array=(a bb ccc dddd ccc)
# 用小 i 输出从左到右第一次匹配到的元素位置
% echo $array[(i)ccc]
3
# 如果找不到,返回数组大小 + 1
% echo $array[(i)xxx]
6
# 用大 I 输出从右到左第一次匹配到的元素位置
% echo $array[(I)ccc]
5
# 如果找不到,返回 0
% echo $array[(I)xxx]
0
# 可以用大 I 判断是否存在元素
% (($array[(I)dddd])) && echo good
good
% (($array[(I)xxx])) && echo good
% array=(aaa bbb aab bbc)
# n:2: 从指定的位置开始查找
% echo ${array[(in:2:)aa*]}
3
记: 用小 i 输出从左到右第一次匹配到的元素位置,如果找不到,返回数组大小 + 1;
用大 I 输出从右到左第一次匹配到的元素位置,如果找不到,返回 0,可以用大 I 判断是否存在元素;
n:p: 从指定的位置(元素p为数组的角标,zsh中第一位为1,bash中则为0)开始查找。
元素排序
% array=(aa CCC b DD e 000 AA 3 aa 22)
# 用小写字母 o 升序排列,从小到大
% echo ${(o)array}
000 22 3 aa aa AA b CCC DD e
# 用大写字母 O 降序排列,从大到小
% echo ${(O)array}
e DD CCC b AA aa aa 3 22 000
# 加 i 的话大小写不敏感
% echo ${(oi)array}
000 22 3 aa AA aa b CCC DD e
% array=(cc aaa b 12 115 90)
# 加 n 的话按数字大小顺序排
% echo ${(on)array}
12 90 115 aaa b cc
# Oa 用于反转数组元素的排列顺序
% echo ${(Oa)array}
90 115 12 b aaa cc
记: 1.正常排序规律:先看第一位,若相同,则比较下一位,依照ASCII码表,数字在前,字母在后。
2.用小写字母 o 升序排列,从小到大;用大写字母 O 降序排列,从大到小。
3.加 i 的话大小写不敏感,加 n 的话按数字大小顺序排,Oa 用于反转数组元素的排列顺序,但先数字(带数字的混合串也算)再字母串的规律仍然遵循!
去除重复元素
% array=(ddd a bb a ccc bb ddd)
% echo ${(u)array}
ddd a bb ccc
助记:(u)->unset的首字母
使用连续字符或者数值构造数组
# 大括号中的逗号分隔的字符串会被展开
% array=(aa{bb,cc,11}) && echo $array
aabb aacc aa11
# .. 会将前后的数组连续展开
% array=(aa{1..3}) && echo $array
aa1 aa2 aa3
# 第二个 .. 后的数字是展开的间隔
% array=(aa{15..19..2}) && echo $array
aa15 aa17 aa19
# 也可以从大到小展开
% array=(aa{19..15..2}) && echo $array
aa19 aa17 aa15
# 可以添加一个或多个前导 0
% array=(aa{01..03}) && echo $array
aa01 aa02 aa03
# 单个字母也可以像数值那样展开,多个字母不行
% array=(aa{a..c}) && echo $array
aaa aab aac
# 字母是按 ASCII 码的顺序展开的
% array=(aa{Y..c}) && echo $array
aaY aaZ aa[ aa\ aa] aa^ aa_ aa` aaa aab aac
# 这些用法都可以用在 for 循环里边
% for i (aa{a..c}) {
> echo $i
> }
aaa
aab
aac
记: 1.第一个..为需要展开的数组区间,第二个..后的数字是展开的间隔;
2.可以从大到小,也可从小到大,..不对前后大小做出规定;
3.单个字母也可以像数值那样展开,多个字母不行;
4.字母是按 ASCII 码的顺序展开的;
5.可以添加一个或多个前导 0。
从字符串构造数组
% str="a bb ccc dddd"
# ${=str} 可以将 str 内容按空格切分成数组
% array=(${=str})
% print -l $array[2,3]
bb
ccc
% str="a:bb:ccc:dddd"
# 如果是其他分隔符,可以设置 IFS 环境变量指定
% IFS=:
% array=(${=str})
% print -l $array[2,3]
bb
ccc
% str="a\nbb\nccc\ndddd"
# 如果是其他分隔符,也可以用 (s:x:) 指定
% array=(${(s:\n:)str})
% print -l $array[2,3]
bb
ccc
% str="a##bb##ccc##dddd"
# 分隔符可以是多个字符
% array=(${(s:##:)str})
% print -l $array[2,3]
bb
ccc
% str="a:bb:ccc:dddd"
# 如果分隔符是 :,可以 (s.:.)
% array=(${(s.:.)str})
% print -l $array[2,3]
bb
ccc
记:1. ${=str} 可以将 str 内容按空格切分成数组;
2.如果是其他分隔符,可以设置 IFS 环境变量指定,如IFS=:;也可以用 (s:x:) 指定,如(s:/n:),前面使用过;
3.分隔符可以是多个字符,特别地是:如果分隔符是 :,可以 (s.:.)。
从文件构造数组
test.txt
内容。
a
bb
ccc
dddd
每行一个元素。
# f 的功能是将字符串以换行符分隔成数组
# 双引号不可省略,不然会变成一个字符串,引号也可以加在 ${ } 上
% array=(${(f)"$(<test.txt)"})
% print -l $array
a
bb
ccc
dddd
# 不加引号的效果
% array=(${(f)$(<test.txt)})
% print -l $array
a bb ccc dddd
记:f 的功能是将字符串以换行符分隔成数组,双引号不可省略,不然会变成一个字符串,引号也可以加在 ${ } 上;不加引号就不会将换行符转义,将所有元素显示在一行。
数组交集差集
% array1=(1 2 3)
% array2=(1 2 4)
# 两个数组的交集,只输出两个数组都有的元素
% echo ${array1:*array2}
1 2
# 两个数组的差集,只输出 array1 中有,而 array2 中没有的元素
% echo ${array1:|array2}
3
# 如果有重复元素,不会去重
% array1=(1 1 2 3 3)
% array2=(4 4 1 1 2 2)
% echo ${array1:*array2}
1 1 2
记: :*——>求交集 :|——>求差集,前者为集合本身即操作对象,后者为差集的计算对象
如果有重复元素,不会去重。
数组交叉合并
% array1=(a b c d)
% array2=(1 2 3)
# 从 array1 取一个,再从 array2 取一个,以此类推,一个数组取完了就结束
% echo ${array1:^array2}
a 1 b 2 c 3
# 如果用 :^^,只有一个数组取完了的话,继续从头取,直到第二个数组也取完了
% echo ${array1:^^array2}
a 1 b 2 c 3 d 1
记: a1:^a2——>先从数组a1中取一个,再从数组a2中取一个,以此类推;
a1:^^a2——>大致上和上面的差不多,就是只有一个数组取完了的话,继续从头取,直到第二个数组也取完了。
对数组中的字符串进行统一的处理
一些处理字符串的方法(主要是各种形式的截取、替换、转换等等),也可以用在数组上,效果是对数组中所有元素统一处理。
% array=(/a/b.htm /a/c /a/b/c.txt)
# :t 是取字符串中的文件名,可以用在数组上,取所有元素的文件名
% print -l ${array:t}
b.htm
c
c.txt
# :e 是取扩展名,如果没有扩展名,结果数组中不会添加空字符串
% print -l ${array:e}
htm
txt
# 字符串替换等操作也可以对数组使用,替换所有字符串
% print -l ${array/a/j}
/j/b.txt
/j/c
/j/b/c.txt
:#
也可以在数组上用,但更实用一些。
% array=(aaa bbb ccc)
# :# 是排除匹配到的元素,类似 grep -v
% print ${array:#a*}
bbb ccc
# 前边加 (M),是反转后边的效果,即只输出匹配到的元素,类似 grep
% print ${(M)array:#a*}
aaa
# 多个操作可以同时进行,(U) 是把字符串转成大写字母
% print ${(UM)array:#a*}
AAA
记: :t 是取字符串中的文件名,可以用在数组上,取所有元素的文件名;
:e 是取扩展名,如果没有扩展名,结果数组中不会添加空字符串;
:# 是排除匹配到的元素,类似 grep -v;
前边加 (M),是反转后边的效果,即只输出匹配到的元素;
多个操作可以同时进行,(U) 是把字符串转成大写字母。
哈希表
哈希表是比数组更复杂的数据结构,在某些语言里被称作关联数组或者字典等等。简单说,哈希表用于存放指定键(key)对应的值(value),键和值的关系,就像字典中单词和释义的对应关系,通过单词可以快速找到释义,而不需要从头依次遍历匹配。
哈希表定义
和其他变量类型不同,哈希表是需要提前声明的,因为哈希表的赋值语法和数组一样,如果不声明,是无法区分的。
% typeset -A table
# 或者用 local,二者功能是一样的
% local -A table
# 赋值的语法和数组一样,但顺序依次是键、值、键、值
% table=(k1 v1 k2 v2)
# 直接用 echo 只能输出值
% echo $table
v1 v2
# 使用 (kv) 同时输出键和值,(kv) 会把键和值都放到同一个数组里
% echo ${(kv)table}
k1 v1 k2 v2
# 哈希表的大小是键值对的数量
% echo $#table
2
记:哈希表需声明,关键词为typeset -A或者local -A,之后的赋值操作和数组一样,奇数位则为key,偶数位为value;
元素读写
读写哈希表的方法和数组类似,只是用于定位的数字变成了字符串。
# 可以声明和赋值写到一行
% local -A table=(k1 v1 k2 v2 k3 v3)
% echo $table[k2]
v2
% table[k2]="V2"
# 删除元素的方法和数组不同,引号不能省略
% unset "table[k1]"
% echo ${(kv)table}
k2 V2 k3 v3
注:删除元素的方法和数组不同,引号不能省略;
哈希表拼接
# 追加元素的方法和数组一样
% table+=(k4 v4 k5 v5)
% echo $table
V2 v3 v4 v5
% local -A table1 table2
% table1=(k1 v1 k2 v2)
% table2=(k2 v222 k3 v3)
# 拼接哈希表,要展开成数组再追加
% table1+=(${(kv)table2})
# 如果键重复,会直接替换值,哈希表的键是不重复的
% echo ${(kv)table1}
k1 v1 k2 v222 k3 v3
注:1.拼接哈希表,要展开成数组再追加;2.如果键重复,会直接替换值,哈希表的键是不重复的。
哈希表遍历
用 (kv)
(k)
等先将哈希表转化成数组,然后再遍历。
% local -A table=(k1 v1 k2 v2 k3 v3)
# 只遍历值
% for i ($table) {
> echo $i
> }
v1
v2
v3
# 只遍历键
% for i (${(k)table}) {
> echo $i
> }
k1
k2
k3
# 同时遍历键和值
% for k v (${(kv)table}) {
> echo "$k -> $v"
> }
k1 -> v1
k2 -> v2
k3 -> v3
元素查找
判断键是否存在。
% local -A table=(k1 v1 k2 v2 k3 v3)
% (($+table[k1])) && echo good
good
% (($+table[k4])) && echo good
如果需要判断某个值是否存在,直接对值的数组判断即可。但这样做就体现不出哈希表的优势了。
% local -A table=(k1 v1 k2 v2 k3 v3)
# value 是值的数组,也可以用 local -a 强行声明为数组
% value=($table)
% (( $value[(I)v1] )) && echo good
good
% (( $value[(I)v4] )) && echo good
元素排序
对哈希表元素排序的方法,和数组类似,多了 k
v
两个选项,其余的选项如 o
(升序)、O
(降序)、n
(按数字大小)、i
(忽略大小写)等通用,不再一一举例。
% local -A table=(aa 33 cc 11 bb 22)
# 只对值排序
% echo ${(o)table}
11 22 33
# 只对键排序
% echo ${(ok)table}
aa bb cc
# 键值放在一起排序
% echo ${(okv)table}
11 22 33 aa bb cc
:#
也可以在哈希表上用。
% local -A table=(k1 v1 k2 v2 k3 v3)
# 排除匹配到的值
% echo ${table:#v1}
v2 v3
# 只输出匹配到的键
% echo ${(Mk)table:#k[1-2]}
k1 k2
多维哈希表
Zsh 并不支持多维哈希表以及多维数组,但可以通过一些方法来模拟,以实现一部分功能。
用一维哈希表模拟多维哈希表
% local -A table
# 这里用 , 作为分隔符,也可以用其他符号。
% table[1,1]=a
% table[1,2]=b
% table[k,v]=c
% echo $table[1,1] $table[1,2] $table[k,v]
a b c
好处:使用方便,而且支持的维数不受限制。
坏处:功能太单一,比如不能对 table[1]
进行处理。
用字符串分割访问来模拟多维哈希表
% local -A table
# 分隔符为空格
% table[1]='a b'
% table[2]='c d'
% print -l $table[1] ${table[1][(w)2]} ${table[2][(w)1]}
a b
b
c
# 分隔符不是空格
% table[a]='aa,bb'
% table[b]='cc,dd'
% print -l $table[a] ${table[a][(ws:,:)2]} ${table[b][(ws:,:)1]}
aa,bb
bb
cc
好处:可以对 table[1]
进行处理。
坏处:不大方便,性能也不好。而且功能同样受限,比如第一维只能是数组,不能是哈希表。可以支持更多维,但需要再增加新的分隔符,使用起来更麻烦。