プロが教える店舗&オフィスのセキュリティ対策術

ファイル内容
********************
あいう,,さしす
たちつ,なにぬ,はひふ
まみむ,,
あいう,win,
********************

#include <stdio.h>
#include <string.h>

#define MBF 256

struct tb{
char aaa[32];
char bbb[32];
char ccc[32];
};

int main(){
struct tb tbl[20];
struct tb *tp;
int ntb,itb;
FILE* fi;
FILE* fo;
char buff[MBF];

// 入力
fi = fopen("sample.csv","r"); // 検査省略
if( fi == NULL ){
printf( "%sファイルが開けません\n" );
return -1;
}
ntb = 0;
while ( fgets(buff,MBF,fi ) != NULL ) {
strcpy(tbl[ntb].aaa,strtok(buff,","));
strcpy(tbl[ntb].bbb,strtok(NULL,","));
strcpy(tbl[ntb].ccc,strtok(NULL,","));
ntb++;
}
fclose( fi );
// 出力
fo = fopen("csvo.csv","w");
if( fo == NULL ){
printf( "%sファイルが開けません\n" );
return -1;
}
for ( itb=0;itb<ntb;itb++ ) {
tp = tbl+itb;
fprintf(fo,"%s%s%s",tp->aaa,tp->bbb,tp->ccc);
}
fclose( fo );
return 0;
}

csvファイルないようが以下であれば格納できるけど、すごく困ってます。
********************
あいう,かきく,さしす
たちつ,なにぬ,はひふ
まみむ,やゆよ,らりる
********************

A 回答 (9件)

FarEyesです。



すみません、#8の下記部分において訂正があります。

> この静的な作業領域は、共通なエリアですので、他の処理(マルチスレッド
> などで並列処理を行っている場合も含めて)で、strtok関数が呼ばれてしまう
> と、今まで保持していた文字列が、書き換えられてしまいます。
> その結果、意図しない結果が発生する可能性があります。

上記文中の、
  (マルチスレッドなどで並列処理を行っている場合も含めて)
の部分ですが、処理系によっては、
  strtok関数などの静的作業領域を使用する関数において、
  『これらの関数を同時に複数のスレッドから呼び出すことによって
  障害が発生することはありません。』
のように謳われている処理系も存在します。
ですので、
  (マルチスレッドなどで並列処理を行っている場合も含めて)
の部分は無視して下さい。

どうも、申し訳ありませんでした。
    • good
    • 0

こんにちは。

 FarEyesです。

===========
1)まず、先に「質問2」についての回答です。

当方でも、質問者さんと同様な箇所に、デバッグ用のprintf文を追加して、
かつ、CSVファイルも同様に以下のもの、
  1,,さしす
  2,a,b
  3,c,d
を使用して検証してみました。

結果は、質問者さんの検証結果と同じ結果となりました。

結論から申し上げると、これは「正常動作」となります。

自分でも、最初ちょっと戸惑いましたが、デバッガによりステップ実行しながら、
検証したみた過程で、正常動作だと気づきました。

printfの出力結果だけを見た場合、同じ状態が2度以上繰り返されているように
見えますが、これは、
  strtok2関数の「1回」の呼び出しで、繰り返されている
訳ではありません。
これは、
  strtok2関数の「複数回」の呼び出しの結果として、繰り返されている
  「ように見える」
ということです。

言葉で説明すると解り辛いので、以下の検証を行ってみて下さい。

■検証操作
デバッグ用に挿入したprintf文はそのままにしておいて、main関数の下記部分、

  <変更前>
    /* 区切り文字(カンマ、改行)で区切って各列の文字列を取得 */
    strtok2( buff, swk, ",\n", 1, 32 );        /*1列目*/
    strtok2( buff, tbl[ntb].bbb, ",\n", 2, 32 );  /*2列目*/
    strtok2( buff, tbl[ntb].ccc, ",\n", 3, 32 );  /*3列目*/

を、以下のように変更したのち、再ビルド&実行してみて下さい。

  <変更後>
    /* 区切り文字(カンマ、改行)で区切って各列の文字列を取得 */
    printf( "== %d行目、%d列目 ==\n", (ntb+1), 1 );
    strtok2( buff, swk, ",\n", 1, 32 );        /*1列目*/
    printf( "== %d行目、%d列目 ==\n", (ntb+1), 2 );
    strtok2( buff, tbl[ntb].bbb, ",\n", 2, 32 );  /*2列目*/
    printf( "== %d行目、%d列目 ==\n", (ntb+1), 3 );
    strtok2( buff, tbl[ntb].ccc, ",\n", 3, 32 );  /*3列目*/

