ファイル内容
********************
あいう,,さしす
たちつ,なにぬ,はひふ
まみむ,,
あいう,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件)
- 最新から表示
- 回答順に表示
No.9
- 回答日時:
FarEyesです。
すみません、#8の下記部分において訂正があります。
> この静的な作業領域は、共通なエリアですので、他の処理(マルチスレッド
> などで並列処理を行っている場合も含めて)で、strtok関数が呼ばれてしまう
> と、今まで保持していた文字列が、書き換えられてしまいます。
> その結果、意図しない結果が発生する可能性があります。
上記文中の、
(マルチスレッドなどで並列処理を行っている場合も含めて)
の部分ですが、処理系によっては、
strtok関数などの静的作業領域を使用する関数において、
『これらの関数を同時に複数のスレッドから呼び出すことによって
障害が発生することはありません。』
のように謳われている処理系も存在します。
ですので、
(マルチスレッドなどで並列処理を行っている場合も含めて)
の部分は無視して下さい。
どうも、申し訳ありませんでした。
No.8
- 回答日時:
こんにちは。
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)その他
> ソースみてもカンマの処理まで理解できないところあります。
> 日本語で箇条書きしていただけませんでしょうか???
申し訳ありませんが、そこまでするサービス精神は、私にはありません、
これは、質問者さんご自身で、解析なさって下さい。
※人から教えてもらうだけだと、身に付かないと思います。
※あと、今回の内容でおおよその部分は、ご理解戴けるかと思います。
※もしも、お気に触られた場合は、申し訳ありません。
FarEyesさん:
本当にありがとうございました。
質問ちゃんと対応してくださって本当にうれしかったです。
カンマ処理するstrtok2関数についてのソースコードは理解できるまでがんばっていきたいです。
また よろしくお願いいたします。
No.7
- 回答日時:
こんばんは。
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);
この行の意味もちょっと説明お願いします。
ソースみてもカンマの処理まで理解できないところあります。
日本語で箇条書きしていただけませんでしょうか???
本当に失礼いたします。
No.6
- 回答日時:
こんにちは。
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」を無視する設定でコンパイルを行って
下さい。
以上です。
FarEyesさん:
有り難うございました!
本当にうれしいです。
struct tb{
int aaa[32];
char bbb[32];
char ccc[32];
};
上は私の間違いです。int aaa; が正解です。
また分からないところありましたら質問していいですか?
本当に助かりました!!!
No.5
- 回答日時:
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);
}
No.4
- 回答日時:
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すら使えません。
No.3
- 回答日時:
こんにちは。
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];
};
お探しのQ&Aが見つからない時は、教えて!gooで質問しましょう!
似たような質問が見つかりました
- C言語・C++・C# c言語の問題の説明、各所ごとに 5 2023/07/26 11:03
- C言語・C++・C# バイナリファイルをコピーするのにかかる時間を測りたいのですが実行するとFatel error:gli 2 2022/11/03 01:10
- C言語・C++・C# プログラミングの授業の課題です 1 2023/01/17 22:15
- C言語・C++・C# c言語の問題です 3 2023/01/10 16:15
- C言語・C++・C# このプログラミング誰か教えてくれませんか 1 2022/06/02 15:27
- C言語・C++・C# c言語でユーザ関数を利用して入力された文字列を反転させるプログラムを作りたいです。 3 2023/01/29 19:47
- PHP PHPでCSVを出力するさいに、ループの中で前の行の値を変更したい 3 2022/10/27 17:44
- 大学・短大 C言語線形リストの問題です 3 2022/12/22 00:45
- C言語・C++・C# C言語で再起関数とポインタを用いて文字列反転をする方法がわかりません。 4 2023/04/29 20:32
- C言語・C++・C# #include <stdio.h>int main(void) { int buf[100] = 6 2022/11/01 22:45
このQ&Aを見た人はこんなQ&Aも見ています
-
賃貸で可能な古民家風レトロな部屋作りのコツ!改めて知る畳の高い機能性と魅力も紹介
畳の部屋を雰囲気のよい部屋に仕上げたい!賃貸住宅でもできる古民家風のレトロな部屋作りのコツを伺った。
-
csvファイルのデータを構造体に
C言語・C++・C#
-
カンマ区切りのデータを配列に読み込みたい
C言語・C++・C#
-
C言語で構造体のメンバを簡単に出力する方法ありますか?
C言語・C++・C#
-
-
4
CSVファイル作成
C言語・C++・C#
-
5
構造体のメンバをfor文で回したい
C言語・C++・C#
-
6
ファイルから読み込んだデータを構造体に格納できますか?
C言語・C++・C#
-
7
fopne で失敗する原因
C言語・C++・C#
-
8
CSVファイルの内容を構造体に格納したい(Unix使用)。
C言語・C++・C#
-
9
C言語でCSVファイルの行数を読み取りたい
C言語・C++・C#
-
10
構造体とfscanf
C言語・C++・C#
-
11
構造体の勉強中です 合計点の高い順に並べ替えがわかりません
C言語・C++・C#
関連するカテゴリからQ&Aを探す
このQ&Aを見た人がよく見るQ&A
デイリーランキングこのカテゴリの人気デイリーQ&Aランキング
-
char*を初期化したいのですが
-
小数点入りの文字列をfloat型に...
-
DWORDとcharの変換
-
char[]をDWORDに格納するには
-
fgetc( )の戻り値はなぜ整数??
-
C言語のintとcharの違いってな...
-
CStringからchar*への型変換に...
-
C言語にて構造体のメンバがNULL...
-
エクセルのMID関数は、C言語では?
-
C++Builder 2009 テキスト...
-
char*型の文字列をchar[10]へ。
-
new charとnew char[N]の違いは?
-
const char* s1とただのchar s1...
-
メモリを0クリアする方法について
-
ポインタを使って回文かどうか...
-
C言語ポインタ 配列について
-
文字列のコピーについて
-
文字列の処理
-
文字列の連結
-
Windows APIでおかしな事になる。
マンスリーランキングこのカテゴリの人気マンスリーQ&Aランキング
-
C言語のintとcharの違いってな...
-
char*を初期化したいのですが
-
C言語にて構造体のメンバがNULL...
-
CStringからchar*への型変換に...
-
strcat関数を自作したいです
-
new charとnew char[N]の違いは?
-
csvファイルをfscanfで読み込む...
-
char型にint型の数値を代入する。
-
動的メモリの初期化方法について。
-
C言語で文字列をかえす正しい書...
-
char 文字列型 の表現範囲が-12...
-
文字列str内の全ての数字を...
-
DWORDとcharの変換
-
fstream型オブジェクトを関数の...
-
C言語のプログラムについてです
-
小数点入りの文字列をfloat型に...
-
szとlpszの違い
-
const char* s1とただのchar s1...
-
文字列内の数字削除
-
c言語でポインタ変数を用いた配...
おすすめ情報