.. _tool: 最低限必要となるツール ======================================================================= コードリーディングに最低限必要と考えらえる3つのツールについて説明します。 ソースコードファイルの閲覧 less 文字列検索 grep ソースコードツリーの巡回 find, xargs この演習は様々なツールの使い方を覚えることを目的とはしていません。 地道にフローを追うというコードリーディングの本質を体験してもらうために、 基本的なツールについてだけ説明することにしました。 コードリーディングを実践している他者に聞いてみた経験では、読解者のリー ディングのスタイルによって好まれるツールは違うようです。またプラット フォームや対象となるソースコードの記述に使われている言語によっては利用 できないツールもあります。基本的なツールはスタイルによらず役に立ちます。 また多くのプラットフォームで利用可能で、しかも言語を選びません。 .. _less: ソースコードファイルの閲覧 ----------------------------------------------------------------------- もし使い慣れたテキストエディタがあれば、それを使って閲覧するのが良いで しょう。Fedoraでは主要なエディタとして、emacs, gedit, viが利用できます。 エディタを使う場合、オープンした読解の対象となるソースコードファイルを意図 せず変更して、さらに保存してしまうことが無いように注意して下さい。ファイルを リードオンリーモードで読み込む、あるいはchmodコマンドを使ってファイル自体の書 き込み権限を削除しておくと良いでしょう。 もし使い慣れたテキストエディタがなければ、ページャー(less)が使えます。 筆者の同僚でソースコードをゴリゴリと読む人のなかには使い慣れたテキストエディ タがあるにもかかわらずlessを使っている人もいます。 lessコマンドは引数で指定したファイルを開きます。 + *N* というオプションを追記 すると開いた直後にファイルの先頭を表示するかわりに、 *N* 行目から表示を開始します。 以降に延べる通り、grepによる文字列検索の結果には、その文字列が出現した行の行番号が番号 が含まれます。 + *N* オプションは、その照合箇所を確認するのに便利です。 foo.cの50行目以降を表示する例:: $ less +50 foo.c ファイルを開いた後にlessは対話的な操作を受けつけます。 以下に特に重要なものを列挙します。 q 終了 SPACE(あるいは^F) 次ページへ移動 ^B 前ページへ移動 \/ 文字列検索(前向き) ? 文字列検索(後ろ向き) n 次の検索結果 N 前の検索結果 *N* g ファイル中の*N* 行目へ移動 .. _grep: 文字列検索 ----------------------------------------------------------------------- grepコマンドを使うと引数で与えたファイル群(FILES)に対してまとめて文字列検索を かけることができます。 :: grep [OPTIONS] -n -e PATTERN FILES... FILESで指定したファイル群に対してPATTERNとマッチする行を表示します。 PATTERNには正規表現を指定します。後述する正規表現のメタ文字を含まない限り文字 列をそのまま指定すれば、それもまた正規表現のサブセットとしてそのまま検 索できます。検索の結果として、指定したファイル群に対して、その文字列を 含むファイル名、行番号、その行を列挙できます。ただし検索文字列をシン グルクォートで囲んだ方が良い場合があります。PATTERN中の文字列がシェルの メタ文字として解釈されるのを避けるためです。 :: $ grep -n -e '__init' audit.c audit_tree.c audit_watch.c auditfilter.c capability. audit.c:937:static int __init audit_init(void) audit.c:968:__initcall(audit_init); audit.c:971:static int __init audit_enable(char *str) audit_tree.c:943:static int __init audit_tree_init(void) audit_tree.c:956:__initcall(audit_tree_init); audit_watch.c:520:static int __init audit_watch_init(void) auditfilter.c:157:int __init audit_register_class(int class, unsigned *list) capability.c:30:static int __init file_caps_disable(char *str) たくさんあるオプションのうち -n と -e をここで先に紹介します。 -n 検索結果に行番号を含める。 -e 検索パターンを明示的に指定する。検索パターンが マイナス記号でで始まる場合、grepからはそれがオプションな のかパターンなのか区別がつきません。-eと指定することで 後続の引数がたとえマイナス記号で始まっていたとし てもPATTERNであると認識します。逆にマイナス記号で 始まらないPATTERNについてはこの-eオプションを省略でき ます。 ファイルの指定 ,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, シェルのグロブ機能を使うとFILESの指定が簡単になります。 現在のディレクトリ中の 全ての.cファイルを対象に検索する:: grep -e PATTERN *.c 現在のディレクトリ中の 全ての.cファイルおよびヘッダファイルを対象に検 索する:: grep -e PATTERN *.[ch] -rオプションを使うとFILES部分にディレクトリが含まれている場合、そのディ レクトリ以下のファイルも全て検索の対象となります。 :ref:`find` で説明するfindコマンドを使うと、より詳細にファイルを指定で きます。例えばあるディレクトリ以下の全ての.cファイルを指定できます。 検索文字列の指定 ,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, 次の正規表現メタ文字を使うと検索を詳細に制御できます。 メタ文字の一部を紹介します。 ^ 行頭にマッチする。 \$ 行末にマッチする。 \. 任意の一文字にマッチする。 [] 括弧の間の一文字とマッチする。 \? 直前の表現に0回か1回マッチする。 \* 直前の表現に0回以上マッチする。 \\+ 直前の表現に1回以上マッチする。 \\b 単語の端の空文字列にマッチする。 \\t タブ文字にマッチする。 これらのメタ文字をただの文字として検索するには\\を前置します。 コードリーディングで良く使うのは ``^``, ``[ \t]``, ``\b`` です。 コマンドラインオプション ,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, \-i 大文字小文字を無視して検索する。 \-v マッチしない行を表示します。 \-B *N* マッチした前 *N* 行も表示する。 \-A *N* マッチした後 *N* 行も表示する。 \-l マッチした行を表示せずファイル名だけを表示する。 \-L マッチしなかったファイル名だけを表示する。 具体的な利用例 ,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, 検索結果に含まれるノイズ(マッチしてしまったが関心の無い検索結果)を下げるため、 あるいは目的の箇所にうまくマッチさせるため、の姑息なテクニックを紹介します。 文字列リテラル ....................................................................... 痕跡文字列として文字列リテラルを検索したくなることがあります。たとえGUI アプリケーションのメニューバーにある File メニュー の定義箇所を探すために :: $ grep -n -e File *.c としたくなります。ところがこのパターンでは、 ``FileDialog`` 、 ``TemporaryFile`` など 文字列リテラル とは関係の無い名前にも マッチしてしまいます。こういった場合、ダブルクォート(``"``)自体を パターンに含めてしまうと良いでしょう。 :: $ grep -n -e '"File"' *.c あるいは :: $ grep -n -e \"File\" *.c とします。 シングルクォートで囲まれた文字定数を探す場合も同様にします。 シングルクォート自体はシェルに解釈されてしまうので、 :: $ grep -n -e "'c'" *.c あるいは :: $ grep -n -e \'c\' *.c とします。 書式付き出力 ....................................................................... 痕跡文字列が書式付き出力経由で表示されていた場合、その痕跡文字列をそのまま 検索しても、対応するコードポイントに辿りつけません。 次のような痕跡文字列を考えます。 :: 3 errors are found. これが実は、 .. code-block:: c printf("%d errors are found.\n", count); というコードを経て出力されている場合、 :: $ grep -n -e '3 errors are found\.' *.c としても検索にひっかかりません。書式付き出力で展開されていそうな箇所 には、 ``.+`` を使って任意の文字列が適合するようパターンを調整 します。 :: $ grep -n -e '.\+ errors are found\.' *.c あるいは、いっそうのこと痕跡文字列を見て可変と考えられる部分をパターンから 削ってしまっても良いかもしれません。 :: $ grep -n -e 'errors are found\.' *.c .. 空白 .. ....................................................................... .. C言語は区切り文字に空白。。。 関数定義 ....................................................................... ある関数の定義をその名前から探そうとすると、関数の呼び出し箇所が多数マッチ してしまい、その中に定義箇所が埋もれてしまう、ということがあります。通常、 目視でそれを選び出します。関数呼び出しの箇所には実引数が与えられている一方、 関数定義箇所には、仮引数がその型ともに与えられているはずです。 :: $ grep -n -e 'foo' *.c a.c:7: x = foo(3); a.c:20: y = foo(a); b.c:20 foo(int i) b.c:35: if (foo(a) < 0) { ただし標準化される以前のc言語では、仮引数を列挙する箇所に型を記載しない ことに注意して下さい :: void inittimeouts(val, sticky) register char *val; bool sticky; { ... /* Taken from sendmail-8.14.4/sendmail/readcf.c */ 対象のソフトウェアが採用しているインデントスタイルによっては、正規表現メタ 文字 ``^`` を使って関数定義だけにマッチさせることができます。 関数定義において、修飾子と返り値の型の後に関数名を記述する、という スタイルがあります。このとき関数名が行頭に来ます。 :: const char * dissector_handle_get_long_name(const dissector_handle_t handle) { このようなスタイルに従って記述されていると期待できる場合、 ``^`` *関数名* という パターンで関数定義だけにマッチさせることができます。 :: $ grep '^dissector_handle_get_long_name' *.c 構造体定義 ....................................................................... 関数の定義と同様に構造体定義についても検索しようとすると、その構造体型を使って 定義/宣言されている変数、その構造体型を引数あるいは返り値にとる関数の定義/宣言が マッチしてノイズにまみれます。 :: $ grep -nH -e vector s.c s.c:1:struct vector { s.c:6:struct vector s.c:7:vector_add(struct vector a, struct vector b) s.c:13:vector_innter_product(struct vector a, struct b) s.c:15: struct vector tmp; ... 対象のソフトウェアが採用しているインデントスタイルによっては、構造体定 義だけにマッチさせることができます。構造体定義において、フィールドの定 義の開始を意味する ``{`` が構造体名の直後、改行を入れずに記載するという インデントスタイルのソースコードの場合、 :: struct vector { ... 次のパターンで構造体定義だけにマッチさせることができます。 :: $ grep 'struct vector \+{' *.c 構造体名の直後に改行を入れて ``{`` を記述するというインデントスタイルの ソースコードの場合、 :: struct vector { ... 次のパターンで構造体定義だけにマッチさせることができます。 :: $ grep 'vector foo$' *.c .. _find: ソースコードツリーの巡回 ----------------------------------------------------------------------- find ,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, findコマンドを使うとあるディレクトリ以下にあるファイルのうち、指定した条件 を満す各ファイルに対して指定したコマンドを実行できます。 読解中のソースコードツリーのトップレベルにカレントディレクトリがあると します。典型的に使うのは次のようなコマンドラインです。 :: find . -type f -exec grep -nH -e PATTERN {} \; この例ではカレントディレクトリ(.)以下の各ファイル(-type f)に対して grepを実行します(-exec grep -nH -e PATTERN {} ``\``;)。 -execオプションで適用したいコマンドラインを指定します。 * ``\``; でコマンドラインの末尾を表現します。 * コマンド実行時に {} の部分が列挙したファイルと置き換えらます。 これを.cファイルとヘッダファイルだけに限定するには、-nameオプションを使います。 :: find . -type f -name '*.[ch]' -exec grep -nH -e PATTERN {} \; -execを指定しない場合ファイルの名前を列挙できます。 :: find . -type f -name '*.[ch]' xargs ,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, * 標準入力を読み込んであらたにコマンドラインを作成して、起動します。 * 読み込んだ行(LINE)毎にコマンドラインを作成して、起動します。 * xargsの引数をベースに、LINEを連結してコマンドラインを作ります。 :: find . type f -name '*.[ch]' | xargs grep -nH -e PATTERN エディタからの利用 ----------------------------------------------------------------------- エディタの種類によっては、その内部からgrepを呼び出して検索結果を活用で きるものがあります。 (近代的な)vi ,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, :: :grep PATTERN FILE-OR-WILDCARD として FILE-OR-WILDCARD 中から PATTERNを探すことができます。 :: :copen 検索結果の一覧を表示できます。一覧のなかで RETURN を押すと該当 する行を、そのエディタのセッションの中で開くことができます。 Emacs ,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, :: M-x grep Run grep (like this): grep --color -nH -e PATTERN PATTERN FILE-OR-WILDCARD として FILE-OR-WILDCARD 中から PATTERNを探すことができます。`*grep*`と いうバッファがオープンするので、その中で関心のある行を探し、RETURNを押 すと該当する行を、そのエディタのセッションの中で開くことができます。