Gomiko (Japanese manual)

Ippei Kishida

Last-modified:2019/05/14 16:46:07.

(→ 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 をした場合、 最初のものは 「日付-時刻」という ID で格納されます。 以後、この ID を「ゴミ ID 」 と呼びます。 2番目以降は「日付-時刻-1」 のように時刻のあとに連番が ゴミID になります。 排他制御のロックディレクトリのような仕組みになっており、 ディレクトリを生成できたプロセスのみがそのディレクトリに入れるようにしているので 複数マシンで NFS 共有しているようなファイルシステムでも安心です。

~/.trash/ 直下に ゴミ ID でディレクトリを作り、その中に対象のファイルを移動します。 また ゴミID.yaml というファイルを作り、削除時の情報を格納します。

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カラム目が削除対象なったパスの代表です。 2カラム目に続けて「…」とあるのは、複数のファイルを対象としていたことを示します。

引数に ゴミ 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.3.2 –all, -a オプション

undo すると、~/.trash 内からデータがなくなります。 しかし、ゴミID.yaml ファイルは残ります。 -a オプションを付けると このように yaml ファイルのみのものも表示します。

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 になっています。

3.5.3 crontab サンプル

私は crontab に以下のように記述しています。 毎日 3:04 に実行されます。 ruby を入れないと、環境変数が上手くロードされないことがあるようです。 gomiko のパスは which gomiko で確認したものを入れると良いでしょう。

04 03 * * * ruby /home/ippei/.gem/ruby/2.3.0/bin/gomiko empty --mtime=-7 --quiet

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 の粒度がその単位になっています。 プログラムからファイルを削除させるときに、 「複数のファイル削除処理を共通のゴミ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ディレクトリを位置付けるべきでしょう。

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