ラベル yes(1) の投稿を表示しています。 すべての投稿を表示
ラベル yes(1) の投稿を表示しています。 すべての投稿を表示

11/14/2011

FreeBSD と GNU coreutils の yes(1) のコードを読んで

すっごくすっきりしてる FreeBSD の yes(1) の実装と、GNU coreutils のお決まり処理をいろいろやってる GNU coreutils の yes(1) の実装。僕はすっきり実装の FreeBSD のほうが好きだなあ。

今回初めて知ったのは、FreeBSD と GNU coreutils の yes(1) の挙動の違い。

Ubuntu (11.04) で yes a b c を実行すると、
$ yes a b c | head -n1
a b c
 て感じだけど、FreeBSD (8.2-RELEASE) だと
% yes a b c | head -n1
a
になっちゃう。シェルスクリプトとかで yes(1) を使うときでスペース区切りの文字列を扱いたければダブルクォートとかでくくればどっちの場合でも動きそうだ。

ふたたび、Ubuntu (11.04) だと、
$ yes "a b c" | head -n1
a b c
 って感じだし、FreeBSD (8.2-RELEASE) も
% yes "a b c" | head -n1
a b c 
ということで、おんなじ結果になる。よかったよかった。

ところで、yes(1) の使い道についてのヒントは en.wikipedia.org の yes (Unix) の記事に書いてある。へー、(テスト目的とかで?)適当なファイルを作りたい時にも便利なんだねー。

よし、今度は tail(1) に挑戦してみよー。ちょっと難しそうだけど...


11/13/2011

GNU coreutils の yes(1) を読む(後編)

GNU coreutils の yes(1) を読む(前編) の続き、yes.c の残りは次のようになっている。
     75   if (argc <= optind)
     76     {
     77       optind = argc;
     78       argv[argc++] = bad_cast ("y");
     79     }
     80
     81   for (;;)
     82     {
     83       int i;
     84       for (i = optind; i < argc; i++)
     85         if (fputs (argv[i], stdout) == EOF
     86             || putchar (i == argc - 1 ? '\n' : ' ') == EOF)
     87           {
     88             error (0, errno, _("standard output"));
     89             exit (EXIT_FAILURE);
     90           }
     91     }
最初の if 文は、引数の数(argc)が argv の中で次に処理されるべき要素を示すインデックス(optind)以下の場合に実行される、ということで、典型的には yes(1) が引数無しで呼ばれたときに if 文の中に入る。if 文の bad_cast() は、src/system.h で定義されていて、const char * を引数にして、char * で返す関数。const だったものを強引に const 外すから、bad_ なのかな。

次の while 文が、yes(1) の出力処理を実行してる。argv の optind から順に fputs していって、最後の要素を fputs したら改行文字を putchar(3) する。というわけで、

$ yes a b c
のように実行すると、"a b c" が出力されるわけだ。
$ yes
みたく、引数がない場合は、argv[optind] に "y" が設定されるから、"y" が連続して出力される。

この yes.c を読んで気になったのは、78行目、
     78       argv[argc++] = bad_cast ("y");
の部分。これ、メモリ破壊起こすんじゃないのかな....argv の要素数は argc で与えられているわけで、argv[argc] は与えられた領域を超えちゃってるような。ググって調べてみたけど、78行目みたいなことができる根拠がわからなかった。。誰か教えて!!

argc <= optind になる場合とか、そのほかいろんな制約がわかってないけど、たとえば、↓のようになってればすっきりするんだけど。。
@@ -74,8 +74,8 @@

   if (argc <= optind)
     {
-      optind = argc;
-      argv[argc++] = bad_cast ("y");
+      optind = argc - 1;
+      argv[optind] = bad_cast ("y");
     }
ま、いいやっ!

11/06/2011

GNU coreutils の yes(1) を読む(前編)

FreeBSD に続き、GNU coretutils の yes.c を前編、後編の2回に分けて読んでみる。対象のソースは Ubuntu 11.04 上で以下のコマンド実行してダウンロードできた coreutils-8.5 の yes.c。
$ apt-get source coreutils
 今回は前編。yes.c の main() 関数の前半部分、yes(1) のメイン処理に移る前の部分を読む。
     62   initialize_main (&argc, &argv);
     63   set_program_name (argv[0]);
     64   setlocale (LC_ALL, "");
     65   bindtextdomain (PACKAGE, LOCALEDIR);
     66   textdomain (PACKAGE);
     67
     68   atexit (close_stdout);
     69
     70   parse_long_options (argc, argv, PROGRAM_NAME, PACKAGE_NAME, Version,
     71                       usage, AUTHORS, (char const *) NULL);
     72   if (getopt_long (argc, argv, "+", NULL, NULL) != -1)
     73     usage (EXIT_FAILURE);
