ターミナルで「ごみ箱」シェルをサクッと作ろう

LinuxやmacOSのターミナルで「ごみ箱」を実現するシェルを「サクッと」作成してみます。

消して後悔しないためには

LinuxやmacOSのターミナル(端末)で rm コマンドを使ってファイルを削除すると、本気で消えてしまいます。ファイルシステム上、存在しなくなるので、特殊なツールと幸運に恵まれない限り、復活はできません。GUIの世界にある「ごみ箱」を漁って元に戻すことは不可能です。バックアップを取得していれば話は別ですが、そんな幸運に恵まれることはまれでしょう。

ターミナルの世界でごみ箱を実現するのは簡単です。
サクッとやってみませんか?

2行のシェルスクリプトで実現するには

まず、ごみ箱のディレクトリを作成します。ホームディレクトリに隠しディレクトリを作成します。

$ mkdir ~/.trash

次に以下のシェルスクリプトを作成します。
ファイル名は、”tra.sh”とします。

#!/bin/sh
mv $@ ~/.trash

コマンドの基本設計をしてみよう

上記だけでも使えますが、加えて次のような機能もあると便利です。

  1. ファイルをごみ箱に移す
  2. ごみ箱の中身を一覧表示する
  3. ごみ箱からファイルを復活
  4. ごみ箱を一括で空にする

上記の機能を、1機能ずつ4つのコマンドにするか、1コマンドにまとめて、オプションで機能を切り替えるかを検討します。1コマンドを1機能は非常にシンプルであり、Unix的な考え方といえますが、4つのコマンド名を覚えるのが大変かもしれません。
今回は1コマンドに集約することにします。

ここで、コマンドの基本設計を書いてみます。

  1. コマンド名と起動方法
    tra.sh [-elr] [file …]
  2. ファイルをごみ箱に移す
    tra.sh file …
    複数のファイルやディレクトリが指定できる。
  3. ごみ箱の中身を一覧表示する (list)
    tra.sh -l
  4. ごみ箱からファイルを復活 (restore)
    tra.sh -r file
    指定できるのは1ファイルのみとし、カレントディレクトリに戻す。
  5. ごみ箱を一括で空にする (empty)
    tra.sh -e
    ファイルは完全に消去され、復元できなくなる。

詳細設計を書いてみよう

続いて、詳細設計を作成します。

  1. ごみ箱ディレクトリ
    なかったら作成。
  2. オプションごとの処理
    • オプションもファイルも指定がない場合
      起動方法(usage)を表示して終了。
    • オプションがなく、ファイル名のみの場合
      ファイルをごみ箱に移動。
    • -l
      ごみ箱の中身を一覧表示。
    • -r
      復元するファイルを1つ指定しているかチェック。
      ファイルがごみ箱にあるかチェック。
      ごみ箱からファイルを復元。
    • -e
      ごみ箱の中身を一括削除。
  3. プログラム処理構造
    プログラムの骨格といえる処理の流れを設計します。具体的には、上記2の処理をどのような順序で実行するか、検討します。
    2の処理では、ごみ箱にファイルを移動する処理だけ、オプションの指定がありません。ファイルのみの指定です。
    そこで、どのオプションにも該当しない場合、ごみ箱に移動する処理が実行されるようにします。
    概略の処理フローは以下の通りです。
if "-l"
then
    ごみ箱の中を一覧表示
elif "-r"
then
    ごみ箱からファイルを復旧
elif "-e"
then
    ごみ箱を空にする
else
    ごみ箱にファイルを移動
fi

シェルスクリプトを書いてみよう

ごみ箱のファイル名を定義する

TRASH='.trash'

ごみ箱のディレクトリ名はいたるところに出てきそうなので、最初にシェル変数として定義しておき、変更する際には1か所の修正で済むようにします。

ごみ箱の作成

ごみ箱がまだなかったら、作成します。

if [ ! -d ~/$TRASH ]
then
    mkdir ~/$TRASH
fi

引数に応じた処理

引数:オプションもファイルも指定されていない場合

