道具箱です。
itoa
:数字データの文字列としての格納
* 一部,C99 の範囲外の内容を含む。
以下のようにすると,環境変数が得られます。
/* Example 18.6 */ #include <stdio.h> int main(void) { extern char **environ; char **z; for (z = environ; *z; z++) printf("%s\n", *z); return 0; }
environ
については man 7 environ
で確認してください。(environ
は C99 の範囲外です。)
これを次のように書き直し,コンパイル後の実行ファイルを CGI として動かせば,httpd の環境変数一覧が得られます。
/* Example 18.7 */ #include <stdio.h> int main(void) { extern char **environ; char **z; printf("Content-Type: text/html\n\n"); puts( "<body>\n" "<pre>"); for (z = environ; *z; z++) printf("%s\n", *z); puts( "</pre>\n" "</body>"); return 0; }
特定の環境変数の値を得るには,stdlib.h
にある getenv
関数を利用します。(こちらは C99 の範囲内です。)
/* Example 18.8 */ #include <stdio.h> #include <stdlib.h> int main(void) { char *env; printf("Content-Type: text/html\n\n"); puts( "<body>\n" "<pre>"); if((env = getenv("PATH"))) printf("(1) %s\n", env); if((env = getenv("REQUEST_METHOD"))) printf("(2) %s\n", env); puts( "</pre>\n" "</body>"); return 0; }
string.h
にある strtok
関数を利用した文字列の分割は,既に見た通りです。(for
文内 Example 14.8
を参照。)ただ,strtok
は,元の文字列を分割後の文字列に変更してしまうため,元の文字列の再利用が不可能になります。そこで,次のような方法があります。
/* Example 18.9 */ #include <stdio.h> int main(void) { char str[] = "Taro\t89\tA"; char name[16] = "\0"; int point = 0; char grade[4] = "\0"; sscanf(str, "%15[^\t]\t%d\t%3[^\t]", name, &point, grade); printf("%s, %d, %s\n", name, point, grade); return 0; }
文字列 Taro\t89\tA
がタブ \t
で分割され,次のような出力を得ます。
Taro, 89, A
[応用]chomp
chomp とは,文字列の末尾にある \n
を取り去る Perl の関数のことです。これは文字列の分割と同様 sscanf
が使えます。
/* Example 18.10 */ #include <stdio.h> int main(void) { char str1[] = "testing\n"; char str2[8] = "\0"; sscanf(str1, "%7[^\n]", str2); printf("\"%s\" chomped.\n", str2); return 0; }
ある文字列を他の文字列の末尾に連結するのは「9.3. 文字列の操作」で見たように,string.h
にある strcat
関数,もしくは strncat
関数が使えます。(後者は,連結する文字数が指定できますので,バッファ・オーバーフローを確実に回避できます。)
末尾への連結ではなく,連結の位置を任意に指定するには,sprintf
関数が利用できます。しかし,sprintf
関数も又,バッファ・オーバーフローが起きないように注意が必要です。次の例は,バッファ・オーバーフローを避けるために snprintf
関数を利用し,文字列 str
と数字 num
から新たな文字列 dir
を作成するものです。
/* Example 18.11 */ #include <stdio.h> int main(void) { char dir[32] = "\x00"; char str[] = "ABCD"; int num = 123; snprintf(dir, sizeof(dir), "/path/to/home/data%d%s.txt", num, str); printf("%s\n", dir); return 0; }
sizeof(dir)-1
個の文字が dir
にコピーされ,最後にナル文字が入ります。[C99, 7.19.6.5]
実行結果です。
/path/to/home/data123ABCD.txt
* CGI で利用する場合には,str
や num
部分を QUERY_STRING
や POSTデータから直接読み込まないでください。
itoa
とは stdlib.h
にある atoi
関数の逆の意味です。すなわち,数字を文字列として格納するものです。ただ,itoa
は存在しません。そこで,snprintf
関数を使います。
/* Example 18.12 */ #include <stdio.h> int main(void) { int n = 123456; char str[4] = "000"; printf("%s\n", str); snprintf(str, sizeof(str), "%d", n); printf("%s\n", str); return 0; }
実行結果です。
000 123
1900年1月からの秒数を取得するには,ヘッダ time.h
で定義されている関数 time
を使います。取得した秒数を DDD MMM dd hh:mm:ss YYYY
の形にするには関数 ctime
を使えば良いです。
/* Example 18.13 */ #include <stdio.h> #include <time.h> /* time_t, time(), ctime() */ int main(void) { time_t timer; timer = time(NULL); printf("%ld, %s", timer, ctime(&timer)); return 0; }
time_t
とはヘッダ time.h
で定義されている時間を示す型です。実行すると,次のような出力を得ます。
1049039826, Mon Mar 31 00:57:06 2003
取得した時間 time_t timer
の年,月,分,秒,週などの個別データを取得するには,ヘッダ time.h
で定義されている構造体 tm
を利用します。構造体 tm
は次のメンバを持ちます。
int tm_sec; /* seconds (0 - 60) */ int tm_min; /* minutes (0 - 59) */ int tm_hour; /* hours (0 - 23) */ int tm_mday; /* day of month (1 - 31) */ int tm_mon; /* month of year (0 - 11) */ int tm_year; /* year - 1900 */ int tm_wday; /* day of week (Sunday = 0) */ int tm_yday; /* day of year (0 - 365) */ int tm_isdst; /* is summer time in effect? */ char *tm_zone; /* abbreviation of timezone name */ long tm_gmtoff; /* offset from UTC in seconds */
次の例は,取得した時間 time_t timer
を time.h
にある localtime
関数
struct tm *localtime(const time_t *timer);
を利用して構造体 tm
型ポインタ t
に渡し,構造体 tm
の各メンバの値を取得するものです。
/* Example 18.14 */ #include <stdio.h> #include <time.h> int main(void) { time_t timer; struct tm *t; timer = time(NULL); printf("%ld, %s", timer, ctime(&timer)); t = localtime(&timer); printf("days since Sunday = %d\n", t->tm_wday); printf("year = %d\n", t->tm_year + 1900); printf("month = %d\n", t->tm_mon + 1); printf("day of the month = %d\n", t->tm_mday); printf("hours since midnight = %d\n", t->tm_hour); printf("minutes after the hour = %d\n", t->tm_min); printf("seconds after the minute = %d\n", t->tm_sec); printf("days since January 1st = %d\n", t->tm_yday + 1); printf("Daylight Saving Time = %d\n", t->tm_isdst); return 0; }
実行結果です。
1049039826, Mon Mar 31 00:57:06 2003 days since Sunday = 1 year = 2003 month = 3 day of the month = 31 hours since midnight = 0 minutes after the hour = 57 seconds after the minute = 6 days since January 1st = 90 Daylight Saving Time = 0
逆に,ある特定の日付けを time_t
型変数(1900年1月からの秒数)に変換することも可能です。それには time.h
にある mktime
関数を利用します。次の例は,日付け my_time
と現在の日付け(1年以内)の日数の差を求めるものです。
/* Example 18.15 */ #include <stdio.h> #include <time.h> int main(void) { time_t time1, time2, timer; struct tm *t, my_time; time1 = time(NULL); printf("Current: %ld, %s", time1, ctime(&time1)); my_time.tm_year = 2003 - 1900; my_time.tm_mon = 2; my_time.tm_mday = 28; my_time.tm_hour = 22; my_time.tm_min = 15; my_time.tm_sec = 0; my_time.tm_isdst = 0; if ((time2 = mktime(&my_time)) != -1) { printf("my_time: %ld, %s", time2, ctime(&time2)); timer = time1 - time2; t = localtime(&timer); printf("%d days %d hours %d minutes %d seconds\n", t->tm_yday, t->tm_hour, t->tm_min, t->tm_sec); } return 0; }
実行結果です。
Current: 1049041988, Mon Mar 31 01:33:08 2003 my_time: 1048857300, Fri Mar 28 22:15:00 2003 2 days 12 hours 18 minutes 8 seconds
ファイルやディレクトリの情報取得には,ヘッダ sys/stat.h
で定義されている関数
int stat(const char *path, struct stat *sb);
と構造体 stat
が利用できます。(低位の取得方法を知りたい方は,コマンド ls
のソースファイルでも。また,stat
関数は C99 の範囲外です。)関数 stat
の第1変数はファイル或いはディレクトリのパス,第2変数はファイル情報を確保するための構造体変数のアドレスです。構造体 stat
のメンバについては man 2 stat
で確認してください。
次は,ファイル情報取得のサンプルプログラムです。サイズ,最後のアクセス,変更時刻,そして最後のアクセスと変更時刻の差がプリントされます。
/* Example 18.16 */ #include <sys/types.h> /* stat(), struct stat */ #include <sys/stat.h> /* stat(), struct stat */ #include <stdio.h> /* fprintf(), printf() */ #include <errno.h> /* errno */ #include <string.h> /* strerror() */ #include <stdlib.h> /* exit() */ #include <time.h> /* time_t, ctime() */ int main(void) { struct stat filestat; char path[] = "/path/to/file"; time_t dtime; if(stat(path, &filestat) == -1) { fprintf(stderr, "* Error (%d) [stat: %s]\n", errno, strerror(errno)); exit(errno); } printf("Size: %ld\n", (long)filestat.st_size); printf("Last accessed: %ld, %s", filestat.st_atime, ctime(&filestat.st_atime)); printf("Last modified: %ld, %s", filestat.st_mtime, ctime(&filestat.st_mtime)); dtime = filestat.st_atime - filestat.st_mtime; printf("%ld\n", dtime); exit(0); }
実行例です。
Size: 86555 Last accessed: 1049485323, Sat Apr 5 04:42:03 2003 Last modified: 1049428803, Fri Apr 4 13:00:03 2003 56520
エラー番号 errno
とその意味 strerror()
については,存在しないファイルを path
に指定してみると良いでしょう。ちなみに,より簡単に標準エラー出力にエラーを出す方法は,stdio.h
にある perror
関数を利用することです。
perror("Error"); perror(NULL);
ファイルをロックするには,BSD系では sys/file.h
にある flock
関数が,他の UNIX系OS では fcntl.h
にある lockf
関数が使えます。ここでは,奥山研究室のサーバーでの実験を元に,flock
関数について紹介します。(但し,flock
関数は C99 の範囲外です。)
flock
関数は次の形をしています。(man 2 flock
で確認してください。)
int flock(int fd, int operation);
第1変数には fopen
関数などでオープンに成功したファイルのファイル・ディスクリプタを,第2変数にはロックの種別を指定します。ロックの種類は次の通りです。
LOCK_SH 1 /* shared lock */ LOCK_EX 2 /* exclusive lock */ LOCK_NB 4 /* don't block when locking */ LOCK_UN 8 /* unlock */
読み込むだけであれば LOCK_SH
を,書き込むならば排他ロックである LOCK_EX
を指定します。いずれの場合にしても,LOCK_NB
を付すと,他によってロックされている際には flock
関数は -1
を返します。
次は,相手にロックされている時は書き込みをせずにファイルを閉じるプログラムです。実行ファイルを異なる名称で2つ作成し,それらを同時に動かすと期待通りに動作することが確認できます。
/* Example 18.17 */ #include <stdio.h> /* fprintf(), printf(), fileno(), FILE */ #include <sys/file.h> /* flock() */ #include <string.h> /* strerror() */ #include <errno.h> /* errno */ #include <stdlib.h> /* exit() */ void myWrite(int, char *); int main(int argc, char *argv[]) { int n; for(n = 1; n <= 20000; n++) myWrite(n, argv[0]); exit(0); } void myWrite(int n, char *myname) { FILE* fptr; if((fptr = fopen("/path/to/you_want_to_write", "a")) == NULL) { fprintf(stderr, "*** %s (%d): %s\n", __FILE__, errno, strerror(errno)); exit(1); } if(flock(fileno(fptr), LOCK_EX | LOCK_NB) == -1) { printf("%s: Oops. Locked! (%d)\n", myname, n); if(fptr) { fclose(fptr); } return; } fprintf(fptr, "%s (%d)\n", myname, n); printf("%s: Yes, I could write! (%d)\n", myname, n); fclose(fptr); }