生涯未熟

生涯未熟

プログラミングをちょこちょこと。

treeコマンド 〜Rオプションの謎〜

treeコマンド、皆さん知ってますか?
そうです、あのファイルやらディレクトリやらを良い感じに木構造で出力してくれるニクい奴です。

f:id:syossan:20190103044420p:plain

こんな感じ

さて、そんなtreeコマンドには多くのオプションがあります。
よく使われるのは -L だったり、 -a とかですかね?

そんな中、 -R オプションというものが存在するのをご存知でしょうか?

Rオプションとは何なのか?

まずは、 --help オプションに書かれている内容を見てみましょう。

-R            Rerun tree when max dir level reached.

直訳すると 最大ディレクトリレベルに達したらツリーを再実行します。 ってとこですかね。
あー、再帰的にツリーを辿るのねと試しに以下のようなコマンドを実行してみました。

$ tree -L 1 -R -o result

これで1階層ごとにtreeの結果が出力されるじゃね?と思ってみました。(あまりやっても意味ないですが・・・
で、やってみた結果、ワーキングディレクトリの下にしかresultが吐かれませんでした・・・

さてはて、予想が外れたのですが冷静に次はmanコマンドを参照してみます。

-R     Recursively cross down the tree each level directories (see -L option), and at each of them execute tree again adding `-o 00Tree.html' as a new option.

これを直訳すると

再帰的に各レベルのディレクトリにツリーをたどります(-Lを参照)
そして、それらのそれぞれで、新しいオプションとして '-o 00Tree.html'を追加して、それぞれtreeを実行します。

オッ、 --help の時と違って新たな -o 00Tree.html という概念が出てきましたね。
ただ、先の実行結果を見渡してみても 00Tree.html とやらが吐き出されている様子は無い模様。
ますます頭にはてなマークが生えてきました。

treeコマンドのソースコードを読む

このままでは埒が明かないので、treeコマンドのソースコードを読んじゃいましょう。
C言語で書かれているので、そこまで難しくはないはずです。

ソースコードは以下のリンク先からよしなに取得してきてください。
The Tree Command for Linux Homepage

さて、それでは読んでいきたいと思います。

まず、何はともあれ -R オプションを追っかけましょう。

case 'R':
  Rflag = TRUE;
  break;

-R オプションはこのようにコード内では Rflag という変数に置き換えられます。
これを追いかけようと、tree.cの中を見てみたのですが、まさかの以下のコードしか参照しているところがありませんでした。

if (Rflag && (Level == -1))
  Rflag = FALSE;

んん?意味自体は分かりますが、これが再帰処理に繋がる気配は無さそうですね・・・
一応他のコードも読んでみたのですが、これまたまさかの html.c というファイル内に以下の意味ありげなコードが書かれていました。

/* This is really hackish and should be done over. */
if (Rflag && (lev == Level) && (*dir)->isdir) {
  if (nolinks) fprintf(outfile,"%s",(*dir)->name);
  else {
    fprintf(outfile,"<a href=\"%s",host);
    url_encode(outfile,d+1);
    putc('/',outfile);
    url_encode(outfile,(*dir)->name);
    fprintf(outfile,"/00Tree.html\">");
    html_encode(outfile,(*dir)->name);
    fprintf(outfile,"</a><br>\n");
  }

  hdir = gnu_getcwd();
  if (sizeof(char) * (strlen(hdir)+strlen(d)+strlen((*dir)->name)+2) > pathsize)
    path = xrealloc(path, pathsize = sizeof(char) * (strlen(hdir)+strlen(d)+strlen((*dir)->name) + 1024));

  sprintf(path,"%s%s/%s",hdir,d+1,(*dir)->name);
  fprintf(stderr,"Entering directory %s\n",path);

  hcmd = xmalloc(sizeof(char) * (49 + strlen(host) + strlen(d) + strlen((*dir)->name)) + 10 + (2*strlen(path)));
  sprintf(hcmd,"tree -n -H \"%s%s/%s\" -L %d -R -o \"%s/00Tree.html\" \"%s\"\n", host,d+1,(*dir)->name,Level+1,path,path);
  system(hcmd);
  free(hdir);
  free(hcmd);
}

うーーーーーん、This is really hackish and should be done over.というコメントから察するに、なんか難易度高そうっすね・・・
一応この処理は -H オプションを付けて実行した際のHTMLを構築する処理の一部になります。

HTML構築のためにディレクトリを -L で指定した深さまで辿っている途中に、この処理が出てくるのですが、ざっくり読み解いてみると、どうやらRオプションがあり、かつLオプションで指定した深さのディレクトリの場合にHTMLを出力しつつ、再帰的に tree コマンドを実行していますね。
manの説明にあった、 00Tree.html の存在も伺えます。どうやら再帰的に実行する際に、各ディレクトリに 00Tree.html を生成しているようです。

というわけで、なんとなく分かったこととして -R オプションは -H オプションと共に使わないと意味が無さそうってことですね。
では実験してみましょう。

実験

それでは -H オプションを付加してやってみましょう。

$ tree -L 1 -HR .

<!DOCTYPE html>
<html>
<head>
 <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
 <meta name="Author" content="Made by 'tree'">
 <meta name="GENERATOR" content="$Version: $ tree v1.8.0 (c) 1996 - 2018 by Steve Baker, Thomas Moore, Francesc Rocher, Florian Sesser, Kyosuke Tokoro $">
 <title>Directory Tree</title>
 <style type="text/css">
  <!--
  BODY { font-family : ariel, monospace, sans-serif; }
  P { font-weight: normal; font-family : ariel, monospace, sans-serif; color: black; background-color: transparent;}
  B { font-weight: normal; color: black; background-color: transparent;}
  A:visited { font-weight : normal; text-decoration : none; background-color : transparent; margin : 0px 0px 0px 0px; padding : 0px 0px 0px 0px; display: inline; }
  A:link    { font-weight : normal; text-decoration : none; margin : 0px 0px 0px 0px; padding : 0px 0px 0px 0px; display: inline; }
  A:hover   { color : #000000; font-weight : normal; text-decoration : underline; background-color : yellow; margin : 0px 0px 0px 0px; padding : 0px 0px 0px 0px; display: inline; }
  A:active  { color : #000000; font-weight: normal; background-color : transparent; margin : 0px 0px 0px 0px; padding : 0px 0px 0px 0px; display: inline; }
  .VERSION { font-size: small; font-family : arial, sans-serif; }
  .NORM  { color: black;  background-color: transparent;}
  .FIFO  { color: purple; background-color: transparent;}
  .CHAR  { color: yellow; background-color: transparent;}
  .DIR   { color: blue;   background-color: transparent;}
  .BLOCK { color: yellow; background-color: transparent;}
  .LINK  { color: aqua;   background-color: transparent;}
  .SOCK  { color: fuchsia;background-color: transparent;}
  .EXEC  { color: green;  background-color: transparent;}
  -->
 </style>
</head>
<body>
    <h1>Directory Tree</h1><p>
    <a href=".">.</a><br>
    ├── <a href="./LICENSE">LICENSE</a><br>
    ├── <a href="./Makefile">Makefile</a><br>
    ├── <a href="./README.md">README.md</a><br>
    ├── <a href="./cmd/00Tree.html">cmd</a><br>
Entering directory /Users/syossan27/go/src/go-tree/cmd
Entering directory /Users/syossan27/go/src/go-tree/cmd/go-tree
<br>
    ├── <a href="./dist/00Tree.html">dist</a><br>
Entering directory /Users/syossan27/go/src/go-tree/dist
<br>
    ├── <a href="./go.mod">go.mod</a><br>
    ├── <a href="./go.sum">go.sum</a><br>
    ├── <a href="./tree.go">tree.go</a><br>
    ├── <a href="./tree.html">tree.html</a><br>
    ├── <a href="./tree_windows.go">tree_windows.go</a><br>
    └── <a href="./validate.go">validate.go</a><br>
    <br><br>
    </p>
    <p>

2 directories, 9 files
    <br><br>
    </p>
    <hr>
    <p class="VERSION">
         tree v1.8.0 © 1996 - 2018 by Steve Baker and Thomas Moore <br>
         HTML output hacked and copyleft © 1998 by Francesc Rocher <br>
         JSON output hacked and copyleft © 2014 by Florian Sesser <br>
         Charsets / OS/2 support © 2001 by Kyosuke Tokoro
    </p>
</body>
</html>

おー、HTMLは無事出力されましたね。
この状態で、 00Tree.html とやらは各ディレクトリに出力されているのか確認します。

$ tree
.
├── LICENSE
├── Makefile
├── README.md
├── cmd
│   ├── 00Tree.html
│   └── go-tree
│       ├── 00Tree.html
│       └── main.go
├── dist
│   ├── 00Tree.html
│   ├── go_tree_darwin_386
│   ├── go_tree_darwin_amd64
│   ├── go_tree_freebsd_386
│   ├── go_tree_freebsd_amd64
│   ├── go_tree_freebsd_arm
│   ├── go_tree_linux_386
│   ├── go_tree_linux_amd64
│   ├── go_tree_linux_arm
│   ├── go_tree_netbsd_386
│   ├── go_tree_netbsd_amd64
│   ├── go_tree_netbsd_arm
│   ├── go_tree_openbsd_386
│   ├── go_tree_openbsd_amd64
│   ├── go_tree_windows_386.exe
│   └── go_tree_windows_amd64.exe
├── go.mod
├── go.sum
├── tree.go
├── tree.html
├── tree_windows.go
└── validate.go

オッ、たしかに出てますね。

で、出力されたHTMLを見てみると以下のような感じになります。

f:id:syossan:20190103053127p:plain

ほほー?一応cmdディレクトリのリンクを踏んでみましょうか。

f:id:syossan:20190103053158p:plain

なるほど、リンク先もtree構造のHTMLになってますね。
これが -R オプション無しだと、リンク先を踏んだ場合にはこんな感じになります。

f:id:syossan:20190103053251p:plain

普通にブラウザでローカルディレクトリを覗いた時の画面ですね。
という形の結果になりました。

まとめ

結局のところ -R オプションってなんぞ?というところなのですが、これはtreeコマンドの開発者が単に内部的に00Tree.htmlを吐く機構が欲しかったから作ったのでは・・・?という感想があります。
CHANGELOGとかも遡って見たり、インターネット上を彷徨ったりしたのですが特に有力な情報は見つからず・・・

あと、ちょっと気になるところとして 00Tree.html の親ディレクトリへのリンク先を押しても戻ることが出来ず、リンク先の出力ロジックがなんか間違えてるんじゃないかと。
こういった時のバグの報告とかどうやればいいのかちょっと謎なので、treeコマンドに詳しい方、情報お待ちしております。