Bash技巧:詳解鍵值對關聯數組、一維索引數組的用法

Bash 的關聯數組詳解

Bash 支持關聯數組(associative arrays),可使用任意的字符串、或者整數做爲下標來訪問數組元素。
關聯數組的下標和值稱爲鍵值對,它們是一一對應關係,鍵是惟一的,值能夠不惟一。shell

要使用關聯數組以前,須要用 declare -A array_name 來進行顯式聲明 array_name 變量爲關聯數組。express

查看 help declare 對 -A 選項的說明以下:數組

-A
to make NAMEs associative arrays (if supported)

例以下面的語句定義了一個名爲 filetypes 的關聯數組,併爲數組賦值:bash

$ declare -A filetypes=([txt]=text [sh]=shell [mk]=makefile)
$ filetypes[c]="c source file"

在使用數組名進行賦值時,須要用小括號 () 把全部的值括起來。測試

在關聯數組裏面,用方括號 [] 括起來的值是 key。
爲方括號 [] 賦予的值是該 key 對應的 value。
不一樣的鍵值對之間用空格隔開。注意不是用逗號隔開。ui

也可使用 filetypes[key]=value 的方式單獨爲指定的關聯數組元素賦值。
若是所給的 key 以前不存在,bash 會自動建立它。
若是已經存在,則修改它的值爲 value 對應的值。lua