[initialize_main]
gnu.core-utils.bugs に Bernard Giroud さんが投稿したメイルによると、initialize_main() は VMS ていうシステム上で redirect を実現するためのアイデアが起源になってるらしい。VMS 上では必ずしも bash (シェル) 上でコマンドが実行されなくて、redirect するのにちょっと工夫がいるみたい。この VMS は OpenVMS のことかな?

initialize_main() の定義は coreutils/src/system.h にある。さっきの VMS とか、そのほか、ちょっと変わった環境で redirect とか実現したい場合は、system.h 内の initialize_main() を適切な形で定義すればいい。こうすれば、それぞれのコマンドのソースを変えなくて済むからイケてる感じ。
/* Redirection and wildcarding when done by the utility itself.
   Generally a noop, but used in particular for native VMS. */
#ifndef initialize_main
# define initialize_main(ac, av)
#endif
[set_program_name]
set_program_name() は argv[0] から実行されたコマンド名だけを取り出して program_name ていうグローバル変数に設定する。program_name 変数は lib/progname.h で extern 定義されてるから、set_program_name(argv[0]) した後は、program_name 変数にアクセスすることでコマンド名が取れるってこと。
/* String containing name the program is called with.  */
extern const char *program_name;
[setlocale, bindtextdomain, textdomain]
このあたりは、ロケール関係。いままで使ったことないから、これはどっかで改めて勉強しよう。

[atexit]
atexit(3) はプロセスが通常終了するときに呼ばれるべき関数を登録する。yes.c だと、標準出力を閉じてそうな、close_stdout() を atexit(3) で登録してる。atexit(3) もいままで使ったことなかったけど、これは便利そうだ。

[parse_long_options]
yes(1) が --help か --version 付きで呼ばれたときの設定をする。--help と --version は coreutils のコマンドで共通みたい。このあたりの作りもかっこいいな。

[getopt_long]
第三引数の "+" の意味は man 3 getopt に書いてある。
If  the first character of optstring is '+' or the environment variable POSIXLY_CORRECT is set, then option  processing  stops as soon as a nonoption argument is encountered.
yes.c の getopt_long(...) を呼び出してるところ、オプション以外の引数があったらそこで解析をやめるし、何かオプションが指定されてたらそれはエラーだよ、って意味。getopt_long(3) は正しく解析できたときに -1 を返すから。

---
後編に続く。

10/30/2011

FreeBSD の yes(1) を読んでみる。

yes(1) は引数で渡された文字列か、"y" を繰り返し表示するコマンド。
お勉強のために、FreeBSD の HEAD にある rev. 1.6 を読んでみる。

プログラムの本体はすっごいすっきり。無駄がない。
int
main(int argc, char **argv)
{
 if (argc > 1)
  while (puts(argv[1]) != EOF)
   ;
 else
  while (puts("y") != EOF)
   ;
 err(1, "stdout");
 /*NOTREACHED*/
}
puts(3) は "return a nonnegative integer on success and EOF on error." だそう。err(3) はエラーメッセージを標準エラー出力に出してくれる。

yes.c の先頭のほうの、#ifndef lint から始まるあたりについては、openbsd-newbies って ML で Chris Palmer さんがやさしく教えてくれるう。
#ifndef lint
static const char copyright[] =
"@(#) Copyright (c) 1987, 1993\n\
 The Regents of the University of California.  All rights reserved.\n";
#endif /* not lint */

#ifndef lint
#if 0
static char sccsid[] = "@(#)yes.c 8.1 (Berkeley) 6/6/93";
#else
static const char rcsid[] = "$FreeBSD: src/usr.bin/yes/yes.c,v 1.6 2010/12/11 08:32:16 joel Exp $";
#endif
#endif /* not lint */
この copywrite とか rcsid はバイナリに埋め込んでおくってことだと思ったんだけど....strings(1) とかで見えないなあ。。ま、また、なんかのときに調べてみよ。

ところで、
$ yes a b c
を FreeBSD の上でやると、"a" が連続して表示されるんだけど、cygwin 上だと、"a b c" が連続されて表示される。今度、GNU coreutils の yes(1) を読んでみよう