GNU Coreutilsソースコードの解析:yesユーティリティ

(記事はオフラインで読むことができます: Markdown | PDF | PDF(print) | HTML



なんで?



私たちの周りの誰もが常に言います:「あなたはプロのプログラムを書く方法を学びたいですか? 他の人がそれをどのように行うかを見てください!」 それで、特に大学の研究が終わりに近づいているので、このアドバイスに従うことにしました。 彼らが教えた方法と実際の世界でそれを行う方法を比較することは特に興味深い。 GNU Coreutilsパッケージは、従う例として選ばれました。 それにはすべてがあります:

  1. 厳しい移植性の要件。
  2. 大きなライフサイクル。
  3. 開発者の巨大なチーム。
  4. さまざまな複雑さのコード:些細なエコーから超洗練されたsed、純粋に適用されたwcからmkdir OSに近いものまで。


GNU Coreutils



GNU Core Utilitesは、基本的なユーザー操作を実行するための一連のユーティリティです:ディレクトリの作成、画面へのファイルの表示など。 開発者によると、これらのユーティリティはすべてのオペレーティングシステムで利用可能である必要があります。現在観察しているのは、Windows用のCygwinがありますが、* nixについては何も言えません。 Coreutilsが準拠しようとする POSIX標準は、異なるシステム間で作業の均一性を維持するのに役立ちます。 Coreutilsには、cat、tail、echo、wcなどの一般的に使用されるユーティリティが含まれています。



最初に、yesと呼ばれる最も単純なプログラムを選択しましょう。 そのシンプルさにより、Coreutilsで使用されるツールとライブラリを理解できます。



ユーティリティはい



マナが言うように、yesユーティリティでできることは、「yn」をstdoutに無限に出力することだけです。 yesに引数を渡すと、「y」の代わりにyesが引数をスペース付きで出力します。 確かに、同様のプログラムがCを勉強し始めたすべての人によって書かれました。つまり、多くの人が、GNUの厳しいひげを生やしたおじさんのやり方と彼らのアプローチを比較する機会を持っています。 実用的なアプリケーションについてはいはウィキペディアに少し書かれています。



ソースコード



ソースコードに渡します。 apt-get source



を使用して取得し、デフォルトでシステムで使用されているバージョンを取得するか、リポジトリから最新バージョンを取得します。 2番目のオプションを選択します。より便利で使いやすいです。

  1. Coreutils: git clone git://git.sv.gnu.org/coreutils



  2. Gnulib(数回そこを見てください): git clone git://git.savannah.gnu.org/gnulib.git





ソースコードは、はい1つのcoreutils/src/yes.c



coreutils/src/yes.c



、それを開きます。



コーディングスタイル



最初に注意することは、コードの異常なフォーマットです。 これについては、 対応する GNUコーディング標準ので読むことができます。 たとえば、関数を定義する場合、戻り値の型は左角括弧のように別の行にある必要があります。



 int main (int argc, char **argv) { foo(); ... }
      
      





インデントと配置にはスペースのみが使用されます。 ネストのレベルが異なる場合、インデントの違いは2スペースです。 演算子を使用した中括弧は、特にひねくれた形をしています。



 if (x < foo (y, z)) haha = bar[4] + 5; else { while (z) { haha += foo (z, z); z--; } return ++x + bar (); }
      
      





12行



yes.c



は、すべてのGPLプログラムの必須コメントで始まります。 彼はすでに他のプログラムで私の目のために祈っていたので、彼の存在の必要性は私にとって謎でした。 このコメントのテキストは、GPLの使用説明書で修正されていることがわかりました。 GPLの下でソフトウェアをリリースしたい人は誰でも、これらの12行のコピーライトステートメントを各ソースコードファイルの先頭に追加する必要があると書かれています。



initialize_main



プログラムが最初に行うことは、 initialize_main



呼び出すことです。 この関数は、プログラムが引数に対して特定のアクションを実行するためのものです。 実際には、Coreutilsには、この機能を有用なものに使用する単一のユーティリティはありません。 coreutils/src/system.h



あるスタブが使用されるcoreutils/src/system.h







 #ifndef initialize_main # define initialize_main(ac, av) #endif
      
      





プログラム名



Coreutilsユーティリティは、2つのプログラム名を区別します。

  1. ユーザーが変更できない正式名。
  2. 実行可能ファイルの実際の名前。


公式名は、アプリケーションのバージョン情報を表示するために使用されます。



 user@laptop:~$ yes --version yes (GNU coreutils) 8.5 Usage: yes [STRING]... or: yes OPTION
      
      





さらに、この名前は実行可能ファイルの名前に依存しません。



 user@laptop:~$ /usr/bin/yes --version yes (GNU coreutils) 8.5 user@laptop:~$ cp /usr/bin/yes ./foo user@laptop:~$ ./foo --version yes (GNU coreutils) 8.5
      
      





この動作は、ファイルの先頭で特別に定義されたPROGRAM_NAME



マクロによって保証されます。



 /* The official name of this program (eg, no `g' prefix). */ #define PROGRAM_NAME "yes"
      
      





本名はargv[0]



からトリックなしで取得され、エラーとヒントを表示するために使用されます。



 user@laptop:~$ yes --help Usage: yes [STRING]... or: yes OPTION user@laptop:~$ /usr/bin/yes --help Usage: /usr/bin/yes [STRING]... or: /usr/bin/yes OPTION
      
      





set_program_name



の2行目のset_program_name



関数を呼び出すことにより、 argv[0]



の値がグローバル変数program_name



配置されます。



 set_program_name (argv[0]);
      
      





set_program_name



関数set_program_name



Gnulibライブラリによって提供されます。 対応するコードは、 gnulib/lib/



ディレクトリのprogname.h



およびprogname.c



set_program_name



は、 argv[0]



値をprogname.h



で宣言されたグローバル変数program_name



保存するだけでなく、動的ライブラリを開発するためのツールであるGNU Libtoolの使用に関連する追加の変換も実行することに注意してください。



国際化



Coreutilsは世界中で使用されているため、すべてのユーティリティにローカライズする機能があります。 さらに、この機能は、 GNU gettextパッケージの使用により最小限の労力で提供されます。 このパッケージはGNUプロジェクトをはるかに超えて広がっているため、gettextの使用は少し驚くべきことです。 たとえば、私のお気に入りのDjango Webフレームワークの国際化は、 特にgettextに基づいて構築されています。 さまざまな言語やフレームワークと一緒にgettextを使用することについては、すでにHabrで書いています



gettextの優れた特性は、すべての言語でほぼ同じ方法で使用されることであり、Cも例外ではありません。 標準のマジック関数_



、その使用usage



usage



関数にあります:



 void usage (int status) { if (status != EXIT_SUCCESS) fprintf (stderr, _("Try `%s --help' for more information.\n"), program_name); ... }
      
      





_



関数の定義は、使い慣れたsystem.h



ファイルにあります。



 #define _(msgid) gettext (msgid)
      
      





Coreutilsの国際化メカニズムの初期化は、 main



3つの関数を呼び出すことにより行われます。



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







エラー処理



main



コードに沿ってさらに進むと、次の行があります。



 atexit (close_stdout);
      
      





直観的に、 close_stdout



関数は標準出力ストリームを閉じ、 stdout



を何らかのファイル記述子に置き換えてバッファリングされた出力を使用する場合、データ損失を排除すると考えるかもしれません。 しかし、リソースをクリーンアップするために追加のアクションが実行されるかどうかにかかわらず、この関数のソースコードを見つけて実際にそこで何が起こるかを理解することに成功しませんでした。



コマンドライン引数



これは、プログラム自体の作業に関係しない最後の質問です。 ここでは、国際化の場合と同様に、多くのプロジェクトにクロールされた実績のあるソリューション( Pythonなど-getoptモジュールを使用します。 このモジュールは非常に単純です。実際、開発者はループ内でgetopt



またはgetopt_long



関数のいずれかを呼び出す必要があります。 インターネットでgetoptの詳細を読むことができます。また、ハブについても書いています。



Gnulibにはparse_long_options



特別な関数があり、引数--version



および--help



を処理します。これは、GNUアプリケーションサポートする必要があります。 これはgnulib/lib/long-options.c



ファイルにあり、作業でgetopt_long



を使用します。



yesのソースコードは、getoptのクールな例です。 同時に、多数の引数を解析することで学習に不必要な複雑さはなく、すべてのgetoptツールの使用があります。 最初に、もちろん、 parse_long_options



呼び出しがparse_long_options



ます。 次に、これ以上キーオプションが渡されず、残りの引数があれば、それが単なる任意の文字列であることを確認します。



 parse_long_options (argc, argv, PROGRAM_NAME, PACKAGE_NAME, Version, usage, AUTHORS, (char const *) NULL); if (getopt_long (argc, argv, "+", NULL, NULL) != -1) usage (EXIT_FAILURE);
      
      





次のコードは、次のようにロシア語に翻訳できます。「コマンドラインの引数リストに--versionおよび--helpキー以外に何もなかった場合、「y」をstdoutに出力します」:



 if (argc <= optind) { optind = argc; argv[argc++] = bad_cast ("y"); }
      
      





argv[argc]



への書き込みはエラーでargv[argc]



ませんargv[argc]



標準では、 argv[argc]



要素がNULLポインターであることを要求しています。



メインサイクル



さて、私たちはプログラムのまさに機能性に到達しました。 そのままです:



 while (true) { int i; for (i = optind; i < argc; i++) if (fputs (argv[i], stdout) == EOF || putchar (i == argc - 1 ? '\n' : ' ') == EOF) error (EXIT_FAILURE, errno, _("standard output")); }
      
      





ここで、すべてのアクションは本体ではなくif



条件内で実行されることに注意してください。 そのため、KerniganとRitchieは、経験豊富なCプログラマーが次のような行のコピーを実装していると書いたときには嘘をつきませんでした。



 while (*dst++ = *src++) ;
      
      






All Articles