if [ $# -eq 0 ]
then
    echo "usage: $0 [-elr] file ..."
    exit 1
fi

usage(起動方法)を表示して終了します。

引数:-l の場合

if [ "$1" = "-l" ]
then
    ls -la ~/$TRASH/

ls コマンドでごみ箱の中身を一覧表示します。
サイズなど多くの情報が表示される -l(long listing) オプションと、”.”から始まる隠しファイルも表示できる -a(all) オプションを指定します。これで隠しファイルを削除しても表示できます。

引数:-r の場合

elif [ "$1" = "-r" ]
then
    # check if file exists
    if [ $# -ne 2 ]
    then
        echo "Bad argument count."
        exit 2
    elif [ ! -e ~/$TRASH/"$2" ]
    then
        echo "File not found."
        exit 3
    fi
    mv ~/$TRASH/"$2" .
    if [ $? -eq 0 ]
    then
        echo "File restored from trash."
    fi

-r の次にファイルが指定され、そのファイルが実在するかをチェックします。左記以外の場合は終了します。

mv コマンドで単一ファイルをごみ箱からカレントディレクトリに復元します。mv で生じたエラーはそのまま、表示されます。mv の終了ステータスコード($?)を確認し、正常終了(=0)してる場合のみ、成功のメッセージを表示します。
ごみ箱への移動と逆の動作です。ただし1ファイルのみです。

引数:-e の場合

elif [ "$1" = "-e" ]
then
    rm -rf ~/$TRASH/* ~/$TRASH/.[^.]*
    echo "Trash emptied."

rm コマンドでごみ箱の中身をすべて削除します。二度と復元できません。
-r(recursive) はディレクトリ階層ごと削除します。
-f(force) は問答無用で削除します。
“.”から始まる隠しファイルも削除します。”.”(カレントディレクトリ)と”..”(親ディレクトリ)は削除するとまずいので、左記以外のファイルを削除します。

引数:オプションがなく、ファイルのみの場合

else
    # move file to trash.
    mv "$@" ~/$TRASH/
fi

上記コードの else – fi 間が実行されるのは、予約語であるオプションに該当しなかった場合のみ、すなわちファイルのみが指定されているケースです。ここで、ごみ箱へのファイルの移動を行います。
mv コマンドで複数ファイルまたはディレクトリ(以降、ファイルと記述)をごみ箱に移動します。”$@”は指定されたファイルすべてに置き換えられます。
mv で生じたエラーはそのまま、表示されます。mv でエラーにならないファイルはすべて、ごみ箱に移動します。ここでは、mv の終了ステータスをチェックしません。なぜなら、終了ステータスがエラーを返した場合であっても、終了ステータスからどのファイルがエラーになり、どのファイルが移動されたか判別できないからです。

完成したシェルスクリプト

#!/bin/sh

# Define the directory name that is the substance of the trash.
TRASH='.trash'

# check if trash directory exists, if not create it
if [ ! -d ~/$TRASH ]
then
    mkdir ~/$TRASH
fi

# check if command line argument is provided
if [ $# -eq 0 ]
then
    echo "usage: $0 [-elr] file ..."
    exit 1
fi

# check if option is provided
if [ "$1" = "-l" ]
then
    ls -la ~/$TRASH/
elif [ "$1" = "-r" ]
then
    # check if file exists
    if [ $# -ne 2 ]
    then
        echo "Bad argument count."
        exit 2
    elif [ ! -e ~/$TRASH/"$2" ]
    then
        echo "File not found."
        exit 3
    fi
    mv ~/$TRASH/"$2" .
    if [ $? -eq 0 ]
    then
        echo "File restored from trash."
    fi
elif [ "$1" = "-e" ]
then
    rm -rf ~/$TRASH/* ~/$TRASH/.[^.]*
    echo "Trash emptied."
else
    # move file to trash.
    mv "$@" ~/$TRASH/
fi

コマンドの実行環境を整えるには

どこでも起動できるようにするには

シェルスクリプトに実行権を与えます。

chmod +x tra.sh

ディレクトリを作成し、シェルスクリプトを格納します。

mkdir ~/bin
mv tra.sh ~/bin

PATH環境変数にシェルの格納パスを追加します。

Linux(bash)の場合

ホームディレクトリにある、”.bashrc”に以下の行を追加します。

export PATH=$PATH:~/bin/

上記を bash に読み込ませて反映します。

. ~/.bashrc

macOS(zsh)の場合

ホームディレクトリの”.zshrc”に以下の行を追加します。”.zshrc”がない場合は作成します。

export PATH=$PATH:~/bin/

上記を zsh に読み込ませて反映します。

. ~/.zshrc

これで、どこのディレクトリからでもコマンド名だけで起動できるようになりました。

以下はmacOSからの実行例です。

apple@iMac12 ~ % pwd
/Users/apple
apple@iMac12 ~ % tra.sh
usage: /Users/apple/bin//tra.sh [-elr] file ...

さいごに

上記のスクリプトは、ごみ箱の中のファイル名とバッティングした場合が考慮されていないなど、使用するうえで改良したくなる要素が残っています。ぜひ、チャレンジしてみてください。