【実践!CSVデータ検索スクリプト作成編】条件を与えて検索結果をCSVデータで出力するスクリプト


2021/04/10

CSVデータの検索に特化したエクセルでいうところのVLOOKUP関数のような機能をもつスクリプトツールを作成してみる特集の第1回目です。

今回は読み込んだCSVデータの特定の1列を検索キーとして、指定した検索キーリストからヒットした要素を順にピックアップしてCSV形式で表示するスクリプトを作成してみます。

なお、シェルコマンドでのスクリプト作成の基本は以前の記事で集中的に取り上げていましたので、そちらの方も参考にしてください。


はじめに

当サイトではオフィス業務のComputer-Aidedなハイブリッドな方法を模索し、より効率的なExcel業務を実現したい多忙なオフィスワーカー向けの主にAwkとSedを使うシェル講座です。

シェルスクリプトはどこでもどんなOSでも基本的に使えて、しかも一度使い方を覚えると、Excelと組み合わせて最高に効率の良いオフィスワークツールが作れることでしょう。

合同会社タコスキングダム|蛸壺の技術ブログ


作成するスクリプトツールの概要

まずは例題として以下のような社員リストのCsvデータの中を検索するとします。下のコマンドでemployee.csvを作成しておきます。

            
            $ cat <<EOF > employee.csv
山下モゲ雄,営業部,本社,3年
島田フガ子,経理部,名古屋支部,15年
岡田ピポ太,製造部,山口工場,8年
佐藤ゴバ文,製造部,山口工場,11年
沢口モフ代,人事部,本社,4年
銭形ガメ吉,海外部,メキシコ支部,11年
上岡ムメ美,営業部,本社,23年
京谷マハ次,製造部,山口工場,3年
園田フマ由,人事部,本社,17年
山田ケム紀,営業部,本社,15年
田川ポゥ子,製造部,ベトナム工場,12年
満田クタ郎,営業部,本社,2年
島寺ルン大,営業部,本社,18年
香下ウル蔵,製造部,山口工場,5年
田頭モフ雄,営業部,名古屋支部,16年
蒲田ウオ奈,海外部,メキシコ支部,9年
郷田ポポ生,営業部,名古屋支部,4年
梅岡ボル伍,経理部,本社,25年
亀川ヲル士,製造部,山口工場,14年
EOF
        
例えば社員の名前の一部を検索キーとしてキーワードをリストとして引数に与えて、CSVデータから該当の行を順番に引っこ抜くようなツールスクリプトが今回作成したいスクリプトとします。コマンド実行のイメージで言うと以下のような感じです。

            
            $ ./simple_finder.sh -l '山田','佐藤','モフ雄' employee.csv
山田の項目...
佐藤の項目...
モフ雄の項目...
        
なお、検索した場合に重複をどう取り扱うか、と言う課題も当然出てくると思います。例えば、このスクリプトで言うと佐藤Aさんと佐藤Bさんがデータとして同時に存在する場合にどう取り扱うかetc..

今回の記事ではCSVデータに重複がないことを想定していますが、重複のあるケースは後日解説していく予定です。


スクリプトツールの雛形作成

まずは今回のスクリプトツール作成に当たって、コマンドに外部から引数を与えられるようにしておきます。

本記事で作成するスクリプトの実装例として、以前
こちらで解説したgetoptsで引数を受ける方法を利用します。

今回は以下のようにスクリプトの概形を仕立て直しておきます。

            
            #!/bin/bash

usage_exit() {
    echo "USAGE: $(basename $0) [-l key_list] [-h help] [input_file]" 1>&2
    exit 1
}

noarg_err() {
    echo "ERROR: must provide key_list!" 1>&2
    exit 1
}

noinputfile_err() {
    echo "ERROR: not allowed input file to be empty!" 1>&2
    exit 1
}

while getopts l: OPT; do
    case $OPT in
        l ) KEY_LIST="$OPTARG"
            ;;
        \? ) usage_exit
            ;;
    esac
done

shift $((OPTIND - 1))

if [ -z "$KEY_LIST" ]; then
    noarg_err
fi

if [ -z "$1" ]; then
    noinputfile_err
fi

echo "FILE: $1, KEY_LIST: ${KEY_LIST}"
        
一旦ここでこのスクリプトが引数を受けて動作するかをsimple_finder.shと名付けて保存し、以下のように実行してみます。

            
            $ chmod +x simple_finder.sh
