(→ English )

1 概要

Gomiko は Windows のゴミ箱のように、ファイルを削除するのではなく一時的にストックしておく機構を提供します。 『UNIX 今日の技/シェル/ゴミ箱を実装する』 でやったように、 私はゴミ箱をシェル上で実装していました。 これで私の要求の 99 % は満足するのですが、 長く使っていると以下のようにちょっと不足に感じる部分もでてきました。

gomiko は gomiko コマンドを提供します。 gomiko コマンドは rm の変わりに ~/.trash にファイルを移動する機能を持ちます。 また削除履歴の表示や undo が備えられています。 Gem としての gomiko は、 Gomiko ライブラリを提供します。 Ruby プログラム上からもゴミ箱を扱えるように設計してあります。

1.1 名前について

gomiko という名前は、日本語の「ゴミ箱」をもじって名付けられました。 「碁を打つ巫女さん」だと思えば可愛いでしょ?

2 インストール

Rubygems を利用しています。 Rubygems を使って、以下のようにインストールできます。

gem install gomiko

2.1 設定(お好みで)

設定は基本的には不要です。 ~/.trash も自動で生成します。 ~/.trash が gomiko 以前から存在していてそこにファイルが入っている場合は、 これらについて少なくとも undo が機能しなくなると思うので、 空にしておいた方が無難でしょう。

gomiko を本格的に使うことに決めたならば、 rm を gomiko rm のエイリアスにした方がいいと思います。 私は ~/.zshrc に以下のように設定しています。

alias rm='gomiko rm'

私は、 undo, empty などのサブコマンドは gomiko undo などのように打てばいいと考えて、 特にエイリアスを作っていません。

3 コマンド

gomiko コマンドを用意しています。 種々の機能はこのサブコマンドとして提供されます。

3.1 gomiko help

引数なし、もしくはサブコマンド help を指定して実行するとヘルプが出ます。

% gomiko

% gomiko help

以下のように help のあとにサブコマンド名を指定すると、サブコマンド rm についてのヘルプがでます。

% gomiko help rm

3.2 gomiko rm

% gomiko rm foo.txt

通常の rm コマンドに置き換わるものです。 削除する変わりに ~/.trash ディレクトリに移動させます。 移動したファイル名は、 「~/.trash/日付-時刻/元のフルパス」 のようになっています。 この 「日付-時刻」は、gomiko を起動したタイミングであって、 内部的に移動が実行された時刻ではありません。 たとえば「gomiko rm *」によって多数のファイルを削除して1秒以上時間がかかっても、 一つの 「日付-時刻」に入れられます。

以下のように、複数のファイルを同時に指定できます。