以下は、上記変更後のプログラムの実行結果(コンソールへの出力ログ)です。

== 1行目、1列目 ==
p2=,,さしす
== 1行目、2列目 ==
p2=,,さしす
p2=,さしす
== 1行目、3列目 ==
p2=,,さしす
p2=,さしす
p2=
== 2行目、1列目 ==
p2=,a,b
== 2行目、2列目 ==
p2=,a,b
p2=,b
== 2行目、3列目 ==
p2=,a,b
p2=,b
p2=
== 3行目、1列目 ==
p2=,c,d
== 3行目、2列目 ==
p2=,c,d
p2=,d
== 3行目、3列目 ==
p2=,c,d
p2=,d
p2=

これを見ると、関数の1回の呼び出しで、重複している部分は無いことが解る
と思います。

今回のご質問での、最初のオリジナルソースの下記部分、

  strcpy(tbl[ntb].aaa,strtok(buff,","));
  strcpy(tbl[ntb].bbb,strtok(NULL,","));
  strcpy(tbl[ntb].ccc,strtok(NULL,","));

でのC言語ライブラリのstrtok関数の処理と比較すると、今回のstrtok2関数
では、無駄な処理が入っているように思えますが、それは下記に述べる現象
を回避するためです。

strtok関数は、ライブラリ内部に、静的な作業領域(ワークバッファ)を持って
いて、そのバッファに引数で渡された文字列を格納して、その文字列を書き
換えながら、部分文字列(正確には、ワークバッファ上の部分文字列への
ポインタ)を戻り値として返すようになっています。

この静的な作業領域は、共通なエリアですので、他の処理(マルチスレッド
などで並列処理を行っている場合も含めて)で、strtok関数が呼ばれてしまう
と、今まで保持していた文字列が、書き換えられてしまいます。
その結果、意図しない結果が発生する可能性があります。

今回のstrtok2関数では、上記のような静的な作業領域は設けず、常に引数
で渡された文字列の先頭から、指定位置(列位置)の部分文字列を取り出す
ような処理を行っています。

===========
2)質問1について

> p2 = strpbrk( (const char*)p1, (const char*)strSep );
>     if( p2 ){
> このif文の条件のところp2って記入されたけど理解できてないです。
> p2が条件?なんの条件ですか?

strpbrk関数の結果、「区切り文字」が、
  見つかった場合     → 戻り値が、見つかった位置のポインタで返される
                   (= NULLではない → = 0 ではない)
  見つからなかった場合 → 戻り値が、NULLで返される
                   (= NULL → 数値で表すと = 0 )
  (注:処理系により異なるかもしれません)、
ということになります。
その結果、p2 の値により、if文の条件が、
  p2 != NULL  → (p2 != 0)  → if( 0以外 ) → 「真」
  p2 = NULL  →  (p2 = 0)  → if( 0 )    → 「偽」
ということになります。

===========
3)質問3について

> usize = (p2 - p1) / sizeof(char);
> この行の意味もちょっと説明お願いします。

これは、usize に「部分文字列」の「文字数」をセットするための計算を行って
います。
  p1 = 文字列の先頭位置のポインタ
  p2 = 文字列中の最初に見つかった「区切り文字」位置のポインタ
これから、
  p2 - p1
として、「部分文字列」の「バイト数」が得られます。
そして、「バイト数」から「文字数」に変換するために、
  / sizeof(char)
として、「文字数」を計算しています。
※これは、処理系により、char型のサイズが異なる可能性を考慮して行って
 います。

===========
4)その他

> ソースみてもカンマの処理まで理解できないところあります。
> 日本語で箇条書きしていただけませんでしょうか???

申し訳ありませんが、そこまでするサービス精神は、私にはありません、
これは、質問者さんご自身で、解析なさって下さい。
※人から教えてもらうだけだと、身に付かないと思います。
※あと、今回の内容でおおよその部分は、ご理解戴けるかと思います。
※もしも、お気に触られた場合は、申し訳ありません。
    • good
    • 1
