C言語でCSV読込みをする方法についてです。
今回はstrtok()を使って実現する方法をご紹介します。
今回作るもの
あるCSVのデータを読込みます。
そのデータから必要なデータを検出して、ピックアップする処理を作っていきます。
サンプルデータ
こんな感じのよくあるCSVデータを用意してみました。
// test.csv KeyAAA,Name123,1.1,1.2,1.3 KeyXXX,NameAAA,2.1,2.2,2.3 KeyXXX,NameBBB,3.1,3.2,3.3 KeyZZZ,Name999,4.1,4.2,4.3
コレをファイル読込み……
と思ったけど面倒なので、読み込んだつもりで文字列から処理します。
ソース全景
ソースの全景はコチラ。長いので畳んでおきます。
表示を開く
#include <stdio.h> #include <stdlib.h> #include <string.h> #define TKEY "KeyXXX" #define TNAME1 "NameAAA" #define TNAME2 "NameBBB" #define MAX_BUF 2048 // 対象要素の取得 int getTargetVal(char* src, double* out1, double* out2, double* out3) { char buf[MAX_BUF]; char* item; int itemNo = 0; strncpy(buf, src, sizeof(buf)); item = strtok(buf, ","); // 要素不一致チェック if ( strncmp(item, TKEY, sizeof(TKEY)) != 0 ) { return 1; } item = strtok(NULL, ","); // 要素が一致チェック if ( strncmp(item, TNAME1, sizeof(TNAME1)) == 0 ) { itemNo = 1; } else if ( strncmp(item, TNAME2, sizeof(TNAME2)) == 0 ) { itemNo = 2; } // 一致要素なし if ( itemNo <= 0 ) { return 2; } item = strtok(NULL, ","); *out1 = atof(item); item = strtok(NULL, ","); *out2 = atof(item); item = strtok(NULL, ","); *out3 = atof(item); return 0; } int main(void) { char* csv[] = { "// test data", "KeyAAA,Name123,1.1,1.2,1.3", "KeyXXX,NameAAA,2.1,2.2,2.3", "KeyXXX,NameBBB,3.1,3.2,3.3", "KeyZZZ,Name999,4.1,4.2,4.3", }; int i; double d1, d2, d3; int ret; printf("TargetKey: %s\n", TKEY); printf("TargetName: %s, %s\n", TNAME1, TNAME2); for ( i = 0; i < (sizeof(csv) / sizeof(csv[0])); i++ ) { ret = getTargetVal(csv[i], &d1, &d2, &d3); if ( ret == 0 ) { printf("> %s\n", csv[i]); printf(" %lf, %lf, %lf\n", d1, d2, d3); } } return 0; }
実行すると結果はこのようになります。
TargetKey: KeyXXX TargetName: NameAAA, NameBBB > KeyXXX,NameAAA,2.1,2.2,2.3 2.100000, 2.200000, 2.300000 > KeyXXX,NameBBB,3.1,3.2,3.3 3.100000, 3.200000, 3.300000
strtok()について
strtokは、指定した文字列に対して、指定した区切り文字ごとの文字列を取得する事ができます。
使うにはまずstring.hをinclude。
#include <string.h>
そしてこんな感じで使います。
この例の場合、bufに入っている文字列に対して、「,(カンマ)」までの文字列を取得していきます。
item = strtok(buf, ","); item = strtok(NULL, ",");
strtok()使用時のイメージ
今回の例にある文字列の場合、bufに入っているのが
"KeyAAA,Name123,1.1,1.2,1.3"
という文字列をだとします。
このとき、
item = strtok(buf, ",");
とすると、itemには先頭からカンマまでの文字列である
KeyAAA
が入ります。
次に「コレ以降のカンマまでの文字を更に取りたい!」となった場合です。
第1引数に対象のバッファではなく、「NULL」を渡してあげます。
item = strtok(NULL, ",");
とすると、itemには、前回カンマがあった次の文字~その次のカンマまでの文字列である
Name123
が入ります。
同じように更に次を取りたいときは「NULL」を渡します。
item = strtok(NULL, ",");
するとitemは
1.1
が取得できます。
strtok()の利点
strtok()を使えば、CSVを分解したいときなどに、いちいち文字列の検出や操作をガリガリしなくてもいいところです。
またscanf()やfscanf()などのscanf()系関数で、フォーマットに沿って一気にデータ取得!
ということもできますが、コレよりも安全性が高いです。
よく参照しているサイトにも書かれていますが
注意!
fscanf はバッファオーバーフロー (buffer over-flow) を発生させやすい関数の 1 つです.
引用http://www.c-tipsref.com/reference/stdio/fscanf.html
のように、scanf()系関数は、エラーの温床となる可能性があります。
ものは使いようですが、strtok()のように別の関数でうまいこと作ってあげることで、リスクを減らせるかもしれませんね。
あとがき
C言語は長らく使ってきました、割と同じような関数でなんとかなっちゃったりしますよね。
なので、初めて使う関数を久しぶりに使ったので、ちょっと新鮮な気持ちでした。
思ったより便利なので、なにかあったら使ってみてください!