C言語入門(46) 実際にプログラムを読んで理解を深めよう tail編(1)

Posted: 2013年06月27日

前回まででC言語の勉強に適していると言われる
lsコマンドのソースを解説してきました。

そのシリーズとして今回からtailコマンドのソースを解説していきます。

tailsコマンドは、

  • ファイルの最後の方のデータを表示
  • ファイルの値をリアルタイムで表示

します。

よくログファイルの監視に使用したりします!!

最新のtailコマンドのソースは
http://www.freebsd.org/cgi/cvsweb.cgi/src/usr.bin/tail/
です。

今回解説するソース
/wp-content/uploads/2013/06/tail.zip
です。

前回と同様に初回はエントリーポイントから
tail.c main関数です。

プログラムプログラム:

int
main(int argc, char *argv[])
{
    struct stat sb;
    const char *fn;
    FILE *fp;
    off_t off;
    enum STYLE style;
    int i, ch, first;
    file_info_t *file;
    char *p;

    /*
     * Tail's options are weird.  First, -n10 is the same as -n-10, not
     * -n+10.  Second, the number options are 1 based and not offsets,
     * so -n+1 is the first line, and -c-1 is the last byte.  Third, the
     * number options for the -r option specify the number of things that
     * get displayed, not the starting point in the file.  The one major
     * incompatibility in this version as compared to historical versions
     * is that the 'r' option couldn't be modified by the -lbc options,
     * i.e. it was always done in lines.  This version treats -rc as a
     * number of characters in reverse order.  Finally, the default for
     * -r is the entire file, not 10 lines.
     */
#define ARG(units, forward, backward) {
    if (style)
        usage();
    off = strtoll(optarg, &p, 10) * (units);
    if (*p)
        errx(1, "illegal offset -- %s", optarg);
    switch(optarg[0]) {
    case '+':
        if (off)
            off -= (units);
            style = (forward);
        break;
    case '-':
        off = -off;
        /* FALLTHROUGH */
    default:
        style = (backward);
        break;
    }
}

    obsolete(argv);
    style = NOTSET;
    off = 0;
    while ((ch = getopt(argc, argv, "Fb:c:fn-2436:qr")) != -1)
        switch(ch) {
        case 'F':   /* -F is superset of (and implies) -f */
            Fflag = fflag = 1;
            break;
        case 'b':
            ARG(512, FBYTES, RBYTES);
            break;
        case 'c':
            ARG(1, FBYTES, RBYTES);
            break;
        case 'f':
            fflag = 1;
            break;
        case 'n':
            ARG(1, FLINES, RLINES);
            break;
        case 'q':
            qflag = 1;
            break;
        case 'r':
            rflag = 1;
            break;
        case '?':
        default:
            usage();
        }
    argc -= optind;
    argv += optind;

    no_files = argc ? argc : 1;

解説解説:

コマンド引数の確認を行なっています。
defineで定義されているARGは、
引数値の+ -を解釈するために用意しています。

-n +5

のように使用します。

obsolete関数では、下位互換性を保証する関数です。
近いうちに解説します。

プログラムプログラム:

    /*
     * If displaying in reverse, don't permit follow option, and convert
     * style values.
     */
    if (rflag) {
        if (fflag)
            usage();
        if (style == FBYTES)
            style = RBYTES;
        else if (style == FLINES)
            style = RLINES;
    }

    /*
     * If style not specified, the default is the whole file for -r, and
     * the last 10 lines if not -r.
     */
    if (style == NOTSET) {
        if (rflag) {
            off = 0;
            style = REVERSE;
        } else {
            off = 10;
            style = RLINES;
        }
    }

解説解説:

引数に応じて表示形式を変更します。

-n +5 -r
-n -5

と同様の意味をなすような処理を行なっています。

プログラムプログラム:

    if (*argv && fflag) {
        files = (struct file_info *) malloc(no_files *
            sizeof(struct file_info));
        if (!files)
            err(1, "Couldn't malloc space for file descriptors.");

        for (file = files; (fn = *argv++); file++) {
            file->file_name = strdup(fn);
            if (! file->file_name)
                errx(1, "Couldn't malloc space for file name.");
            if ((file->fp = fopen(file->file_name, "r")) == NULL ||
                fstat(fileno(file->fp), &file->st)) {
                if (file->fp != NULL) {
                    fclose(file->fp);
                    file->fp = NULL;
                }
                if (!Fflag || errno != ENOENT)
                    ierr(file->file_name);
            }
        }
        follow(files, style, off);
        for (i = 0, file = files; i < no_files; i++, file++) {
            free(file->file_name);
        }
        free(files);
    } else if (*argv) {
        for (first = 1; (fn = *argv++);) {
            if ((fp = fopen(fn, "r")) == NULL ||
                fstat(fileno(fp), &sb)) {
                ierr(fn);
                continue;
            }
            if (argc > 1 && !qflag) {
                (void)printf("%s==> %s <==n",
                    first ? "" : "n", fn);
                first = 0;
                (void)fflush(stdout);
            }

            if (rflag)
                reverse(fp, fn, style, off, &sb);
            else
                forward(fp, fn, style, off, &sb);
        }
    } else {
        fn = "stdin";

        if (fstat(fileno(stdin), &sb)) {
            ierr(fn);
            exit(1);
        }

        /*
         * Determine if input is a pipe.  4.4BSD will set the SOCKET
         * bit in the st_mode field for pipes.  Fix this then.
         */
        if (lseek(fileno(stdin), (off_t)0, SEEK_CUR) == -1 &&
            errno == ESPIPE) {
            errno = 0;
            fflag = 0;      /* POSIX.2 requires this. */
        }

        if (rflag)
            reverse(stdin, fn, style, off, &sb);
        else
            forward(stdin, fn, style, off, &sb);
    }
    exit(rval);
}

解説解説:

状況に応じて表示するものを変えています。
-fをつけた場合複数ファイルを許可するため
複数のファイルをオープンします。

通常のばあいファイルをひとつづつオープンします。

それ以外の場合
標準入力を用いて処理を行います。

このデータは、
reverse,forward関数にて実際に表示処理が行われます。

カテゴリー: プログラム, 入門 | タグ: , , | コメント無し »

コメント