この回答へのお礼

FarEyesさん:
本当にありがとうございました。
質問ちゃんと対応してくださって本当にうれしかったです。

カンマ処理するstrtok2関数についてのソースコードは理解できるまでがんばっていきたいです。
また よろしくお願いいたします。

お礼日時:2009/11/20 22:52

こんばんは。

 FarEyesです。

お礼をいただき有り難うございます。
問題が解決されたのであれば、こちらも嬉しく思います。

一応、疑問符"?"で、ご返信戴いたので、こちらも返信させて戴きました。

> また分からないところありましたら質問していいですか?

何時でもどうぞ。(ここは、そのための場所ですので。。。)

なお、今回の件に直接関係ない御質問等であれば、新たに質問スレッドを
立てられた方が良いかと思います。
(すみません。御解りかと思いますが、念のため。。。)

この回答への補足

for(icnt=1; icnt<=nPos && *p1!='\0'; icnt++)
  {
    /* 区切り文字の位置取得 */
    /* ※ポインタで返される。なければNULL */
    p2 = strpbrk( (const char*)p1, (const char*)strSep );
    if( p2 ){
      /* 区切り文字あり */
      if( icnt==nPos ){
        /* 指定の区切り位置*/
        usize = (p2 - p1) / sizeof(char);
        if( usize >= nSize ) usize = (nSize - 1);
        if( usize > 0 ){
          /* 部分文字列あり */
          strncpy( strDest, p1, usize );
          strDest[usize] = '\0';
          ians = 1;
        }
        else{
          /* 部分文字列なし */
          strDest[0] = '\0';
          ians = 0;
        }
      }
      p1 = p2+1;  /* 参照元の文字列ポインタを1列分進める */
    }
    else{
      /* 区切り文字なし */
      if( icnt==nPos ){
        /* 指定の区切り位置 */
        usize = strlen( p1 );
        if( usize >= nSize ) usize = (nSize - 1);
        if( usize > 0 ){
          /* 部分文字列あり */
          strncpy( strDest, p1, usize );
          strDest[usize] = '\0';
          ians = 1;
        }
        else{
          /* 部分文字列なし */
          strDest[0] = '\0';
          ians = 0;
        }
      }
      else{
        /* 指定の区切り位置でない */
        strDest[0] = '\0';
        ians = 0;
      }
      break;
    }
  }

  return ians;
}

FarEyesさん、上の処理が分からない箇所がありましてm(_ _)m
また貴重な時間を~~~~~~~~~~m(_ _)m !


