mapfile(マップファイル)
bash シェルの mapfile
コマンドは、読み取り配列としてよく知られています。
主な目的は、標準入力行を読み取り、それらをインデックス付き配列変数に格納することです。
mapfile
は、パイプではなく置換 (<) から読み取る必要があります。
さらに、読み取りループと比較して、mapfile
ははるかに高速で便利なソリューションです。
コマンドの実行が成功した場合は 1 を返し、失敗した場合は 0 を返します。
配列名を指定しない場合、mapfile
変数がデフォルトの配列変数となります。
データテキストの準備
ここで簡単なデータテキスト用意します。
One
Two
Three
普通のやり方 while read パターン
このテキストを読み込んで配列にデータを格納したいと思います。
多少冗長ではありますが通常は以下のような感じになります。
#!/usr/bin/bash
DATAFILE="data.txt"; # データファイル
declare -a aLine; # 配列の宣言
declare -i COUNT=0; # カウンターの宣言
IFS=$'\n'; # 区切り文字を改行コードに指定
while read line;do
# 1行ずつ読み込んだ内容 $line を配列に代入
aLine[$COUNT]="$line";
((COUNT++)); # インクリメント
done<$DATAFILE # ファイルの入力
echo "配列の内容すべてを表示"
echo ${aLine[@]}; # One Two Three
echo "添字の0を表示"
echo ${aLine[0]}; # One
echo "添字の1を表示"
echo ${aLine[1]}; # Two
echo "添字の2を表示"
echo ${aLine[2]}; # Three
実行結果は以下のとおりです。
bash-3.2$ bash array01.sh
配列の内容すべてを表示
One Two Three
添字の0を表示
One
添字の1を表示
Two
添字の2を表示
Three
bash-3.2$
普通のやり方 fileコマンドパターン
ファイルの読み込みを以下のようにすることもできますね。
#!/usr/bin/bash
DATAFILE="data.txt"; # データファイル
declare -a aLine; # 配列の宣言
declare -i COUNT=0; # カウンターの宣言
IFS=$'\n'; # 区切り文字を改行コードに指定
# ファイルを配列に読み込む
file=(`cat "$DATAFILE"`)
# 行ごとに繰り返し処理を実行
for line in "${file[@]}"; do
# 1行ずつ読み込んだ内容 $line を配列に代入
aLine[$COUNT]="$line";
((COUNT++)); # インクリメント
done
echo "配列の内容すべてを表示"
echo ${aLine[@]}; # One Two Three
echo "添字の0を表示"
echo ${aLine[0]}; # One
echo "添字の1を表示"
echo ${aLine[1]}; # Two
echo "添字の2を表示"
echo ${aLine[2]}; # Three
実行結果は以下のとおりです。
bash-3.2$ bash array02.sh
配列の内容すべてを表示
One Two Three
添字の0を表示
One
添字の1を表示
Two
添字の2を表示
Three
bash-3.2$
登場! mapfile を使う
なんと、ファイル読み込みや配列への代入にあれこれやっていましたが、mapfileを使うと1行で住みます。
COUNT
変数といったカウンターや IFSといった定義も不要です。
One Two Three
Four Five Six
Seven Eight Nine
Ten
#!/usr/bin/bash
DATAFILE="data.txt"; # データファイル
declare -a aLine; # 配列の宣言
# -t は行末の改行を除去
mapfile -t aLine < "$DATAFILE";
echo "配列の内容すべてを表示"
echo ${aLine[@]}; # One Two Three
echo "添字の0を表示"
echo ${aLine[0]}; # One
echo "添字の1を表示"
echo ${aLine[1]}; # Two
echo "添字の2を表示"
echo ${aLine[2]}; # Three
実行結果はいずれも同じですが以下のとおりです。
bash-3.2$ bash array03.sh
配列の内容すべてを表示
One Two Three
添字の0を表示
One
添字の1を表示
Two
添字の2を表示
Three
bash-3.2$
すごいですね。
配列に入れるだけならmapfile
で十分です。しかも読み込み専用ということもあり、読み込み速度は通常の数十倍高速です。
列の代入
ここで余談ですが、これまでは行の読み込みを行い、行を単位に配列に格納してきました。
列の中で空白区切りで値が入っている場合の配列への代入はどうしましょう?
こうなります。
#!/usr/bin/bash
# 1行に3つの値が空白区切りで並んでいます
read -a aLine <<< "One Two Three"
echo "配列の内容すべてを表示"
echo ${aLine[@]}; # One Two Three
echo "添字の0を表示"
echo ${aLine[0]}; # One
echo "添字の1を表示"
echo ${aLine[1]}; # Two
echo "添字の2を表示"
echo ${aLine[2]}; # Three
実行結果は以下のとおりです。
bash-3.2$ bash col.sh
配列の内容すべてを表示
One Two Three
添字の0を表示
One
添字の1を表示
Two
添字の2を表示
Three
bash-3.2$
データファイルからの入力
では、データファイルの構造を少し複雑にしてみます。
One Two Three
Four Five Six
Seven Eight Nine
Ten
mapfile
コマンドで行の内容を配列に入れる方法は説明しましたが、今回は、行の中で空白区切りの値が3つあります。
こうしたデータ構造をmapfile
に加えてread
コマンドを使って効率的に、かつ高速に読み込んでみます。
#!/usr/bin/bash
DATAFILE="data02.txt";
declare -a aLine;
# データファイルを読み込みます。
mapfile -t aLine<"$DATAFILE";
for((i=0;i<4;i++));do
# 行の内容を読み込み、空白区切りで配列に格納します
read -a var <<< "${aLine[$i]}";
echo "varの中身は以下の通り";
echo "${var[@]}";
echo "添字の0を表示"
echo ${var[0]}; # One
echo "添字の1を表示"
echo ${var[1]}; # Two
echo "添字の2を表示"
echo ${var[2]}; # Three
done
実行結果は以下のとおりです。
bash-3.2$ bash colArray.sh
varの中身は以下の通り
One Two Three
添字の0を表示
One
添字の1を表示
Two
添字の2を表示
Three
varの中身は以下の通り
Four Five Six
添字の0を表示
Four
添字の1を表示
Five
添字の2を表示
Six
varの中身は以下の通り
Seven Eight Nine
添字の0を表示
Seven
添字の1を表示
Eight
添字の2を表示
Nine
varの中身は以下の通り
Ten
添字の0を表示
Ten
添字の1を表示
添字の2を表示
bash-3.2$
最後の行は値が一つしかありません(Ten)
必要であれば値がない場合は出力しないなどの処理をすれば良さそうです。
(配列的には別に値がなくても問題はないと思いますが)
mapfile
とread
コマンドを上手に使って、効率的に配列に代入してください。