はじめに
LinuxのBashのスクリプトでシンプルなCSVファイルを読み込んで処理を行う際に、意外と簡単に記述する事ができるので紹介します。
・cut
コマンドを使用する方法
よく見かけるBashスクリプトでCSVファイルを読み込んで処理を行う方法として、CSVファイルを標準入力から一行ずつ読み込み、cut
コマンドを使用してカラムを変数に格納する記述があります。
#!/bin/bash
while read line
do
# $lineに読み込んだCSVファイルの一行のテキストが格納され、それをcutコマンドでカラムに分割し変数に格納
col1=$(echo ${line} | cut -d , -f 1)
col2=$(echo ${line} | cut -d , -f 2)
col3=$(echo ${line} | cut -d , -f 3)
# 処理内容をここに記載
# $colX で読み込んだCSVファイルのテキストを参照
echo "col1:$col1 col2:$col2 col3:$col3"
done < $1
$ cat hoge.csv
a1,a2,a3
b1,b2,b3
c1,c2,c3
$
$ ./hoge.sh hoge.csv
col1:a1 col2:a2 col3:a3
col1:b1 col2:b2 col3:b3
col1:c1 col2:c2 col3:c3
$
・IFSでカラムを変数へ格納する方法
区切り文字の環境変数であるIFS
を,
に変更しreadコマンドに複数の変数を設定する事で、cut
コマンドを使用せずにシンプルに記述する事ができます。
#!/bin/bash
# 読み込んだCSVファイルの一行のテキストをカラムに分割し複数の変数に格納
while IFS=, read col1 col2 col3
do
# 処理内容をここに記載
# $colX で読み込んだCSVファイルのテキストを参照
echo "col1:$col1 col2:$col2 col3:$col3"
done < $1
・IFSでカラムを配列へ格納する方法(●)
また、readコマンドの-a
オプションを使用する事で分割したカラムを配列に格納する事ができます。
#!/bin/bash
# 読み込んだCSVファイルの一行のテキストをカラムに分割し配列に格納
while IFS=, read -a col
do
# 処理内容をここに記載
# ${col[X]}で読み込んだCSVファイルのテキストを参照
echo "col1:${col[0]} col2:${col[1]} col3:${col[2]}"
done < $1
配列になっているため分割したカラムをループさせたり追加・削除・加工がしやすく、また変数展開を使用して柔軟に参照・表示する事ができるため、この方法が一番おすすめです。
#!/bin/bash
while IFS=, read -a col
do
# カラムをループで処理
for c in ${col[@]}
do
# 処理内容をここに記載
echo "loop:$c"
done
# 配列なので配列操作ができる
## 3カラム目を削除
unset col[2]
## カラムを追加
col+=(lastcol)
# カラムの参照・表示に配列・変数展開が利用できる
## 全てのカラムを表示
echo "${col[@]}"
## 2つめ以降の全てのカラムを表示
echo "${col[@]:1}"
## 先頭にcol:をつけて全てのカラムを表示
echo "${col[@]/#/col:}"
done < $1
$ ./hoge.sh hoge.csv
loop:a1
loop:a2
loop:a3
a1 a2 lastcol
a2 lastcol
col:a1 col:a2 col:lastcol
loop:b1
loop:b2
loop:b3
b1 b2 lastcol
b2 lastcol
col:b1 col:b2 col:lastcol
loop:c1
loop:c2
loop:c3
c1 c2 lastcol
c2 lastcol
col:c1 col:c2 col:lastcol
$
・スペースかタブ区切りのファイルの場合
ファイルの区切り文字がスペース区切り(SSV)かタブ区切り(TSV)の場合、区切り文字の環境変数であるIFSのデフォルトがスペース・タブ・改行のため、スクリプト内でIFSの指定をせずに処理ができます。
#!/bin/bash
while read -a col
do
echo "col1:${col[0]} col2:${col[1]} col3:${col[2]}"
done < $1
ただし、カラムが空の場合は前後の連続したスペースやタブが省略され、変数の場所が詰めて格納されてしまいます。
$ cat hoge.ssv
a1 a2 a3
b1 b3
c3
$
$ ./hoge.sh hoge.ssv
col1:a1 col2:a2 col3:a3
col1:b1 col2:b3 col3:
col1:c3 col2: col3:
$
これを防ぐためには一度スペースやタブをカンマに置換し、カンマ区切りで配列に読み込む必要があります。
#!/bin/bash
# 区切り文字をカンマに変更
IFS=,
while read line
do
# スペースをカンマに置換し、カンマが区切り文字の状態で配列を作成
col=(${line// /,})
echo "col1:${col[0]} col2:${col[1]} col3:${col[2]}"
done < $1
#!/bin/bash
# 区切り文字をカンマに変更
IFS=,
while read line
do
# タブをカンマに置換し、カンマが区切り文字の状態で配列を作成
col=(${line//$'\t'/,})
echo "col1:${col[0]} col2:${col[1]} col3:${col[2]}"
done < $1
・読み込むCSVファイルを加工する方法
CSVファイルを逆順で読み込みたい、特定の文字を置換したりしてから読み込みたいなど、CSVファイルを読み込む前に加工したい場合は標準入力にプロセス置換 <()
の結果を渡す事で実現できます。
#!/bin/bash
while IFS=, read -a col
do
echo "col1:${col[0]} col2:${col[1]} col3:${col[2]}"
done < <(tac $1)
$ cat hoge.csv
a1,a2,a3
b1,b2,b3
c1,c2,c3
$
$ ./hoge.sh hoge.csv
col1:c1 col2:c2 col3:c3
col1:b1 col2:b2 col3:b3
col1:a1 col2:a2 col3:a3
$
・awk
コマンドを使う方法
CSVファイルの簡単な加工や集計を行う場合はawk
コマンドで処理する方がシンプルに記述できる場合があります。
awk
コマンドは引数として指定したファイルを先頭から一行ずつ読み込み、区切り文字で区切られた内容を$1
,$2
...という変数に自動的に格納し、一行毎に処理したい内容を記述する事ができます。
なお、CSVファイルを処理する場合は-F
オプションで区切り文字であるカンマを指定する必要があります。
$ awk -F, '{print "col1:"$1,"col2:"$2,"col3:"$3}' hoge.csv
col1:a1 col2:a2 col3:a3
col1:b1 col2:b2 col3:b3
col1:c1 col2:c2 col3:c3
$
また、処理内容が複雑になる場合はスクリプトとしてファイルに処理内容を記述する事もできます。
{
# 処理内容をここに記載
print "col1:"$1,"col2:"$2,"col3:"$3
}
$ awk -F, -f hoge.awk hoge.csv
col1:a1 col2:a2 col3:a3
col1:b1 col2:b2 col3:b3
col1:c1 col2:c2 col3:c3
$
ファイルの区切り文字がスペース区切り(SSV)かタブ区切り(TSV)の場合、Bashスクリプトの時と同様にオプションを指定せずに読み込む事ができますが、変数の場所が詰めて格納されるため、オプションを指定する方が安全です。
$ awk -F'[. ]' '{print "col1:"$1,"col2:"$2,"col3:"$3}' hoge.ssv
col1:a1 col2:a2 col3:a3
col1:b1 col2: col3:b3
col1: col2: col3:c3
$
$ awk -F'[.\t]' '{print "col1:"$1,"col2:"$2,"col3:"$3}' hoge.tsv
col1:a1 col2:a2 col3:a3
col1:b1 col2: col3:b3
col1: col2: col3:c3
$
awk
コマンドも強力なコマンドですが、より直感的で複雑な処理が可能なperl
コマンドでCSVファイルを処理する方法もあります。
Linuxサーバーにデフォルトで入っているコマンドとしては非常に複雑な処理が可能なので、興味のある方はそちらを利用しても良いかもしれません。