質問1;
p2 = strpbrk( (const char*)p1, (const char*)strSep );
    if( p2 ){
このif文の条件のところp2って記入されたけど理解できてないです。
p2が条件?なんの条件ですか?

質問2;
p2 = strpbrk( (const char*)p1, (const char*)strSep );
下に printf("p2=%s",p2); を記入して
p2の値を出力してみました、
ファイル内容が
1,,さしす
2,a,b
3,c,d
の場合:

p2=,,さしす
p2=,,さしす
p2=,さしす
p2=,,さしす   
p2=,さしす
p2=
p2=,a,b
p2=,a,b
p2=,b
p2=,a,b
p2=,b
p2=
p2=,c,d
p2=,c,d
p2=,d
p2=,c,d
p2=,d
p2=
いうふうに表示されます。
無駄な処理があるということでしょうか?
実は以下になるようにするんですかね?
それとも私の理解まちがいですか?

p2=,,さしす   
p2=,さしす
p2=
p2=,a,b
p2=,b
p2=
p2=,c,d
p2=,d
p2=




質問3;
usize = (p2 - p1) / sizeof(char);
この行の意味もちょっと説明お願いします。


ソースみてもカンマの処理まで理解できないところあります。
日本語で箇条書きしていただけませんでしょうか???
本当に失礼いたします。

補足日時:2009/11/19 22:56
    • good
    • 0

こんにちは。

 FarEyesです。

■補足1
> if( usize >= nSize ) usize = (nSize - 1);
> if( usize >= nSize ) usize = (nSize - 1);
> 上の2箇所警告が出ます、
> 警告 W8012 1116.cpp 126: 符号付き値と符号なし値の比較(関数 strtok2(char *,char *,char *,int,int) )
> なぜでしょうか?

申し訳ありません。当方の検証不足でした。
「警告」の理由は、表示出力されたメッセージのとおり、
  「符号付き値と符号なし値の比較」 → ( usize >= nSize ) の部分です。
を行っているからです。

今回は、usize が符号なし(unsigned long)、nSize が符号付き(int)となっていますが、
実行時の値を比較する時点、

  if( usize >= nSize ) usize = (nSize - 1);

では、両方の値とも必ず 0 以上の値になっているので、処理上は問題なく実行される
と思います。
しかしながら、厳密には型を合わせるのが基本ですので、正しくは、例えば、

  if( usize >= (unsigned long)nSize ) usize = ((unsigned long)nSize - 1);

のようにキャストするなどの記述をすべきでしたね。(すみませんでした。)

■補足2
> 上の処理を消しても正常に動いてます。ちょっとわかりませんので教えていただけますか?

それは、実行時に以下のif文が「真」になる条件、
  if( usize >= nSize ) usize = (nSize - 1);
( usize >= nSize )の状態になること( → usize = (nSize - 1); が実行される条件 )が、
発生しないために、この文をとっても処理が変わらなかったためだと思われます。

ちなみに、( usize >= nSize )が真になる条件とは、
  入力CSVファイル上の、カンマ区切りの「部分文字列」の文字数(=usize)が、
  それを格納する引数で指定された文字列バッファの指定サイズ(=nSize)を、
  オーバーしてしまうとき。
となります。
ですので、このif文を入れずに、上記の状態になってしまった場合は、格納先の
文字列バッファの確保領域を越えて、文字列がコピーされてしまうことになります。
このif文は、それを防止するために入れてあります。

■補足3
> もし
> ファイル内容が
> 1,あいうえお、かきく
> 2,,
> 3,,さしす
> で 以下の構造体に格納したい場合どこを直したらいいですか?
> どこでint型に変換するんですか?

> struct tb{
> int aaa[32];
> char bbb[32];
> char ccc[32];
> };

ひとつ確認があります。
  struct tb{
   int aaa[32];
   char bbb[32];
   char ccc[32];
  };
の記述は、
  struct tb{
   int aaa;
   char bbb[32];
   char ccc[32];
  };
とするのが正しいのではないでしょうか?
※CSVデータの1列目のみ「数値」として扱うのであれば、構造体のメンバには、
 対応する数値変数を1個、用意すればよく、配列にする必要はないと思われ
 ます。

上記で後者の方の構造体の場合だったとして、以下は修正案の一例です。

■修正案
1)まず、以下のようにヘッダのインクルードと、main関数に作業用のchar型配列
  と、char型ポインタを、

  <追加部分>
     :
  #include <stdlib.h>  /* strtol関数のためのインクルード */
     :
  /*== main ==*/
  int main(int argc, char *argv[])
  {
     :
    char swk[32];    /* 作業用の文字列バッファ */
    char *p1;      /* 作業用のchar型ポインタ */
     :

 のように追加しておきます。

2)次に、main関数のCSVデータ取り込み部分の

  <変更前>
     :
  /* 取得文字列のバッファを始めにクリア */
  tbl[ntb].aaa[0] = '\0';
  tbl[ntb].bbb[0] = '\0';
  tbl[ntb].ccc[0] = '\0';

  /* 区切り文字(カンマ、改行)で区切って各列の文字列を取得 */
  strtok2( buff, tbl[ntb].aaa, ",\n", 1, TBF );  /*1列目*/
  strtok2( buff, tbl[ntb].bbb, ",\n", 2, TBF );  /*2列目*/
  strtok2( buff, tbl[ntb].ccc, ",\n", 3, TBF );  /*3列目*/
     :

 の部分を、

  <変更後>
     :
  /* 取得文字列のバッファを始めにクリア */
  tbl[ntb].aaa = 0;
  tbl[ntb].bbb[0] = '\0';
  tbl[ntb].ccc[0] = '\0';

  /* 区切り文字(カンマ、改行)で区切って各列の文字列を取得 */
  strtok2( buff, swk, ",\n", 1, 32 );      /*1列目*/
  strtok2( buff, tbl[ntb].bbb, ",\n", 2, 32 );  /*2列目*/
  strtok2( buff, tbl[ntb].ccc, ",\n", 3, 32 );  /*3列目*/
  /* 1列目の数字文字列を整数値に変換して構造体メンバに格納 */
  tbl[ntb].aaa = strtol( (const char*)swk, &p1, 10 );
     :

 のように変更します。
 注)ただし、数字文字列の正当性チェック&エラー処理、
    →文字列が数値として解釈できない文字列だった場合の処理
   は省略しています。

