擬似tree

 ディレクトリ・ファイルの構成を確認したいとき、treeコマンドをよく使う。

$ tree
.
├── fuga
│   └── index.html
└── hoge
    ├── fuga
    │   └── piyo
    │       └── index.html
    ├── index.html
    └── text.txt

4 directories, 4 files


 ただ、たまにtreeコマンドが使えない&treeコマンドをインストールできない環境を利用することがある。
 そのような環境でディレクトリ・ファイル構成を知りたいときに使えるスクリプトを考えてみようと思った。
 当然、このような発想を実現した先駆者は既に存在しており、「treeコマンド 再現」とかで調べれば答えを知ることができる。
 ただ、今回は勉強も兼ねて自分でゼロから考えてみたいと思ったので、そのようなネタバレ?はあえて調べずに検討してみた。
 なので当然自分の再現結果は最適化されたものではない可能性が高く、実際に利用するときは他のつよつよエンジニアの方が考案された擬似treeコマンドを利用する方がいいと思う。


 先に結論を書いておく。

$ find . | sed 's/[^\/]*\//\//g; s/^\/\([^\/]*\)$/├── \1/g; s/\/\([^\/]*\)$/└── \1/g; s/\//    /g; s/^ /│ /g'
.
├── hoge
│    └── index.html
│    └── fuga
│        └── piyo
│            └── index.html
│    └── text.txt
├── fuga
│    └── index.html



 このコマンドを細かく分解していく。

 配下のファイル一覧をfindコマンドで出力するとこんな感じになる。

$ find .
.
./hoge
./hoge/index.html
./hoge/fuga
./hoge/fuga/piyo
./hoge/fuga/piyo/index.html
./hoge/text.txt
./fuga
./fuga/index.html

 この状態からsedを駆使していい感じにtreeっぽくしていく。

 見た感じ<ディレクトリ名/空白に置き換えればいい感じに階層になりそう。

% find . | sed 's/[^/]*\//   /g'
.
   hoge
      index.html
      fuga
         piyo
            index.html
      text.txt
   fuga
      index.html

 もうこれでいい気がしてきた。


 まあ、そういうわけにもいかないのでもう少し考えてみる。
 まず、最下層以外のファイル名はツリー構造に影響を与えないのでカットする。

エスケープを非表示にした置換処理はこんなイメージ

$ find . | sed 's/[^\/]*\//\//g'
.
/hoge
//fuga
///piyo
////index.html
/fuga
//index.html



 最上層のファイル名を├── ファイル名にする。

$ find . | sed 's/[^\/]*\//\//g; s/^\/\([^\/]*\)$/├── \1/g'
.
├── hoge
//index.html
//fuga
///piyo
////index.html
//text.txt
├── fuga
//index.html



 最下層のファイル名を└── ファイル名にする。

% find . | sed 's/[^\/]*\//\//g; s/^\/\([^\/]*\)$/├── \1/g; s/\/\([^\/]*\)$/└── \1/g'
.
├── hoge
/└── index.html
/└── fuga
//└── piyo
///└── index.html
/└── text.txt
├── fuga
/└── index.html



 階層を表現するため、/を空白3つに変換する。

$ find . | gsed 's/[^\/]*\//\//g; s/^\/\([^\/]*\)$/├── \1/g; s/\/\([^\/]*\)$/└── \1/g; s/\//    /g'
.
├── hoge
    └── index.html
    └── fuga
        └── piyo
            └── index.html
    └── text.txt
├── fuga
    └── index.html


 最後に先頭をでつなげる(先頭の空白をに置き換える)。

$ find . | gsed 's/[^\/]*\//\//g; s/^\/\([^\/]*\)$/├── \1/g; s/\/\([^\/]*\)$/└── \1/g; s/\//    /g; s/^ /│ /g'
.
├── hoge
│    └── index.html
│    └── fuga
│        └── piyo
│            └── index.html
│    └── text.txt
├── fuga
│    └── index.html




 ドンッ!!

 悪くない気がする。
 findsedコマンドだけでここまで綺麗に表現できるとは思わなかった。
 第2階層以下でを表示できないのが気になるが、を表示するかどうかは直後の行に依存するので今回は諦めた(findの表示順番を考慮実現できるかもしれない)。
 実際はtreeコマンドを使える機会が多いのでこれを利用する頻度はそんなにないと思うけど、思ったより簡単なsedで再現できた。

 どうでもいいが、sedの置換処理はエスケープがあると何やってるかわかりづらいから人に説明するときはエスケープを除いたコマンドを見せる方が良さそうだなと思った(コピペされないように注意する必要もありそうだけど)。