% gomiko rm foo.txt bar.txt */*.txt

以下のように、ディレクトリもオプションなしでゴミ箱に移動できます。

% gomiko rm dir1/ dir2/

同じ時刻に複数の gomiko rm をした場合、 最初のものは 「~/.trash/日付-時刻」というディレクトリ名になりますが、 次以降は 「~/.trash/日付-時刻-1」 のように時刻のあとに連番で作っていきます。 排他制御のロックディレクトリのような仕組みになっており、 ディレクトリを生成できたプロセスのみがそのディレクトリに入れるようにしているので 複数マシンで NFS 共有しているようなファイルシステムでも安心です。

以後、~/.trash/ 直下にあるファイル・ディレクトリを「ゴミIDディレクトリ」、 そのディレクトリ名を「ゴミ ID」と呼びます。 gomiko rm 経由ではなく手動で作られたものであっても、 ゴミIDディレクトリ、ゴミIDとして使用できます。 (undo などのコマンドが意図通り動くかは別問題ですが。)

3.3 gomiko ls

~/.trash の中身を表示します。

% gomiko ls
size date-time-id    typical-path[ ...]
20K  20170623-125159 /home/ippei/tmp/gomiko/0
20K  20170623-125810 /home/ippei/tmp/gomiko/5 ...
84K  20170627-084500 /home/ippei/git/gomiko/test/gomiko/tmp/
24K  20170627-091649 /home/ippei/git/gomiko/a/
32K  20170627-091712 /home/ippei/git/gomiko/test/gomiko/b (exist in original path)
100K 20170627-092407 /home/ippei/git/gomiko/test/gomiko/tmp/

1行目は情報のタイトル行。 2行目と3行目がデータです。 1カラム目の size は du で取得できるサイズです。 2カラム目が削除対象と推測されるパスの代表です。 この推測は、ゴミIDディレクトリを再帰的に掘り、 実際のフルパス上に存在しないファイル・ディレクトリを提示しています。 2カラム目に続けて「...」とあるのは、代表以外にも推測されるパスがある、 すなわち複数ファイルを1コマンドでゴミ箱に移したと推測されることを示します。

削除した元のパスにファイルが存在するものが含まれていれば、 以下のように (may exist newer file) と出力します。

20K  20170623-125810 /home/ippei/tmp/gomiko/5 ...(may exist newer file)

引数に ゴミ ID を指定できます。

% gomiko ls 20170623-125159 20170623-125810
size date-time-id    typical-path[ ...]
20K  20170623-125159 /home/ippei/tmp/gomiko/0
20K  20170623-125810 /home/ippei/tmp/gomiko/5 ...

ゴミ ID のかわりにパスでも OK です。

% gomiko ls ~/.trash/20170623-125*
size date-time-id    typical-path[ ...]
20K  20170623-125159 /home/ippei/tmp/gomiko/0
20K  20170623-125810 /home/ippei/tmp/gomiko/5 ...

3.3.1 --long, -l オプション

--long(-l) オプションを使うとより詳しい情報を出力します。

% gomiko ls --long
------------------------------------------------------------
size      : 20K
id        : 20170628-112425
guess path: /home/ippei/tmp/gomiko/0
+----- filetype in trash
| +--- filetype in original path
| | +- original path
/ / /home
/ / /home/ippei
/ / /home/ippei/tmp
/ / /home/ippei/tmp/gomiko
.   /home/ippei/tmp/gomiko/0

------------------------------------------------------------
size      : 20K
id        : 20170628-112425-1
guess path: /home/ippei/tmp/gomiko/1
+----- filetype in trash
| +--- filetype in original path
| | +- original path
/ / /home
/ / /home/ippei
/ / /home/ippei/tmp
/ / /home/ippei/tmp/gomiko
.   /home/ippei/tmp/gomiko/1

------------------------------------------------------------
size      : 20K
id        : 20170628-112425-3
guess path: /home/ippei/tmp/gomiko/3
+----- filetype in trash
| +--- filetype in original path
| | +- original path
/ / /home
/ / /home/ippei
/ / /home/ippei/tmp
/ / /home/ippei/tmp/gomiko
.   /home/ippei/tmp/gomiko/3

ゴミ ID ごとに横線で区切っています。 容量(size), ゴミ ID(id), 推測された元パス(guess path), その下はゴミIDディレクトリに格納されている全ディレクトリ・ファイルの情報で、 各行の最初の要素はゴミIDディレクトリ内のファイルタイプ、 次の要素は現在のファイルツリー上の元パスのファイルタイプです。 ファイルタイプを示す文字の意味は以下です。

3.4 gomiko undo

サブコマンド undo はファイル削除のアンドゥ機能を提供します。 以下のように引数なしで実行すると ゴミIDディレクトリのうち、最も新しいものの中身を元に戻します。

% gomiko undo

undo によって該当の ゴミIDディレクトリを削除するので、 さらに gomiko undo を実行すると、その次に新しい ゴミIDディレクトリをアンドゥします。 すなわち、続けて gomiko undo を実行することで、削除をどんどん遡れます。

削除した元パスにファイルが存在していれば、 該当するファイルに関しては警告を出して移動を実行しません。 それ以外のファイルは戻します。

% gomiko undo
normal file already exist: /home/ippei/tmp/gomiko/0
Cannot undo: /home/ippei/.trash/20170623-133644

引数に undo を実行するゴミID を指定することができます。

% gomiko undo 20170623-133644

複数指定できます。(この場合、ワイルドカード展開は普通できません。)

% gomiko undo 20170623-133644 20170623-123456

パスでも指定できます

% gomiko undo ~/.trash/20170623-*

3.5 gomiko empty

~/.trash ディレクトリの中身を空にします。 引数を省略した場合は ~/.trash に入っている全てのファイル・ディレクトリを対象とします。 gomiko empty は gomiko rm 非経由で ~/.trash に入っているファイル・ディレクトリも 対象とします。

引数にゴミ ID を指定することができます。

% gomiko empty 20170628-112425-2

ゴミ ID ではなくパスで指定することもできます。

% gomiko empty ~/.trash/20170628-112425-2

3.5.1 --mtime オプション

~/.trash の中身を定期的に削除していくことはしばしば行われています。 その時にある段階で空にしてしまうと、ついさっき ~/.trash に移動したファイルが 削除されてしまうというタイミングもありえます。 タイムスタンプを見て、たとえば 7 日経過したファイルなら削除、ということが 常套手段です。 --mtime オプションはこれを実現します。

% gomiko empty --mtime=-7

で、7日以上経過したものを削除します。 find の -mtime オプションを参考に記法を決定したので、 通常は負数を指定することになるでしょう。 --mtime オプションの引数のデフォルト値は 0 で、 過去に作られたゴミIDディレクトリを全て対象とします。 このタイムスタンプは、 ゴミ ID ディレクトリのタイムスタンプ(更新時刻) を見ています。 ファイル名から日付を取得していませんので、 ファイル名を変更しても日付判定は変化しません。

引数のゴミ ID 指定と --mtime オプションを併用したときは、 AND 条件で両方に合致するものを選択します。 (メモ:OR 条件にすることも検討したけれど、 OR でやりたければコマンド2回発行すれば済むし、 AND 条件はもうちょっと手間がかかるのでまだしも価値がありそうだから。 そもそも併用することはあまりないと思うけど。)

3.5.2 --quiet オプション

cron で動かすときには標準出力のログは不要でしょう。 そういうときには --quiet オプションで標準出力を抑制することができます。

% gomiko empty --quiet

手動で empty サブコマンドを実行するときは 何かしら表示された方が嬉しいので、デフォルトでは --quiet が off になっています。

4 ライブラリ

Gomiko.new で Gomiko オブジェクトを生成し、 Gomiko#throw を使うことで、 プログラムからゴミ箱を利用できます。

5 却下した機能

5.1 NFS 共有ファイルを削除するときにローカルに移動してくると時間がかかる

この要求に対する解決法は困難です。 要は ~/.trash に相当するゴミ箱ディレクトリをどのように置くか、という問題になります。 df コマンドなどでファイルシステムの情報が得られますが、 シンボリックリンクやパーミッションなど様々な要因が絡んで自動的に判定することが困難です。 たとえばシンボリックリンクを展開した絶対パスを上に辿り、 同一ファイルシステムかつ自分に書き込み権限のある最上位のディレクトリに ゴミ箱ディレクトリを作るという方針を想定してみましょう。 しかしこれだと /tmp 内で削除を行うと /tmp/.trash というディレクトリを作ることになります。 あまり良いお作法とは言えないでしょう。 その上、ゴミ箱ディレクトリが複数になり、 対象のファイルがどこに格納されたのかが分かり難くなります。 極めて複雑な処理をするわりには得られるメリットは薄いし、デメリットも少なからずあるということです。 オプションを作ってゴミ箱ディレクトリの場所を指定するくらいなら可能でしょうが、 そのオプションも今のところ作る予定はありません。

5.2 mkdir と ディレクトリ指定

gomiko rm する度(もしくは内部で Gomiko#throw を投げる度)に新しいゴミIDディレクトリが作られ、 gomiko undo の粒度がその単位になっていまます。 プログラムからファイルを削除させるときに、 「そこで1つのプロセスで共通のゴミIDディレクトリにまとめたい」という要求は、ありえることです。 解決するには gomiko mkdir でゴミIDディレクトリを作り、 そのディレクトリを指定して gomiko rm や Gomiko#throw を実行するという手法がありえます。 技術上はさほど難しくはないのですが、 プログラムのロジックとして問題を孕むことになります。 一つのゴミIDディレクトリに複数のファイル削除が同じパスになりうるというのが問題です。 仮想的に gomiko mkdir と gomiko rm に --id オプションを実装したと仮定して、 シェルスクリプトで考えてみましょう。

DIR=`gomiko mkdir` #ゴミIDディレクトリを作らせて、その ID を返す
touch a
gomiko rm --id=$DIR a #ゴミIDディレクトリを指定してそこに放り込む。
touch a
gomiko rm --id=$DIR a #同じゴミIDディレクトリの同じパスに放り込まれる筈。

2度目の gomiko rm で重複が生じます。 異なる時点のファイルツリーを一つにまとめているため、 このような問題が生じうるわけです。 プログラムとしては、削除処理を発行した時点のファイルツリーのスナップショットとしてゴミIDディレクトリを位置付けるべきでしょう。

これらのことから、この機能を追加する予定はありません。

5.3 redo 機能

現在のところ、gomiko は ~/.trash 以下にあるファイルそのものをデータとして 処理を行っています。 履歴を別ファイルとして持たないことで、 情報の一意性をシンプルに保っています。 redo を行うには、undo で ~/.trash から削除してしまったファイルについて、 どこかに記録を置いておく必要があります。 そうすると情報の置き場所が一元化しにくくなり、 整合性を保つのに気を払わねばならなくなります。 gomiko 自体に redo 機能がなくても、 シェルのヒストリを使うことでほぼ同じことができる筈です。 ということで労多くして利少ない機能だと思うので、 この機能を追加する予定はありません。

5.4 ディレクトリ生成時刻の利用

ゴミIDディレクトリ内のディレクトリと 移動元のディレクトリの生成時刻を比較できれば、 より精度の高い削除ターゲット推測が可能でしょう。 たとえば、/a/b/c.txt というファイルがあって、 /a/bで削除したとしましょう。 存在する末端のファイル・ディレクトリは /a と ~/.trash/ゴミID/a/b という状態になる筈です。 その後 /a/b を生成すれば、 ~/.trash/ゴミID/a/b より /a/b の方が 生成時刻が後になる筈です。 一方 a に注目すれば、削除時点で既に存在していた /a より、削除によって生成された ~/.trash/ゴミID/a の方が後になる筈です。 このように削除の後で生成されたディレクトリかどうかをこのように判定できる筈なのですが、 Linux で主流の ext4 ファイルシステムでは ファイル・ディレクトリの生成時刻がタイムスタンプに記録されません。 よってこれは使えせん。 生成日時が使えるファイルシステムが主流になったころにまた検討しましょう。