3)次に、同じくmain関数の出力部分の

  <変更前>
     :
  for ( itb=0; itb<ntb; itb++ ) {  /*取得行数分ループ*/
    tp = tbl + itb;        /*出力対象の構造体のポインタ設定*/
                  /*1行分を出力*/
    fprintf( fo, "%s,%s,%s\n", tp->aaa, tp->bbb, tp->ccc );
  }
     :

 の部分を、

  <変更後>
     :
  for ( itb=0; itb<ntb; itb++ ) {  /*取得行数分ループ*/
    tp = tbl + itb;        /*出力対象の構造体のポインタ設定*/
                  /*1行分を出力*/
    fprintf( fo, "%d,%s,%s\n", tp->aaa, tp->bbb, tp->ccc );
  }
     :

 のように変更すれば良いと思います。

■補足(コンパイル時の警告について)
 Borland系のコンパイラーを使用して、今回のサンプル(#3、及び、今回変更後のソース)
 のコンパイルを行った場合、
   警告 W8004 tcsv21.c 148: 'p2' に代入した値は使われていない(関数 strtok2 )
 のような「警告」が出る場合があります。
 ※この「警告」は実行上、問題はありません。

 この「警告」を出したくない場合は、コンパイル時に、
   bcc32 -w-8004 tcsv21.c
 のように、オプション指定で該当の「warning」を無視する設定でコンパイルを行って
 下さい。

以上です。
    • good
    • 0
この回答へのお礼

FarEyesさん:
 有り難うございました!
 本当にうれしいです。

struct tb{
   int aaa[32];
   char bbb[32];
   char ccc[32];
  };
上は私の間違いです。int aaa;  が正解です。

また分からないところありましたら質問していいですか?
本当に助かりました!!!

お礼日時:2009/11/18 21:18

CSVの読み込みは何回も議題にあがっているため、一発プログラムを組んでみました。


CSVファイルは仕様自体が曖昧な所もありますが、一般的に知られていると思われる仕様でやっているつもりです。
冗長的なプログラムなので指摘等well comeです。

----------------------------------------------
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#define STRING_MAX 2048
#define FIELD_MAX 256
#define RECORD_MAX 16384
#define STRING_BUF 16384

typedef struct scsvrec{
 char chr[FIELD_MAX][STRING_MAX];
}SCSVREC;

typedef struct scsvdat{
 long record_cnt;
 SCSVREC *dat[RECORD_MAX];
}SCSVDAT;

bool csvFileRead(char *filename ,SCSVDAT *csvDat);
SCSVREC* csvResolution(char *buf);
bool isKanji(unsigned char c);

int main()
{
 bool bRet;
 SCSVDAT *cdat;
 char filename[1024] = "sample.csv";

 cdat = (SCSVDAT*)malloc(sizeof(SCSVDAT));
 bRet = csvFileRead(filename, cdat);

 return(0);
}

bool csvFileRead(char *filename ,SCSVDAT *csvDat)
{
 FILE *fp;
 SCSVREC *onerec;
 bool ret = false;
 char buf[STRING_BUF];

 memset(csvDat ,0 ,sizeof(SCSVDAT));

 fp = fopen(filename,"r");
 if(fp == NULL){
  printf("ファイルが開けませんでした。\n");
  return(false);
 }
 while(fgets(buf,sizeof(buf),fp) != NULL){
  if (buf[strlen(buf)-1] == '\n'){
   buf[strlen(buf)-1] = '\0';
  }
  onerec = csvResolution(buf);
  csvDat->dat[csvDat->record_cnt] = onerec;
  (csvDat->record_cnt)++;
 }
 fclose(fp);
 return (true);
}

SCSVREC* csvResolution(char *buf)
{
 int bcnt, rcnt, mcnt;
 bool dblfld;
 SCSVREC *onedat;

 onedat = (SCSVREC*)malloc(sizeof(SCSVREC));
 memset(onedat ,0 ,sizeof(SCSVREC));

 bcnt = rcnt = mcnt= 0;
 dblfld = false;
 for(;;bcnt++) {
  if (buf[bcnt] == '\0') break;
  if (mcnt == 0 && buf[bcnt] == '\"') {
   dblfld = true;
   continue;
  }
  if (isKanji((unsigned char)buf[bcnt])) {
   onedat->chr[rcnt][mcnt] = buf[bcnt];
   mcnt++;
   bcnt++;
   onedat->chr[rcnt][mcnt] = buf[bcnt];
   mcnt++;
   continue;
  }
  if (dblfld == false && buf[bcnt] == ',') {
   rcnt++;
   mcnt = 0;
   continue;
  }
  if (dblfld == true && buf[bcnt] == ',') {
   onedat->chr[rcnt][mcnt] = buf[bcnt];
   mcnt++;
   continue;
  }
  if (dblfld == true && buf[bcnt] == '\"' && buf[bcnt+1] == '\"') {
   onedat->chr[rcnt][mcnt] = '\"';
   mcnt++;
   bcnt++;
   continue;
  }
  if (dblfld == true && buf[bcnt] == '\"' && (buf[bcnt+1] == ',' || buf[bcnt+1] == '\0')) {
   rcnt++;
   mcnt = 0;
   bcnt++;
   dblfld = false;
   continue;
  }
  onedat->chr[rcnt][mcnt] = buf[bcnt];
  mcnt++;
 }
 return onedat;
}

bool isKanji(unsigned char c)
{
 bool ret = false;
 if ((0x81 <= c && c <= 0x9f) || (0xE0 <= c && c <= 0xEF)) {
  ret = true;
 }
 return (ret);
}
    • good
    • 0
この回答へのお礼

hiro_knighさん:

 本当にありがとうございました!!
 参考にしました。
 

お礼日時:2009/11/18 21:23

strtokは区切り文字が連続する場合があるCSVには使えません。


この場合なら、sscanfが使えます。
sscanf(buff,"%[^,],%[^,],%[^,]",tbl[ntb].aaa,tbl[ntb].bbb,tbl[ntb].ccc);

もし、CSVが
123,"456,789",999
などのようにカンマを含む文字列を持つなら、sscanfも使えません。一文字ずつ独自に処理する必要があります。
さらに、
123,"456
789",999
といった感じで改行を含む文字列も持つなら、fgetsすら使えません。
    • good
    • 0

こんにちは。



strtok関数では、区切り文字が2文字以上、連続している場合は、1つの区切り区分
として処理されてしまうようです。

従って、入力ファイル上のCSVデータが、
  あいう,,さしす
のような場合、
1回目のstrtokでは、"あいう"の先頭ポインタが返され、
2回目のstrtokでは、空き文字列ではなく、"さしす"の先頭ポインタが返されます。

また、strtok関数では、次の区切り文字まで(文字列終端の'\0'のコードも区切り
に含まれます)に文字列がなかった場合は、NULLが返されます。

ですので、#2の方が言われているように、この戻り値がNULLだった場合のチェック
をしないで、ご提示のような、
  strcpy(tbl[ntb].ccc,strtok(NULL,","));
のコードを実行してしまうと、NULLポインタから文字列をコピーしようとして、その
結果、例外エラーなどが発生してしまう可能性があります。

以上のような障害を回避するには、strtok関数は使用せずに、別な方法(専用の
関数を作るなど)をとる必要があります。

以上を踏まえて、ご提示のコードを変更したバージョンを作成してみました。
strtok関数の代わりに、専用の関数【strtok2】を作ってあります。

また、入力ファイル名と出力ファイル名は、実行時のコマンドラインで指定するよう
に変更しています。

■変更バージョン(サンプル)
注1)インデント等のため、全角スペースを入れています。
   コピペの際は、半角スペースorタブに置換して下さい。
