Zsh 開發指南(五): 陣列

陌辭寒發表於2017-09-24

Zsh 開發指南(五): 陣列

導讀

瞭解完結構比較簡單的字串後,我們來看更復雜一些的陣列。其實字串在 zsh 中也可以當字元陣列操作,但很少有需要把字串當陣列來處理的場景。本篇中主要講的是字串陣列,複雜度要比單個字串高一些。

在實際的指令碼編寫中,較少需要處理單個的字串。往往需要處理從各個地方過來的大量文字,不可避免會用到陣列。用好陣列,會讓文字處理工作事半功倍。

本篇只涉及陣列的基礎用法。

陣列定義

陣列可以直接賦值使用,不需要提前宣告。等號和小括號之間不能有空格,小括號中的元素以空格隔開。

% array=(a bc ccc dddd)
# 用 $array 即可訪問陣列全部元素,輸出時元素以空格分隔
% echo $array
a bc ccc dddd

# 使用 print -l 可以每行輸出一個元素
% print -l $array
a
bc
ccc
dddd

# 輸出陣列中的元素個數,用法和取字串長度一樣
% echo $#array
4

# 包含帶空格的字串
% array=(a "bc ccc" dddd)
% print -l $array
a
bc ccc
dddd

# 可以換行賦值,但如果行中間有空格,依然需要加引號
% array=(
> a
> bb
> "c c c"
> dddd
> )

元素讀寫

% array=(a bc ccc dddd)

# 用法和取字串的第幾個字元一樣,從 1 開始算
% echo $array[3]
ccc
# -1 依然是最後一個元素,-2 是倒數第二個,以此類推
% echo $array[-1]
dddd

% array[3]=CCC

# 如果賦值的內容是一個空的小括號,則刪除該元素
% array[2]=()

% print -l $array
a
CCC
dddd

# 用 += 為陣列新增一個新元素
% array+=eeeee
% print -l $array
a
CCC
dddd
eeeee

# 用 unset 可以刪除整個陣列
% unset array

# array 變數變成未定義狀態
% echo $+array
0

陣列拼接

% array1=(a b c d)
% array2=(1 2 3 4)

# 用 += 拼接陣列
% array1+=(e f g)
% echo $array1
a b c d e f g

# 拼接另一個陣列,小括號不可以省略,否則 array1 會被轉成一個字串
% array2+=($array1)
% echo $#array2
11

# 去掉小擴號後,array1 被轉成了一個字串
% array2+=$array1
% echo $#array2
12
% echo $array2[12]
a b c d e f g


# 字串可以直接拼接陣列而轉化成陣列
% str=abcd
% str+=(1234)

% echo $#str
2

陣列遍歷

% 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)

% echo $array[2,3]
bb ccc

# 依然可以多對多地替換元素
% array[3,-1]=(1 2 3 4)
% echo $array
a bb 1 2 3 4

# 也可以使用另一種語法,不建議使用
% echo ${array:0:3}
a bb 1

元素查詢

陣列的元素查詢方法,和字串的子字串查詢語法一樣。

% 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

元素排序

% 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

去除重複元素

% array=(ddd a bb a ccc bb ddd)

% echo ${(u)array}
ddd a bb ccc

使用連續字元或者數值構造陣列

# 大括號中的逗號分隔的字串會被展開
% 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

從字串構造陣列

% 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

從檔案構造陣列

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


# 從檔案構造陣列,並將每行按分隔符 : 分隔後輸出所有列
for i (${(f)"$(<test.txt)"}) {
    array=(${(s.:.)i})
    echo $array[1,-1]
}

從檔案列表構造陣列

# 這裡的 * 即上一篇講的萬用字元,所有的用法都可以在這裡使用。
% array=(/usr/bin/vim*)
% print -l $array
/usr/bin/vim
/usr/bin/vimdiff
/usr/bin/vimtutor

# 要比 ls /usr/bin/[a-b]?? | wc -l 快很多
% array=(/usr/bin/[a-b]??) && print $#array
3

陣列交集差集

% 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

對陣列中的字串進行統一的處理

一些處理字串的方法(主要是各種形式的擷取、替換、轉換等等),也可以用在陣列上,效果是對陣列中所有元素統一處理。

% 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

總結

本篇講的是陣列的基礎用法,還有很多複雜的操作方法,以後會提到。

參考

http://zshwiki.org/home/scripting/array

http://www.bash2zsh.com/zsh_refcard/refcard.pdf

更新歷史

20170830:增加“使用連續字元或者數值構造陣列”。

20170909:修正“從字串構造陣列”中的錯誤。

20170910:增加“從字串構造陣列”中的部分內容。

相關文章