1. 程式人生 > >bash內建命令mapfile:讀取檔案內容到陣列

bash內建命令mapfile:讀取檔案內容到陣列

bash提供了兩個內建命令:readarray和mapfile,它們是同義詞。它們的作用是從標準輸入讀取一行行的資料,然後每一行都賦值給一個數組的各元素。顯然,在shell程式設計中更常用的是從檔案、從管道讀取,不過也可以從檔案描述符中讀取資料。

需要先說明的是,shell並不像其它專門的程式語言對陣列、列表提供了大量的操作工具,反而直接操作文字檔案更為常見(sed、awk等),所以mapfile用的並不多。

1.語法

mapfile [OPTIONS] ARRAY
readarray [OPTIONS] ARRAY

其中options:
-O INDEX   :指定從哪個索引號開始儲存資料,預設儲存資料的起始索引號為0
-n count   :最多隻拷貝多少行到陣列中,如果count=0,則拷貝所有行
-s count   :忽略前count行不讀取
-c NUM     :每讀取NUM行就呼叫一次"-C callback"選項指定的callback程式
-C callback:每讀取"-c NUM"選項指定的NUM行就執行一次callback回撥程式
-d string  :指定讀取資料時的行分隔符,預設是換行符
-t         :移除尾隨行分隔符,預設是換行符
-u fd      :指定從檔案描述符fd而非標準輸入中讀取資料
  • 如果不指定ARRAY引數,則預設使用陣列MAPFILE
  • 如果不指定"-O"選項,則在儲存資料之前先清空陣列(如果該陣列已存在)
  • 給定了"-C callback"卻沒有給定"-c NUM"時,則預設為每5000行呼叫一次回撥程式
  • 回撥程式是在讀取給定行數之後,賦值到陣列元素之前執行的。所以流程為:"讀NUM行-->callback-->賦值"
  • 每次呼叫回撥函式時,都將呼叫callback之前的最後一行資料及其對應的索引號作為回撥程式的引數。例如-c 3 -C callback,則會將索引號2和第3行內容,索引號5和第6行內容作為callback程式的引數
  • "-t"去除行尾分隔符,一般來說都是換行符。用其他語言程式設計過的人都知道行尾換行符有多煩心,但對於shell程式設計來說,倒是無所謂

2.幾個示例和注意事項

先建立一個示例用的檔案alpha.log,每行一個小寫字母,共26行:

$ echo {a..z} | tr " " "\n" >alpha.log
$ cat alpha.log
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
z

讀取該檔案並將每一行儲存到陣列myarr中(如果不指定,則儲存到預設的MAPFILE陣列中)。

$ mapfile myarr <alpha.log
$ echo ${myarr[@]}
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 z
$ echo ${myarr[2]}
c

既然是讀取標準輸入,常見的就有以下幾種讀取形式:

$ mapfile myarr <alpha.log            # 1.輸入重定向
$ mapfile myarr < <(cat alpha.log)    # 2.程序替換
$ cat alpha.log | mapfile myarr       # 3.管道傳遞

第1、2種寫法沒什麼問題,但第3種寫法是有問題的。

$ cat alpha.log | mapfile myarr1
$ echo ${#myarr1[@]}
0

從結果中可以看到,myarr1根本就不存在。為什麼?我在shell中while迴圈的陷阱中給出過解釋。這裡簡單說明一下,對於管道組合的多個命令,它們都會放進同一個程序組中,會進入子shell執行相關操作。當執行完畢後,程序組結束,子shell退出。而子shell中設定的環境是不會粘滯到父shell中的(即不會影響父shell),所以myarr1陣列是子shell中的陣列,回到父shell就消失了。

解決方法是在子shell中運算元組:

$ cat alpha.log | { mapfile myarr1;echo ${myarr1[@]}; }

mapfile可以指定每讀取多少行就執行一次的回撥函式,並且會將執行回撥函式時讀取的最後一行和對應的索引號傳遞給回撥函式作為它額外的引數。

一個簡單的示例,每讀取3行就執行一次echo,注意看下面傳遞給給echo的引數值。

$ mapfile -c 3 -C "echo" myarr <alpha.log
2 c

5 f

8 i

11 l

14 o

17 r

20 u

23 x

這裡的echo就是回撥函式。輸出結果中每執行一次就有一空行,這是因為檔案中資料是分行的,而echo又自帶換行功能。所以,可以使用"-t"選項,在每次讀取一行後就去掉該行的換行符。

$ mapfile -t -c 3 -C "echo" myarr <alpha.log
2 c
5 f
8 i
11 l
14 o
17 r
20 u
23 x

可以寫一個指令碼,或者定義一個函式作為回撥程式,實現更復雜的功能,但一定要注意,mapfile傳遞給callback的兩個引數總是最後兩個引數。例如:

$ myecho(){ echo [email protected]; };mapfile -t -c 3 -C "myecho haha" myarr <alpha.log
haha 2 c
haha 5 f
haha 8 i
haha 11 l
haha 14 o
haha 17 r
haha 20 u
haha 23 x

還可以將多個操作組合起來作為一個回撥程式:

$ mapfile -t -c 3 -C "echo haha;echo" myarr<alpha.log
haha
2 c
haha
5 f
haha
8 i
haha
11 l
haha
14 o
haha
17 r
haha
20 u
haha
23 x