基於前面定義的 filetypes 這個數組名:code

  • ${!filetypes[*]}:獲取關聯數組的全部鍵名,注意在 filetypes 前面有一個感嘆號 ‘!’。遞歸

    $ echo ${!filetypes[*]}
    txt sh c mk
  • ${!filetypes[@]}: 獲取關聯數組的全部鍵名。後面會說明使用 *@ 的區別。索引

    $ echo ${!filetypes[@]}
    txt sh c mk
  • ${filetypes[*]}:獲取關聯數組的全部值。相比於獲取鍵名的表達式,少了前面的感嘆號 ‘!’。

    $ echo ${filetypes[*]}
    text shell c source file makefile
  • ${filetypes[@]}:獲取關聯數組的全部值。

    $ echo ${filetypes[@]}
    text shell c source file makefile
  • ${#filetypes[*]}:獲取關聯數組的長度,即元素個數。注意在 filetypes 前面有一個井號 ‘#’。

    $ echo ${#filetypes[*]}
    4
  • ${#filetypes[@]}:獲取關聯數組的長度,即元素個數

    $ echo ${#filetypes[@]}
    4
  • ${filetypes[key]}:獲取 key 這個鍵名對應的值。注意大括號 {} 是必須的。

    $ echo ${filetypes[sh]}
    shell
    $ echo $filetypes[sh]
    [sh]        # 能夠看到,不加大括號時,並不能獲取到數組元素的值

查看 man bash 的 Arrays 小節,說明了這幾個表達式的含義,同時還提到使用 *@ 的區別,貼出具體的區別以下:

If the word is double-quoted, ${name[*]} expands to a single word with the value of each array member separated by the first character of the IFS special variable, and ${name[@]} expands each element of name to a separate word. When there are no array members, ${name[@]} expands to nothing.

${!name[@]} and ${!name[*]} expand to the indices assigned in array variable name. The treatment when in double quotes is similar to the expansion of the special parameters @ and * within double quotes.

即,使用 * 時,若是用雙引號把整個表達式括起來,例如寫爲 "${!name[*]}"、或者 "${name[*]}",那麼會把全部值合併成一個字符串。

使用 @ 時,若是用雙引號把整個表達式括起來,例如寫爲 "${!name[@]}"、或者 "${name[@]}",那麼會獲得一個字符串數組。
每一個數組元素會用雙引號括起來,因此數組元素自身的空格不會致使拆分紅幾個單詞。

具體以下面的例子所示,這也是遍歷數組元素的例子:

$ for key in "${filetypes[*]}"; do echo "****:" $key; done
****: text shell c source file makefile
$ for key in "${filetypes[@]}"; do echo "@@@@:" $key; done
@@@@: text
@@@@: shell
@@@@: c source file
@@@@: makefile

能夠看到,"${filetypes[*]}" 只產生一個字符串,for 循環只遍歷一次。
"${filetypes[@]}" 產生了多個字符串,for 循環遍歷屢次,是一個字符串數組。
並且所給的 "c source file" 這個字符串沒有被空格隔開成幾個單詞。

上面的例子也演示瞭如何用 for 命令來遍歷數組元素。

可使用 declare -p 命令來查看數組具體的鍵值對關係:

$ declare -p filetypes
declare -A filetypes='([txt]="text" [sh]="shell" [c]="c source file" [mk]="makefile" )'

Bash 的一維數組詳解

Bash 只支持一維數組 (one-dimensional indexed array),不支持二維數組。
聲明一維數組的方式是:declare -a array_name
因爲 bash 不要求明確指定變量的類型,其實不聲明也能夠,按數組的方式直接賦值給變量便可。

查看 help declare 對 -a 選項的說明以下:

-a
to make NAMEs indexed arrays (if supported)

使用 declare -a 聲明的數組,默認以數字做爲數組下標,並且不須要指定數組長度。
其賦值方式說明以下:

  • array=(value1 value2 value3 ... valueN):這種方式從數組下標 0 開始爲數組元素賦值,不一樣值之間用空格隔開,所給的值能夠是數字、字符串等。

    $ declare -a array=(1 2 "30" "40" 5)
    $ echo ${array[@]}
    1 2 30 40 5
  • array=([0]=var1 [1]=var2 [2]=var3 ... [n]=varN):這種方式顯式提供數組下標,指定爲該元素賦值,所給的數組下標能夠不連續。

    $ declare -a array=([0]=1 [1]=2 [3]="30" [6]="60" [9]=9)
    $ echo ${array[@]}    # 用 ${array[@]} 獲取全部數組元素的值
    1 2 30 60 9
    $ echo ${array[5]}    # 上面賦值的時候,跳過了數組下標 5,因此它對應的值爲空
    
    $ declare -p array    # 使用 declare -p 命令查看,會打印出被賦值的全部元素
    declare -a array='([0]="1" [1]="2" [3]="30" [6]="60" [9]="9")'
  • array[0]=value1; array[1]=value2; ...; array[n]=varN:這種方式是單獨爲數組元素賦值。

    $ unset array; declare -a array
    $ array[0]=0; array[1]=1; array[7]="70"
    $ declare -p array
    declare -a array='([0]="0" [1]="1" [7]="70")'

一維數組的其餘用法和前面文章介紹的關聯數組用法同樣。
例如,能夠用 ${array[@]} 獲取全部數組元素的值,用 ${#array[@]} 獲取數組的元素個數,等等。

能夠參考下面的代碼片斷來遍歷一維數組元素:

for item in "${array[@]}"; do
    echo $item
done

一維數組經過正整數來索引數組元素。
若是提供負整數的下標值,那麼它具備特殊含義,表示從數組末尾開始往前索引。
例如,array[-1] 會索引到數組的最後一個元素,array[-2] 索引到數組的倒數第二個元素,依此類推。

具體舉例說明以下:

$ declare -a array=([0]=0 [1]=1 [2]="20" [3]=3)
$ echo ${array[-1]}, ${array[-3]}
3, 1

注意:雖然 declare -a 聲明的數組要用數字做爲數組下標,可是使用字符串做爲數組下標並不會報錯。
實際測試有一些比較古怪的地方。具體舉例以下:

$ declare -a array=([0]=0 [1]=1 [2]="20" [3]=3)
$ array[index]=1000
$ echo ${array[index]}
1000
$ array[new]=2000
$ echo ${array[index]}
2000
$ echo ${array[new]}
2000
$ declare -p array
declare -a array='([0]="2000" [1]="1" [2]="20" [3]="3")'

能夠看到,爲 array[index] 元素賦值,沒有報錯,使用 ${array[index]} 能夠正常獲取到它的值。
可是爲 array[new] 賦值爲 2000 後,使用 ${array[index]} 打印 index 這個字符串下標對應的數組元素值,發現變成了 2000,跟 ${array[new]} 打印的值同樣。
看起來,就像是這兩個字符串下標關連到同一個數組元素。

實際上,它們都對應到數組元素 0。能夠看到,上面的 declare -p array 命令打印出 [0] 這個元素值變成了 2000。

查看 man bash 的 Arrays 部分,說明以下:

Indexed arrays are referenced using integers (including arithmetic expressions) and are zero-based;

An indexed array is created automatically if any variable is assigned to using the syntax name[subscript]=value.
The subscript is treated as an arithmetic expression that must evaluate to a number.

Referencing an array variable without a subscript is equivalent to referencing the array with a subscript of 0.

即,indexed array 的下標必定是數字、或者是通過算術表達式 (arithmetic expressions) 計算獲得的數字。
若是沒有提供數組下標,默認會使用數組下標 0。

因爲 bash 的算術表達式在獲取變量值時,不須要使用 $ 符號,因此上面的 array[index] 實際上至關於 array[$index],也就是獲取 index 變量的值來做爲數組下標。

若是所給的 index 變量沒有值,就至關於沒有提供數組下標,默認使用數組下標 0,因此爲 array[index] 賦值,其實是爲 array[0] 賦值。
同理,爲 array[new] 賦值,也是爲 array[0] 賦值,會看到 array[index] 的值也跟着改變。

若是 index 變量的值不是 0,並且 new 變量沒有值,那麼爲 array[index] 賦值,將不會影響到 array[new]

在上面例子的基礎上,繼續執行下面語句:

$ index=1
$ array[index]=100
$ echo "array[index] = ${array[index]}, array[1] = ${array[1]}"
array[index] = 100, array[1] = 100
$ array[new]=900
$ echo "array[new] = ${array[new]}, array[0] = ${array[0]}, array[index]=${array[index]}"
array[new] = 900, array[0] = 900, array[index]=100
$ recurse=index
$ array[recurse]=500
$ echo "array[index] = ${array[index]}, array[recurse] = ${array[recurse]}, array[1] = ${array[1]}"
array[index] = 500, array[recurse] = 500, array[1] = 500

能夠看到,將 index 變量賦值爲 1,修改 array[index] 的值,則改變的是數組下標 1 對應的元素、也就是 array[1] 的值。
即至關於用 $index 獲取該變量的值來做爲數組下標。
此時,因爲沒有爲 new 變量賦值,修改 array[new] 的值仍是關連到 array[0],不會影響到 array[index]

若是將變量賦值爲字符串,那麼會往下遞歸獲取該字符串對應的變量值。
上面將 recurse 賦值爲 "index" 字符串,修改 array[recurse] 的值,能夠看到 array[1] 的值被改變了。
即至關於先用 $recurse 獲取 recurse 變量的值是 "index",發現是字符串,繼續把 "index" 字符串做爲變量名。
$index 來獲取 index 變量的值是 1,最終使用 1 做爲數組下標。

相關文章
相關標籤/搜索