bashでtsvファイルを連想配列に入れる時にハマったこと

概要

bashtvsファイルを読み込んで連想配列に入れたい処理があり、うまくできず2時間程度ハマりました。最終的にはうまくできましたが、結構典型的なことだと思いますので、まとめてみました。

tsvファイル

例示するため、tsvファイルは以下のものを使います。

JPY<tab>日本円
USD<tab>ドル
CNY<tab>中国人民元
EUR<tab>ユーロ

このtsvファイルを詠込んで、次のような連想配列に入れる想定

currency(
["JPY"]="日本円"
["USD"]="ドル"
["CNY"]="中国人民元"
["EUR"]="ユーロ"
)

shell script

#!/usr/bin/env bash

TSV_FILE =./currency.tsv

declare -A currency
cat $TSV_FILE | while read code name; do
    echo [$code] [$name]
    currency+=([$code]=$name)
done

echo ${currency[@]}

実行結果

$ bash tsv2dict.sh
[JPY] [日本円]
[USD] [ドル]
[CNY] [中国人民元]
[EUR] [ユーロ]

あれれ、whileループ内でちゃんとtsvファイルから値を取得できたことを検証できているのに、ループを抜けt後に、値を入れたはずのcurrency連想配列には何も入っていない、なぞで仕方ありません。

原因

whileが悪いではなく、cat $TSV_FILEの結果をパイプで whiile ループに渡してたのがいけませんでした。

bashでは、パイプでデータを while ループに渡した後の処理は、子プロセス(fork)として動くため、親プロセスの変数は受け取れて使えるが、子プロセス内に変更した値が、親プロセス内には反映されないことになるため、currency連想配列は空のママになっている訳です。

参考:シェルスクリプトのwhile文の中の変数を外で使う方法 - Qiita

解決方法

パイプで標準出力を渡すのではなく、ヒアドキュメントでwhileループにデータを渡せば、whileループ内の処理も親プロセル内になるため、currency変数の値は後から引き継がれる。

while read code name; do
    echo [$code] [$name]
    currency+=([$code]=$name)
done << EOF
$(cat $TSV_FILE)
EOF

echo ${currency[@]}

実行結果

$ bash tsv2dict.sh
[JPY] [日本円]
[USD] [ドル]
[CNY] [中国人民元]
[EUR] [ユーロ]
ドル 日本円 中国人民元 ユーロ

連想配列から正しく出力できました。

参考:シェルスクリプトのwhile readループが悪いのか?回避策は? - Qiita