注2)一応、動作検証はしていますが、まだ不十分な部分があるかもしれません。
   そのため、ご使用の環境で上手く動作しなかった場合は、すみません。
=========================
/*
* tcsv20.c : CSVファイル読み込み&書き込みテスト
*/
#include <stdio.h>
#include <string.h>

#define MBF 256    /* 汎用の文字列バッファのサイズ */
#define TBF 32    /* 構造体用の文字列バッファのサイズ */

/* CSVデータ読み込み用のバッファ構造体 */
struct tb{
  char aaa[TBF+1];  /* 1列目のデータ用 */
  char bbb[TBF+1];  /* 2列目のデータ用 */
  char ccc[TBF+1];  /* 3列目のデータ用 */
};

/* 関数プロトタイプ */
int strtok2( char *strSrc, char *strDest, char *strSep, int nPos, int nSize );

/*== main ==*/
int main(int argc, char *argv[])
{
  struct tb tbl[20];  /* CSVデータ読み込み用の構造体配列 */
  struct tb *tp;    /* ↑のアクセス用の構造体ポインタ */
  int ntb,itb;    /* 行カウンタ */
  char szInpCSV[256];  /* 入力CSVファイル名 */
  char szOutCSV[256];  /* 出力CSVファイル名 */
  FILE* fi;      /* 入力CSVファイル用のファイルポインタ */
  FILE* fo;      /* 出力CSVファイル用のファイルポインタ */
  char buff[MBF];    /* 1行読み込み用バッファ */

  //== コマンドラインのファイル名の有無チェック ==
  if( argc < 3 ){
    printf( "入力ファイル名(CSV)と出力ファイル名(CSV)を指定して下さい。\n" );
    return -1;
  }

  //== コマンドラインの入力ファイル名、出力ファイル名を取得 ==
  strcpy( szInpCSV, argv[1] );
  strcpy( szOutCSV, argv[2] );

  //== 入力CSVファイルからの読み込み処理 ==
  fi = fopen( szInpCSV, "r" );  /*入力ファイルオープン*/
  if( fi == NULL ){
    printf( "入力ファイル\"%s\"が開けません\n", szInpCSV );
    return -2;
  }
  ntb = 0;  /*行カウンタの初期化*/
  while ( fgets(buff, MBF, fi) != NULL )  /*1行毎に読み込み*/
  {
    /* 取得文字列のバッファを始めにクリア */
    tbl[ntb].aaa[0] = '\0';
    tbl[ntb].bbb[0] = '\0';
    tbl[ntb].ccc[0] = '\0';

    /* 区切り文字(カンマ、改行)で区切って各列の文字列を取得 */
    strtok2( buff, tbl[ntb].aaa, ",\n", 1, TBF );  /*1列目*/
    strtok2( buff, tbl[ntb].bbb, ",\n", 2, TBF );  /*2列目*/
    strtok2( buff, tbl[ntb].ccc, ",\n", 3, TBF );  /*3列目*/
/*
*    strcpy(tbl[ntb].aaa,strtok(buff,","));
*    strcpy(tbl[ntb].bbb,strtok(NULL,","));
*    strcpy(tbl[ntb].ccc,strtok(NULL,","));
*/
    ntb++;  /*行カウンタ+1*/
  }
  fclose( fi );  /*入力ファイルクローズ*/

  //== 出力CSVファイルへの書き込み処理 ==
  fo = fopen( szOutCSV, "w" );  /*出力ファイルオープン*/
  if( fo == NULL ){
    printf( "出力ファイル\"%s\"が開けません\n", szOutCSV );
    return -3;
  }
  for ( itb=0; itb<ntb; itb++ ) {  /*取得行数分ループ*/
    tp = tbl + itb;        /*出力対象の構造体のポインタ設定*/
                  /*1行分を出力*/
    fprintf( fo, "%s,%s,%s\n", tp->aaa, tp->bbb, tp->ccc );
  }
  fclose( fo );  /*出力ファイルクローズ*/

  return 0;
}

