GNU coreutils: trueコマンドのコードを読む

さてド素人の私がcoreutilsでも読むかー

coreutils のなかでも、たぶん一番シンプルなコマンドは true だろう!そうに違いない。

Githubのソースコードはこちら。短いから全部コピペだ:

#include <config.h>
#include <stdio.h>
#include <sys/types.h>
#include "system.h"

/* Act like "true" by default; false.c overrides this.  */
#ifndef EXIT_STATUS
# define EXIT_STATUS EXIT_SUCCESS
#endif

#if EXIT_STATUS == EXIT_SUCCESS
# define PROGRAM_NAME "true"
#else
# define PROGRAM_NAME "false"
#endif

#define AUTHORS proper_name ("Jim Meyering")

void
usage (int status)
{
  printf (_("\
Usage: %s [ignored command line arguments]\n\
  or:  %s OPTION\n\
"),
          program_name, program_name);
  printf ("%s\n\n",
          _(EXIT_STATUS == EXIT_SUCCESS
            ? N_("Exit with a status code indicating success.")
            : N_("Exit with a status code indicating failure.")));
  fputs (HELP_OPTION_DESCRIPTION, stdout);
  fputs (VERSION_OPTION_DESCRIPTION, stdout);
  printf (USAGE_BUILTIN_WARNING, PROGRAM_NAME);
  emit_ancillary_info (PROGRAM_NAME);
  exit (status);
}

int
main (int argc, char **argv)
{
  /* Recognize --help or --version only if it's the only command-line
     argument.  */
  if (argc == 2)
    {
      initialize_main (&argc, &argv);
      set_program_name (argv[0]);
      setlocale (LC_ALL, "");
      bindtextdomain (PACKAGE, LOCALEDIR);
      textdomain (PACKAGE);

      /* Note true(1) will return EXIT_FAILURE in the
         edge case where writes fail with GNU specific options.  */
      atexit (close_stdout);

      if (STREQ (argv[1], "--help"))
        usage (EXIT_STATUS);

      if (STREQ (argv[1], "--version"))
        version_etc (stdout, PROGRAM_NAME, PACKAGE_NAME, Version, AUTHORS,
                     (char *) nullptr);
    }

  return EXIT_STATUS;
}

ちなみに false コマンドはこんななってて共通化されてます:

#define EXIT_STATUS EXIT_FAILURE
#include "true.c"

おーなるほど、面白いなー、とか思ったけども、私はこーゆーのはあんまり好きじゃないな…。抽象化したなら true とか false とか意識しないコードになってるべきだけど、usage() のとこで EXIT_STATUS == EXIT_SUCCESS ? ... みたいなコードが洩れてるから、これは抽象化というか、単によく似たコードを圧縮しただけだろう…

しかし、そこへいくとOpenBSDの trueこれですよ:

int
main(int argc, char *argv[])
{
	return (0);
}

そして falseこれ

int
main(int argc, char *argv[])
{
	return (1);
}

まぁそうだよね、みたいなシンプルさ。逆にいうと GNUの trueに書かれているコードはGNU的に省略できない最低限のものなんだろう、きっと。

であらためてGNUのほうをみると、helpversion オプションがおそらく必須なのと、あとは gettext の国際化がある。ちょっと面白いのは、51行目のコメントにあるが、オプションがあるせいで trueコマンドが失敗する可能性がある、という。そうすると一応エラー用の設定もしておかねばならんとなり…

そう思うと、OpenBSDのコードはいきがって短くしてるわけでもなく、true が失敗すると意味論的にも不明瞭になるので余計なことはしない!という気概を感じる。

ちなみに私は神は信じておりますが、OpenBSDの信者ではありません。

私はド素人なので、気になったところを少し見てくと、

      initialize_main (&argc, &argv);

これは普通は何もしないようだ。OS/2のときに何かあるらしい。

      set_program_name (argv[0]);

プログラム名をセットする。特定のパス(<dirname>/.libs/ とか)は削るが、普通はそのまま。コメントに書いてある(GNUはこういうコメントが丁寧だな)、例えばユーザーが

$ /some/hidden/place/bin/cp foo foo

と打ったら /some/hidden/place/bin/cp: `foo' and `foo' are the same file というエラーメッセージになるべきだ、ということらしい。これは前から少し不思議に思ってたが、そうあるべきということだったのか。

      setlocale (LC_ALL, "");
      bindtextdomain (PACKAGE, LOCALEDIR);
      textdomain (PACKAGE);

ここは gettext の設定。PACKAGE="coreutils"LOCALEDIR="/usr/local/share/locale" になっていた。ちなみに翻訳ファイル (.po と .gmo) は、どこから作られたのか分からんが(というかソースコードなんかよりあのmakeファイル読むほうがよほどしんどいだろ、どう考えても…)、ビルドすると po/ja.po に置かれる。あといつのまにか横に .gmo ファイルも作られている。なのでこれをLOCALEDIR下にコピーすれば日本語も何となく出ている:

$ cp po/ja.gmo /usr/local/share/locale/LC_MESSAGES/coreutils.mo

あと gettextが入れ子になっている部分があるが(下のコード)、この N_ を消すと上の文字列だけが翻訳対象になってしまうらしい。そういう謎テクニックらしい。そうなの?

  printf ("%s\n\n",
          _(EXIT_STATUS == EXIT_SUCCESS
            ? N_("Exit with a status code indicating success.")
            : N_("Exit with a status code indicating failure.")));

で最後に謎なのが、この標準出力を閉じるコードなのだが…

      atexit (close_stdout);

このコードはcoreutilsでは共通して呼ばれている。close_stdout はgnulibの関数で、ちょろっと書いてあるコメントによれば、”標準出力のクローズでエラーがあるようなときは、非0で終了するのは重要なのだ、なぜなら多くのツール、特に make とかのビルド管理系は、終了ステータスで失敗を検知するから”、と。

coreutilsのコマンドはビルドでよく使われるものも多いから、念を入れてこういうコードが埋まっている、ということだろうか?少し検索すると gnulibのメーリングリストでこの件に関して文句いわれてるスレッドがある( Re: Why does close_stdout close stdout and stderr? )。話題が難しすぎて理解できないが、何か闇の入り口てきなものを感じますわ…

試しにコメントアウトしてビルドして、何か問題が起きそうなサンプルが作れるか試してみたが、ちょっとできませんでした。

まとめ

true コマンドはcoreutilsのテンプレみたいなシンプルなコマンドだけど、もうすでにGNUの深遠を感じるんだなぁ  ぐぬを

コメント

タイトルとURLをコピーしました