コラム
2025/12/25
プログラミングについて 第122回目
『作っておくと便利なちょっとしたプログラム その6』
今回もこれまでに引き続き、ちょっと便利なプログラムを作っていきましょう。
ということで、今回はコンマで区切られたテキストファイルを見やすく整形するプログラムを作ることにします。コンマで区切られたテキストファイルといえば、私はエクセルから出力されるCSVファイルをよく処理することがあります。プログラムで読み込むときにはそれほど苦労することはないのですが、このファイルをテキストエディタで見ると、それぞれの項目の位置(カラム位置)が一定ではないので非常に苦労します。
1,,2,555,444,,1,2
1234,222,,,abc444,chinchin,22,def
,222,sss,,ab4,lop555,2qwe2,tanuki
pantu,22,sss,,ab4,osarunoosiri,2qwe2,tanuki
だいたい上のような感じです。これでは、6番目の項目を比べてみようとしてもコンマの数を数えるだけで嫌になってしまいます。もしこれが、
1 , ,2 ,555,444 , ,1 ,2
1234 ,222, , ,abc444,chinchin ,22 ,def
,222,sss, ,ab4 ,lop555 ,2qwe2,tanuki
pantu,22 ,sss, ,ab4 ,osarunoosiri,2qwe2,tanuki
と表示できたらどんなに幸せだろうかと思う事がよくあります。そんなに思っているのならばプログラムを作ってしまえばいいのですが、これも例外ではなく仕事の最中でしかないのです。結局はその度にテキストエディタでスペースをパコパコと入力してカラム数を合わせる始末なのです。
今回はこれをプログラム化してしまいましょう。プログラムの起動方法は次のようなものにすることにしましょう。
起動時のパラメータは、
reform [-e|c|?] [-t1|2] [-l|r] [-i] [-s] [ソースファイル名]
とします。ここで、
-e 見やすいように拡張して表示します。
-c コンマ間のスペースを削除(圧縮)して表示します。
-? 本ヘルプを表示します。
∴ -e、-c、-? のいずれかのオプションも指定されないときには -e として表示します。
-t1 文字列の内のダブルクォーテーションは \\\" と表現されているテキストを処理します。
-t2 文字列の内のダブルクォーテーションは \"\" と表現されているテキストを処理します。
-l -e のときには左詰めで表示します。
-c のときには本オプションは無視します。
-r -e のときには右詰めで表示します。
-c のときには本オプションは無視します。
-i -e のときにはコンマの前後のスペースを無視します。
-c のときには本オプションは無視します。
-s -e のときにはコンマの前後に1つスペースを挿入します。
-c のときには本オプションは無視します。
ソーステキストファイル名
処理するソーステキストファイルの名称。本パラメータを省略すると標準入力からの読み込みになります。
拡張と圧縮の両方の表示方法をできるようにしたのは、見るだけではなく、テキストを直した後、元のフォーマットに戻して別のプログラムで使用するということも考えられるからです。ただし、圧縮時にはコンマの前後のスペースすべてを削除するしか方法がないので全く元と同じになるという保証ができません。
文字列はダブルクォーテーションで囲まれているということを前提にしていますが、
文字列内のダブルクォーテーションの表現方法がエクセルから出力されたものは連続する2つのダブルクォーテーションで表現されているので、バックスラッシュでエスケープする表現方法との両方が使用できるようにしました。オプションの指定の方法が -t1などとちょっと覚えられないものにしてしまいましたが、良い方法が思い浮かばなかったので適当に決めてしまいました。
それではプログラムを作っていくことにしましょう。
まずプログラムの最初の部分は、
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <malloc.h>
#define NEW(type,element) (type *)malloc(sizeof(type)*(element))
#define DELETE(addr) free(addr)
#define PROC_MODE_EXPAND 1
#define PROC_MODE_COMPRESS 2
#define PROC_MODE_HELP 3
int proc_mode=PROC_MODE_EXPAND;
int ignore_terminal_space=0;
#define LEFT_ADJUST 1
#define RIGHT_ADJUST 2
int display_mode=LEFT_ADJUST;
int display_add_space=0;
char text_enclose_char='"';
char text_escape_char='\\';
char *fname=NULL;
FILE *fp=stdin;
char *new_strcat(char *string1,char *string2);
といっぱいあるのですが、殆どが前回まで作ってきた uline で説明したものばかりです。
#define PROC_MODE_EXPAND 1
#define PROC_MODE_COMPRESS 2
#define PROC_MODE_HELP 3
int proc_mode=PROC_MODE_EXPAND;
int ignore_terminal_space=0;
#define LEFT_ADJUST 1
#define RIGHT_ADJUST 2
int display_mode=LEFT_ADJUST;
int display_add_space=0;
char text_enclose_char='"';
char text_escape_char='\\';
の部分がこのプログラムにしかないところです。
#define PROC_MODE_EXPAND 1
#define PROC_MODE_COMPRESS 2
#define PROC_MODE_HELP 3
int proc_mode=PROC_MODE_EXPAND;
は動作モードの設定で、初期値(省略値)は拡張する設定にしてあります。
int ignore_terminal_space=0;
はコンマの前後のスペースを無視するか否かの設定で、初期値(省略値)は無視しない設定にしてあります。
#define LEFT_ADJUST 1
#define RIGHT_ADJUST 2
int display_mode=LEFT_ADJUST;
は、拡張表示のときに左詰めまたは右詰めのどちらで表示するかの設定で初期値(省略値)は左詰めにしてあります。
int display_add_space=0;
表示時にコンマの前後にスペースを挿入するか否かの設定で、初期値(省略値)は挿入しない設定にしてあります。
char text_enclose_char='"';
char text_escape_char='\\';
は文字列内のダブルクォーテーションの表現方法の設定です。text_enclose_char は文字列がどんな文字で囲まれているのかという文字、text_escape_char はこの文字の次の文字を無条件に無視するための文字です。文字列がダブルクォーテーションで囲まれているという前提ですので text_enclose_char という変数をわざわざ用意する必要はないのですが、将来違った文字で囲まれているフォーマットのテキストのための拡張用に用意してあります。text_enclose_char と text_escape_char が同じ文字か否かで文字列の解析時の動作を変えるようにします。
次はメイン関数です。これも前回までと同じようにまずはパラメータの解析を行ないます。
void main(argc,argv)
int argc;
char *argv[];
{
if(analize_argment(argc,argv)== -1)goto end;
end:
exit(0);
}
関数 analize_argment は、
analize_argment(argc,argv)
int argc;
char *argv[];
{
int i;
for(i=1;i<argc;i++){
if(stricmp(argv[i],"-c")==0
|| stricmp(argv[i],"/c")==0)proc_mode=PROC_MODE_COMPRESS;
else if(stricmp(argv[i],"-e")==0
|| stricmp(argv[i],"/e")==0)proc_mode=PROC_MODE_EXPAND;
else if(stricmp(argv[i],"-?")==0
|| stricmp(argv[i],"/?")==0){
proc_mode=PROC_MODE_HELP;
return 1;
}
else if(stricmp(argv[i],"-i")==0
|| stricmp(argv[i],"/i")==0)ignore_terminal_space=1;
else if(stricmp(argv[i],"-l")==0
|| stricmp(argv[i],"/l")==0)display_mode=LEFT_ADJUST;
else if(stricmp(argv[i],"-r")==0
|| stricmp(argv[i],"/r")==0)display_mode=RIGHT_ADJUST;
else if(stricmp(argv[i],"-t1")==0
|| stricmp(argv[i],"/t1")==0){
text_enclose_char='"' ;
text_escape_char='\\';
}
else if(stricmp(argv[i],"-t2")==0
|| stricmp(argv[i],"/t2")==0){
text_enclose_char='"';
text_escape_char='"';
}
else if(stricmp(argv[i],"-s")==0
|| stricmp(argv[i],"/s")==0)display_add_space=1;
else{
for(;i<argc;i++){
if(fname && fname[0])fname=new_strcat(fname," ");
fname=new_strcat(fname,argv[i]);
}
}
}
return 1;
}
とやけにごちゃごちゃしているのですが、難しい内容ではありませんので特に説明はしません。
関数 new_strcat も前回までと全く同じです。
char *new_strcat(char *string1,char *string2)
{
if(string1==NULL){
string1=NEW(char,strlen(string2)+1);
strcpy(string1,string2);
}
else{
int l;
l=strlen(string1)+strlen(string2)+1;
if(l>_msize(string1))string1=realloc(string1,l);
strcat(string1,string2);
}
return string1;
}
次に処理する機能を追加するためにメイン関数を直します。
void main(argc,argv)
int argc;
char *argv[];
{
if(analize_argment(argc,argv)== -1)goto end;
if(proc_mode==PROC_MODE_HELP){
}
else{
if(proc_mode==PROC_MODE_COMPRESS){
ignore_terminal_space=1;
display_add_space=0;
display_mode=LEFT_ADJUST;
}
if(open_source()== -1)goto end;
close_source();
}
end:
exit(0);
}
とりあえずヘルプの表示は最後に付け加えるとします。テキストファイルを読み込む部分も表示する部分もできる限り同じ関数にしたいこともありますので、圧縮時(proc_modeが PROC_MODE_COMPRESS のとき)には、読み込み時にはコンマの前後の空白を無視、表示時にはコンマの前後にはスペースを挿入しない、表示は左詰めの設定を強制的に行なっています。この設定があることで、圧縮時の無意味なパラメータを無視することになります。
関数 open_source と関数 close_source も前回のものと全く同じです。
open_source()
{
if(fname){
fp=fopen(fname,"r");
if(fp==NULL){
printf("入力ファイル\n");
printf("File : %s\n",fname);
printf("のオープン時にエラーが発生しました。\n");
printf("%s\n",strerror(errno));
return -1;
}
}
return 1;
}
close_source()
{
if(fname)fclose(fp);
return 1;
}
今回はここまでにしましょう。また次回。
第122回 プログラミングについて『作っておくと便利なちょっとしたプログラム その6』
プログラミングについて 第122回目
『作っておくと便利なちょっとしたプログラム その6』
今回もこれまでに引き続き、ちょっと便利なプログラムを作っていきましょう。
ということで、今回はコンマで区切られたテキストファイルを見やすく整形するプログラムを作ることにします。コンマで区切られたテキストファイルといえば、私はエクセルから出力されるCSVファイルをよく処理することがあります。プログラムで読み込むときにはそれほど苦労することはないのですが、このファイルをテキストエディタで見ると、それぞれの項目の位置(カラム位置)が一定ではないので非常に苦労します。
1,,2,555,444,,1,2
1234,222,,,abc444,chinchin,22,def
,222,sss,,ab4,lop555,2qwe2,tanuki
pantu,22,sss,,ab4,osarunoosiri,2qwe2,tanuki
だいたい上のような感じです。これでは、6番目の項目を比べてみようとしてもコンマの数を数えるだけで嫌になってしまいます。もしこれが、
1 , ,2 ,555,444 , ,1 ,2
1234 ,222, , ,abc444,chinchin ,22 ,def
,222,sss, ,ab4 ,lop555 ,2qwe2,tanuki
pantu,22 ,sss, ,ab4 ,osarunoosiri,2qwe2,tanuki
と表示できたらどんなに幸せだろうかと思う事がよくあります。そんなに思っているのならばプログラムを作ってしまえばいいのですが、これも例外ではなく仕事の最中でしかないのです。結局はその度にテキストエディタでスペースをパコパコと入力してカラム数を合わせる始末なのです。
今回はこれをプログラム化してしまいましょう。プログラムの起動方法は次のようなものにすることにしましょう。
起動時のパラメータは、
reform [-e|c|?] [-t1|2] [-l|r] [-i] [-s] [ソースファイル名]
とします。ここで、
-e 見やすいように拡張して表示します。
-c コンマ間のスペースを削除(圧縮)して表示します。
-? 本ヘルプを表示します。
∴ -e、-c、-? のいずれかのオプションも指定されないときには -e として表示します。
-t1 文字列の内のダブルクォーテーションは \\\" と表現されているテキストを処理します。
-t2 文字列の内のダブルクォーテーションは \"\" と表現されているテキストを処理します。
-l -e のときには左詰めで表示します。
-c のときには本オプションは無視します。
-r -e のときには右詰めで表示します。
-c のときには本オプションは無視します。
-i -e のときにはコンマの前後のスペースを無視します。
-c のときには本オプションは無視します。
-s -e のときにはコンマの前後に1つスペースを挿入します。
-c のときには本オプションは無視します。
ソーステキストファイル名
処理するソーステキストファイルの名称。本パラメータを省略すると標準入力からの読み込みになります。
拡張と圧縮の両方の表示方法をできるようにしたのは、見るだけではなく、テキストを直した後、元のフォーマットに戻して別のプログラムで使用するということも考えられるからです。ただし、圧縮時にはコンマの前後のスペースすべてを削除するしか方法がないので全く元と同じになるという保証ができません。
文字列はダブルクォーテーションで囲まれているということを前提にしていますが、
文字列内のダブルクォーテーションの表現方法がエクセルから出力されたものは連続する2つのダブルクォーテーションで表現されているので、バックスラッシュでエスケープする表現方法との両方が使用できるようにしました。オプションの指定の方法が -t1などとちょっと覚えられないものにしてしまいましたが、良い方法が思い浮かばなかったので適当に決めてしまいました。
それではプログラムを作っていくことにしましょう。
まずプログラムの最初の部分は、
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <malloc.h>
#define NEW(type,element) (type *)malloc(sizeof(type)*(element))
#define DELETE(addr) free(addr)
#define PROC_MODE_EXPAND 1
#define PROC_MODE_COMPRESS 2
#define PROC_MODE_HELP 3
int proc_mode=PROC_MODE_EXPAND;
int ignore_terminal_space=0;
#define LEFT_ADJUST 1
#define RIGHT_ADJUST 2
int display_mode=LEFT_ADJUST;
int display_add_space=0;
char text_enclose_char='"';
char text_escape_char='\\';
char *fname=NULL;
FILE *fp=stdin;
char *new_strcat(char *string1,char *string2);
といっぱいあるのですが、殆どが前回まで作ってきた uline で説明したものばかりです。
#define PROC_MODE_EXPAND 1
#define PROC_MODE_COMPRESS 2
#define PROC_MODE_HELP 3
int proc_mode=PROC_MODE_EXPAND;
int ignore_terminal_space=0;
#define LEFT_ADJUST 1
#define RIGHT_ADJUST 2
int display_mode=LEFT_ADJUST;
int display_add_space=0;
char text_enclose_char='"';
char text_escape_char='\\';
の部分がこのプログラムにしかないところです。
#define PROC_MODE_EXPAND 1
#define PROC_MODE_COMPRESS 2
#define PROC_MODE_HELP 3
int proc_mode=PROC_MODE_EXPAND;
は動作モードの設定で、初期値(省略値)は拡張する設定にしてあります。
int ignore_terminal_space=0;
はコンマの前後のスペースを無視するか否かの設定で、初期値(省略値)は無視しない設定にしてあります。
#define LEFT_ADJUST 1
#define RIGHT_ADJUST 2
int display_mode=LEFT_ADJUST;
は、拡張表示のときに左詰めまたは右詰めのどちらで表示するかの設定で初期値(省略値)は左詰めにしてあります。
int display_add_space=0;
表示時にコンマの前後にスペースを挿入するか否かの設定で、初期値(省略値)は挿入しない設定にしてあります。
char text_enclose_char='"';
char text_escape_char='\\';
は文字列内のダブルクォーテーションの表現方法の設定です。text_enclose_char は文字列がどんな文字で囲まれているのかという文字、text_escape_char はこの文字の次の文字を無条件に無視するための文字です。文字列がダブルクォーテーションで囲まれているという前提ですので text_enclose_char という変数をわざわざ用意する必要はないのですが、将来違った文字で囲まれているフォーマットのテキストのための拡張用に用意してあります。text_enclose_char と text_escape_char が同じ文字か否かで文字列の解析時の動作を変えるようにします。
次はメイン関数です。これも前回までと同じようにまずはパラメータの解析を行ないます。
void main(argc,argv)
int argc;
char *argv[];
{
if(analize_argment(argc,argv)== -1)goto end;
end:
exit(0);
}
関数 analize_argment は、
analize_argment(argc,argv)
int argc;
char *argv[];
{
int i;
for(i=1;i<argc;i++){
if(stricmp(argv[i],"-c")==0
|| stricmp(argv[i],"/c")==0)proc_mode=PROC_MODE_COMPRESS;
else if(stricmp(argv[i],"-e")==0
|| stricmp(argv[i],"/e")==0)proc_mode=PROC_MODE_EXPAND;
else if(stricmp(argv[i],"-?")==0
|| stricmp(argv[i],"/?")==0){
proc_mode=PROC_MODE_HELP;
return 1;
}
else if(stricmp(argv[i],"-i")==0
|| stricmp(argv[i],"/i")==0)ignore_terminal_space=1;
else if(stricmp(argv[i],"-l")==0
|| stricmp(argv[i],"/l")==0)display_mode=LEFT_ADJUST;
else if(stricmp(argv[i],"-r")==0
|| stricmp(argv[i],"/r")==0)display_mode=RIGHT_ADJUST;
else if(stricmp(argv[i],"-t1")==0
|| stricmp(argv[i],"/t1")==0){
text_enclose_char='"' ;
text_escape_char='\\';
}
else if(stricmp(argv[i],"-t2")==0
|| stricmp(argv[i],"/t2")==0){
text_enclose_char='"';
text_escape_char='"';
}
else if(stricmp(argv[i],"-s")==0
|| stricmp(argv[i],"/s")==0)display_add_space=1;
else{
for(;i<argc;i++){
if(fname && fname[0])fname=new_strcat(fname," ");
fname=new_strcat(fname,argv[i]);
}
}
}
return 1;
}
とやけにごちゃごちゃしているのですが、難しい内容ではありませんので特に説明はしません。
関数 new_strcat も前回までと全く同じです。
char *new_strcat(char *string1,char *string2)
{
if(string1==NULL){
string1=NEW(char,strlen(string2)+1);
strcpy(string1,string2);
}
else{
int l;
l=strlen(string1)+strlen(string2)+1;
if(l>_msize(string1))string1=realloc(string1,l);
strcat(string1,string2);
}
return string1;
}
次に処理する機能を追加するためにメイン関数を直します。
void main(argc,argv)
int argc;
char *argv[];
{
if(analize_argment(argc,argv)== -1)goto end;
if(proc_mode==PROC_MODE_HELP){
}
else{
if(proc_mode==PROC_MODE_COMPRESS){
ignore_terminal_space=1;
display_add_space=0;
display_mode=LEFT_ADJUST;
}
if(open_source()== -1)goto end;
close_source();
}
end:
exit(0);
}
とりあえずヘルプの表示は最後に付け加えるとします。テキストファイルを読み込む部分も表示する部分もできる限り同じ関数にしたいこともありますので、圧縮時(proc_modeが PROC_MODE_COMPRESS のとき)には、読み込み時にはコンマの前後の空白を無視、表示時にはコンマの前後にはスペースを挿入しない、表示は左詰めの設定を強制的に行なっています。この設定があることで、圧縮時の無意味なパラメータを無視することになります。
関数 open_source と関数 close_source も前回のものと全く同じです。
open_source()
{
if(fname){
fp=fopen(fname,"r");
if(fp==NULL){
printf("入力ファイル\n");
printf("File : %s\n",fname);
printf("のオープン時にエラーが発生しました。\n");
printf("%s\n",strerror(errno));
return -1;
}
}
return 1;
}
close_source()
{
if(fname)fclose(fp);
return 1;
}
今回はここまでにしましょう。また次回。






