法大奥山研究室

 previous  contents

18.3. Tools


 道具箱です。


■環境変数

 以下のようにすると,環境変数が得られます。

/* 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関数を利用した文字列の分割は,既に見た通りです。(forExample 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 で利用する場合には,strnum 部分を QUERY_STRING や POSTデータから直接読み込まないでください。


itoa

 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 timertime.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);
}

 previous  contents