lsっぽいコマンドを作る

作成:

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

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

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

ファイル指定できるようにする

このシリーズの一番最初に作成したコマンドから、引数にディレクトリパスを指定することが可能なように作ってきた。

$ ./ls12 -l /proc/tty
dr-x------   2     root     root         0 07/12 20:46 driver
dr-xr-xr-x   2     root     root         0 07/12 20:46 ldisc
-r--r--r--   1     root     root         0 07/12 20:46 drivers
-r--r--r--   1     root     root         0 07/12 20:46 ldiscs

しかし、ファイルを直接指定することはできない。指定するとエラーとなる。

$ ./ls12 -l /proc/tty/drivers
/proc/tty/drivers: Not a directory

ファイル名が指定された場合は指定されたファイルについての情報を表示したいだろう。 今回はファイル名の直接指定ができるような対応を行う。

まず、引数にディレクトリではなく、ファイルを指定した場合、 Not a directoryと表示されている。 これはlist_dir関数の opendirが失敗したため、戻り値がNULLとなり、 perrorによって表示されているメッセージである。

static void list_dir(struct dir_path *base) {
    const char *base_path = base->path;
    ...
    dir = opendir(base_path);
    if (dir == NULL) {
      perror(base_path);
      return;
    }

一番最初に、与えられたパスの情報をstatを使って読み出し、ファイルかディレクトリかを判定する方法もあるだろう。 しかし、ディレクトリでないということは、通常のファイルかそもそもパスに読み出し可能なものが何もないかのどちらかなので、 このエラーをディレクトリかどうかの判定に使ってもよさそうだ。 最初にディレクトリとして扱い、エラーになった場合はファイルとして扱えるように、 list_dirのエラー処理の一つとしてファイル情報出力を実装することにする。

ファイルの情報を取得するには、すでに作成済みのnew_info関数を使用する。 必要な引数はファイルのパスと、ファイル名だ。 この時点ではパスの情報しかないので、パスからファイル名を抽出する関数を作成する。

static const char *find_filename(const char *path) {
  int i;
  size_t path_len = strlen(path);
  for (i = path_len;i >= 0; i--) {
    if (path[i] == '/') {
      return &path[i+1];
    }
  }
  return path;
}

やっていることは単純で、パスの末尾から走査してゆき、 最初に現れたセパレータ(/)の一つ手前までをファイル名として戻している。 セパレータが見つからない場合はパス全体をファイル名と見なす。

ここまでできたら、あとはopendirがエラーとなったところに、 ファイル情報を出力するためのコードを入れるだけでよい。

dir = opendir(base_path);
if (dir == NULL) {
  if (errno == ENOTDIR) {
    const char *name = find_filename(base_path);
    struct info *info = new_info(base_path, name);
    if (info != NULL) {
      print_info(info);
      free(info);
    }
  } else {
    perror(base_path);
  }
  return;
}

これでファイル名を指定された場合は、そのファイルの情報を表示させることができるようになった。

$ ./ls13 -l /proc/tty/drivers
-r--r--r--   1     root     root         0 07/12 21:31 drivers

この修正自体はあまり意味のあるものではないが、次回への布石である。