$ ./simple_finder.sh -l '山田','佐藤','モフ雄' employee.csv
FILE: employee.csv, KEY_LIST: 山田,佐藤,モフ雄
        

Awkによる検索メソッドの実装

では上記のスクリプトの雛形にAwkを使った検索関数を作成していきます。

Awkでの文字列検索の基礎は、match関数の使ったやり方では以下のリンクの記事で解説していました。

またmatch関数ではなくてもAwkでは強力な正規表現による判定も使えることを以下の記事でも解説していました。

今回のケースでは正規表現のほうがmatch関数よりもお手軽に使えるので、こちらのテクニックでコーディングしてみます。

では先程のスクリプトに以下のように追加編集してみます。

            
            #!/bin/bash
#....
#....中略
#....
echo "FILE: $1, KEY_LIST: ${KEY_LIST}"

#👇①...☆ここから追加
PARR=($(echo "${KEY_LIST}" | tr ',' ' '))
for p in "${PARR[@]}"; do
    awk -F"," '
        $1 ~ /'"$p"'/ { print $0 }
    ' $1
done
        
このスクリプトの作成ポイントは、キーリストを配列化してfor...inループで回すことと、Awkスクリプトの部分を複数のシングルクォーテーションで区切って、その合間でダブルクオーテーションでシェルコマンド呼び出しを挟んでいることです。

awkスクリプト外部から変数を注入する方法は他に、-vオプションを使ったり、getline処理で外部コマンドからの処理を挟んだりすることもできます。

これを実行すると、

            
            $ ./simple_finder.sh -l 山田,佐藤,モフ雄 employee.csv
FILE: employee.csv, KEY_LIST: 山田,佐藤,モフ雄
山田ケム紀,営業部,本社,15年
佐藤ゴバ文,製造部,山口工場,11年
田頭モフ雄,営業部,名古屋支部,16年
        
のように検索結果がCSV形式で得られました。


## 検索に引っかからない時の措置~#N/Aで埋めてみる

先程作成したスクリプトではリストのキーワードで探せなかった場合には何も起こらずにスキップされてしまいます。

            
            $ ./simple_finder.sh -l 山田,佐川,モフ雄 employee.csv
FILE: employee.csv, KEY_LIST: 山田,佐川,モフ雄
山田ケム紀,営業部,本社,15年
田頭モフ雄,営業部,名古屋支部,16年
        
検索結果が無い場合にも何らかのレスポンスが欲しい場合があります。

以前、Excel風にエラーが発生した時に出る
#N/Aを処理できなかったときのレスポンス値として、シェルスクリプトでも取り扱う方法を議論したことがありました。

ということでここでも同じように、先程のスクリプトを再度以下のようにちょこっと編集します。

            
            #!/bin/bash
#....
#....中略
#....
echo "FILE: $1, KEY_LIST: ${KEY_LIST}"

#👇①...☆Awkスクリプト部分を編集
PARR=($(echo "${KEY_LIST}" | tr ',' ' '))
for p in "${PARR[@]}"; do
    awk -F"," '
        $1 ~ /'"$p"'/ { last_macthed = $0; }
        END { print last_macthed ? last_macthed : "'"$p"'?,#N/A,#N/A,#N/A"; }
    ' $1
done
        
修正のポイントは検索でマッチした場合の結果を保持させておいて、ENDアクションブロックでその結果を表示させるように変更しただけです。

もし検索結果が無い場合には、各セルを#N/A値で埋めたレスポンス行を生成して返すようにしています。

この修正したスクリプトを先程と同様に実行すると、

            
            $ ./simple_finder.sh -l 山田,佐川,モフ雄 employee.csv
FILE: employee.csv, KEY_LIST: 山田,佐川,モフ雄
山田ケム紀,営業部,本社,15年
佐川?,#N/A,#N/A,#N/A
田頭モフ雄,営業部,名古屋支部,16年
        
となって、検索できなかったキーが明瞭に分かると思います。


まとめ

以上、コマンド形式でキーワードによるCSVデータのリスト検索を行うスクリプトツールの大まかな作成方法をAwkベースで作成してみました。

今回のスクリプトツールはかなりシンプルですので、検索する要素の重複などは一切考慮していませんでしたので、次回以降で重複させる・させないの挙動を制御させるような修正をネタに記事を更新していこうと思います。
記事を書いた人

記事の担当:taconocat

ナンデモ系エンジニア

主にAngularでフロントエンド開発することが多いです。 開発環境はLinuxメインで進めているので、シェルコマンドも多用しております。 コツコツとプログラミングするのが好きな人間です。