lsっぽいコマンドを作る

作成:

UNIX 環境のコマンドラインを触ったことがある人ならどんな人でも使ったことがあるコマンドの一つ、 lsっぽいコマンドラインプログラムを作りながら、そのために必要な要素について解説していく。 あくまで「っぽい」であり、本物と全く同じものを作るわけではないので注意。

説明に使用するプログラムコードについては GitHub で公開している。 ソースコードの全文をよく見たい、ダウンロードしたいなどの場合はこちらを参照してほしい。

今回は ls14.c を利用した説明になる。

複数のパスを指定できるようにする

これまで作ってきたコマンドでは、表示する対象のパスを引数で指定することができたが、 複数のパスをまとめて指定したくても対応していなかった。 今回は複数のパスをまとめて渡されることに対応させ、渡されたパスすべてについてリスト表示を行うようにする。

といっても、今まで作ってきたコードはこれについても見越した構造になっているため、 必要最小限の修正でこの対応を盛り込むことができる。

サブディレクトリの再帰的表示の改善において、 表示すべきパスをスタックとして保持するリンクリストを作成している。 今までは、渡されたパスを先頭要素として作成し、ループを開始していたが、 複数のパスが渡された場合は、単純にこのスタックに渡された分のパスを積んでおくだけでよい。

コマンドライン引数をパースするparse_cmd_args関数の戻り値をdir_path *に変更し、 表示すべきパスのスタックをこの中で作成してしまうようにする。


static struct dir_path *parse_cmd_args(int argc, char**argv) {
...
  if (argc <= optind) {
    return new_dir_path("./", 0, NULL);
  } else {
    struct dir_path *head;
    struct dir_path **work = &head;
    int i;
    for (i = optind; i < argc; i++) {
      *work = new_dir_path(argv[i], 0, NULL);
      work = &(*work)->next;
    }
    return head;
  }
}

このように、引数リスト処理の最後にパラメータが余っていなければカレントパスのみのスタック、 パラメータが余っていれば、それらをすべて指定パスと捉え、リンクリストのスタックに積み込み、先頭構造体のポインタを返すようにする。

修正点はこれでほぼ全部である。 main関数でのループにこの構造体を突っ込めば先頭から順番に処理されていく。


int main(int argc, char**argv) {
    struct dir_path *head = parse_cmd_args(argc, argv);
    if (head == NULL) {
      return EXIT_FAILURE;
    }
    while(head != NULL) {
      if (head->depth != 0) {
        printf("\n%s:\n", head->path);
      }
      list_dir(head);
      struct dir_path *tmp = head;
      head = head->next;
      free(tmp);
    }
    return EXIT_SUCCESS;
  }

これで、引数で複数のパスを指定できるようになった。 複数のパス指定は単純に羅列する方法もあるが、ワイルドカードを使うこともできる。 ワイルドカードはシェルによって展開され、プログラム側からは複数のパスが引数で指定された状態に見えるのだ。

$ ./ls14 -l *.c
-rw-r--r--   1  ryosuke  ryosuke       737 03/03 12:00 ls1.c
-rwxr-xr-x   1  ryosuke  ryosuke      9688 07/12 20:41 ls10.c
-rwxr-xr-x   1  ryosuke  ryosuke     10852 07/12 20:41 ls11.c
-rwxr-xr-x   1  ryosuke  ryosuke     14843 07/12 20:41 ls12.c
-rwxr-xr-x   1  ryosuke  ryosuke     15517 07/12 21:11 ls13.c
-rwxr-xr-x   1  ryosuke  ryosuke     15688 07/12 21:11 ls14.c
-rw-r--r--   1  ryosuke  ryosuke      1334 03/03 12:00 ls2.c
-rw-r--r--   1  ryosuke  ryosuke      2217 03/03 12:00 ls3.c
-rw-r--r--   1  ryosuke  ryosuke      3738 03/03 12:00 ls4.c
-rw-r--r--   1  ryosuke  ryosuke      5170 03/03 12:00 ls5.c
-rwxr-xr-x   1  ryosuke  ryosuke      5686 07/12 20:40 ls6.c
-rwxr-xr-x   1  ryosuke  ryosuke      6958 07/12 20:41 ls7.c
-rwxr-xr-x   1  ryosuke  ryosuke      7203 07/12 20:41 ls8.c
-rwxr-xr-x   1  ryosuke  ryosuke      9045 07/12 20:41 ls9.c        

第一回から少しずつ改良を重ねてきたlsっぽいコマンド。 もちろん本物のlsに比べればまだまだ足りないところはたくさんあるが、 普通に使えるレベルの機能を備えるようになったのではないだろうか。

今一度、コード全体を眺めてみよう。 ls14.c

可読性を意識し、エラー対策も盛り込み、コメントまで含め、 600行程度の1ファイルに収まる小さなプログラムだ。 特に難解なアルゴリズムが含まれているわけでもなく、 これまでの解説を読めば、入門書を卒業したばかりの初心者でも理解できるようなコードになっていると思う。

一方で、linuxのAPIをたたいたり、ユーザー入力を処理したりと、 演習用課題のような意味の無いプログラムではなく、 実用プログラミングの要素がたくさん詰め込まれていると思う。

入門書を卒業した初心者がステップアップするのにちょうどよい教材にもなるのではないだろうか。