/*
* strtok2 : 区切り文字単位の部分文字列の取得
*【引数】
*  char *strSrc;  参照元の文字列
*  char *strDest;  取り出した部分文字列の格納先
*  char *strSep;  区切り文字の文字群
*  int nPos;    取り出し位置(区切り文字毎のカラム位置)
*  int nSize;    strDestのバッファサイズ
*【戻り値】
*  int       =0 : 部分文字列なし
*           =1 : 部分文字列あり
*           =-1: 引数エラー
*/
int strtok2( char *strSrc, char *strDest, char *strSep, int nPos, int nSize )
{
  int ians = 0;      /* 戻り値 */
  int icnt;        /* ループ変数 */
  unsigned long usize;  /* 文字列のサイズ */
  char *p1;        /* 文字列アクセス用のポインタ */
  char *p2;        /* 文字列アクセス用のポインタ */

  /*== 引数のチェック==*/
  if( strSrc==NULL || strDest==NULL || strSep==NULL
    || nPos<1 || nSize<1 ){
    return -1;
  }

  /* 部分文字列を空き文字列として初期化 */
  strDest[0] = '\0';

  /*== 部分文字列の取り出し ==*/
  p1 = strSrc;
  p2 = NULL;
  /* 指定の区切り位置までループ */
  for(icnt=1; icnt<=nPos && *p1!='\0'; icnt++)
  {
    /* 区切り文字の位置取得 */
    /* ※ポインタで返される。なければNULL */
    p2 = strpbrk( (const char*)p1, (const char*)strSep );
    if( p2 ){
      /* 区切り文字あり */
      if( icnt==nPos ){
        /* 指定の区切り位置*/
        usize = (p2 - p1) / sizeof(char);
        if( usize >= nSize ) usize = (nSize - 1);
        if( usize > 0 ){
          /* 部分文字列あり */
          strncpy( strDest, p1, usize );
          strDest[usize] = '\0';
          ians = 1;
        }
        else{
          /* 部分文字列なし */
          strDest[0] = '\0';
          ians = 0;
        }
      }
      p1 = p2+1;  /* 参照元の文字列ポインタを1列分進める */
    }
    else{
      /* 区切り文字なし */
      if( icnt==nPos ){
        /* 指定の区切り位置 */
        usize = strlen( p1 );
        if( usize >= nSize ) usize = (nSize - 1);
        if( usize > 0 ){
          /* 部分文字列あり */
          strncpy( strDest, p1, usize );
          strDest[usize] = '\0';
          ians = 1;
        }
        else{
          /* 部分文字列なし */
          strDest[0] = '\0';
          ians = 0;
        }
      }
      else{
        /* 指定の区切り位置でない */
        strDest[0] = '\0';
        ians = 0;
      }
      break;
    }
  }

  return ians;
}
=========================

■上記サンプルの実行結果
※以下のように、出力ファイルは、入力ファイルと同一フォーマットになります。
 (まぁ、ここに貼り付けただけだとあまり意味はないですね。。。)

1)入力CSVファイルが下記だった場合
あいう,かきく,さしす
たちつ,なにぬ,はひふ
まみむ,やゆよ,らりる

<出力CSVファイルの結果>
あいう,かきく,さしす
たちつ,なにぬ,はひふ
まみむ,やゆよ,らりる

2)入力CSVファイルが下記だった場合
あいう,,さしす
たちつ,なにぬ,はひふ
まみむ,,
あいう,win,

<出力CSVファイルの結果>
あいう,,さしす
たちつ,なにぬ,はひふ
まみむ,,
あいう,win,

3)入力CSVファイルが下記だった場合
,B1,
,,C2
A3,,

<出力CSVファイルの結果>
,B1,
,,C2
A3,,

以上です。参考になれば幸いです。

この回答への補足

親切な回答ありがとうございました。
動かしてみました。

質問があります。
if( usize >= nSize ) usize = (nSize - 1);
if( usize >= nSize ) usize = (nSize - 1);
上の2箇所警告が出ます、
警告 W8012 1116.cpp 126: 符号付き値と符号なし値の比較(関数 strtok2(char *,char *,char *,int,int) )
なぜでしょうか?

上の処理を消しても正常に動いてます。ちょっとわかりませんので教えていただけますか?

もし
ファイル内容が
1,あいうえお、かきく
2,,
3,,さしす
で 以下の構造体に格納したい場合どこを直したらいいですか?
どこでint型に変換するんですか?

struct tb{
int aaa[32];
char bbb[32];
char ccc[32];
};

補足日時:2009/11/17 21:45
    • good
    • 1

だめなところ



struct tb tbl[20]; を初期化していない。
strtok の戻り値を無評価で使用している。
    • good
    • 0

strtok関数が使えるんじゃないかな?

    • good
    • 0

お探しのQ&Aが見つからない時は、教えて!gooで質問しましょう!

このQ&Aを見た人はこんなQ&Aも見ています