プログラミング

Bashで”rm -rf *”コマンドが実行される前に警告するプロンプトを実装する

Linuxパソコン・サーバーのBashプログラミング言語(スクリプティング言語)上のコマンドラインにおいて、”rm -rf *”`コマンドが実行される前に警告するプロンプトを実装する方法について紹介します。

どうも。先日.bashrcが10000行を超えたのを皮切りに今更zshに乗り換えようか検討しています。yuisです。

Linuxコマンドrm -rfは最も危険なコマンドのひとつとして名高いですね。近年ではサーバー管理者が意図せずこのコマンドを実行してしまい全データを消してしまったという話も聞いたことがあります。
かく言う僕も、このコマンドにまつわるこんな経験があります。

それは、ある友人にMacOSXのレクチャーをしてあげていたときに起こりました。その友人はクソ不真面目な野郎で、それを承知の上で僕は、パソコンに疎いというその友人にメールのやり方やら色々と教えてあげていました。そしてある時、友人はお得意の「もうやだ。やめたい。才能がない」などと喚き散らし、それを発端としていつの間にやら、僕らはいつもの喧嘩をはじめていました。ほどなくして我慢の限界を超えた僕は、ついに例のコマンド、rm -rfを実行し、そしてそのMacは二度と起動することができなくなってしまいましたとさ。

※ このMacパソコンは契約のもの僕がプレゼントしてあげたものです。違法性はありませんのでご安心ください。

今回はそんなrm -rfコマンドの危険性を少しでも下げることができたらという思いつきから書いたコードを紹介します。

使用例

https://yuis.xsrv.jp/images/ss/ShareX_ScreenShot_2675b7f6-aeb0-4f5b-a77e-31850e7064d8.gif

$ tmpdird # 新規ディレクトリへ移動

$ ls
total 0
166351711236005864 drwxrwxrwx 1 yuis yuis 4096 Jul 10 21:59 ..
 23080948093026057 drwxrwxrwx 1 yuis yuis 4096 Jul 10 21:59 .

$ genfiles 3 # ファイルを生成

$ ls
total 0
166351711236005864 drwxrwxrwx 1 yuis yuis 4096 Jul 10 21:59 ..
  8444249304069546 -rwxrwxrwx 1 yuis yuis    2 Jul 10 21:59 3.txt
 23080948093026057 drwxrwxrwx 1 yuis yuis 4096 Jul 10 21:59 .
 51509920740553578 -rwxrwxrwx 1 yuis yuis    2 Jul 10 21:59 2.txt
 36310271996515302 -rwxrwxrwx 1 yuis yuis    2 Jul 10 21:59 1.txt

$ rm -rf * # 全ファイルを削除 > 確認プロンプト > n
W: It seems You are attempting to run kind of dangerous command. Continue? [y/n]
n

$ ls # ファイルが消されていないことを確認
total 0
166351711236005864 drwxrwxrwx 1 yuis yuis 4096 Jul 10 21:59 ..
  8444249304069546 -rwxrwxrwx 1 yuis yuis    2 Jul 10 21:59 3.txt
 23080948093026057 drwxrwxrwx 1 yuis yuis 4096 Jul 10 21:59 .
 51509920740553578 -rwxrwxrwx 1 yuis yuis    2 Jul 10 21:59 2.txt
 36310271996515302 -rwxrwxrwx 1 yuis yuis    2 Jul 10 21:59 1.txt

$ rm -rf * # 全ファイルを削除 > 確認プロンプト > y
W: It seems You are attempting to run kind of dangerous command. Continue? [y/n]
y

$ ls # ファイルが消されてることを確認
total 0
166351711236005864 drwxrwxrwx 1 yuis yuis 4096 Jul 10 21:59 ..
 23080948093026057 drwxrwxrwx 1 yuis yuis 4096 Jul 10 21:59 .

上記のような具合に、rm -rf *を実行しようとすると、実行される前に警告を行うプロンプトをします。yで実行をし、nで実行をキャンセルします。

コード


ok(){

  : <<< '
  yes or no prompt
  e.g. printf "The file alredy exist here. Override it? " ; ok && echo y || echo n
  '

  read -n 1 -r ; [[ $REPLY =~ ^[Yy]$ ]] && { echo ; return 0 ; } || { echo ; return 1 ; }

}

red=
\e[1;31m'
grn=
\e[1;32m'
yel=
\e[1;33m'
blu=
\e[1;34m'
mag=
\e[1;35m'
cyn=
\e[1;36m'
end=
\e[0m'

warn(){
  printf "${yel}W: ${*}\n${end}" >&2
}

##

shopt -s extdebug

preexec_invoke_exec () {
    [ -n "$COMP_LINE" ] && return  # do nothing if completing
    [ "$BASH_COMMAND" = "$PROMPT_COMMAND" ] && return # don't cause a preexec for $PROMPT_COMMAND
    local this_command=`HISTTIMEFORMAT= history 1 | sed -e "s/^[ ]*[0-9]*[ ]*//"`;

    # So that you don't get locked accidentally
    if [ "shopt -u extdebug" == "$this_command" ]; then
        return 0
    fi

        # // filtering

    # Modify $this_command and then execute it
    # eval "${this_command}"

    # echo "${this_command}"


    if [[ "${this_command}" =~ ^(somethingdangercommand$|somethingdangercommand[[:space:]]) ]]; then warn "It seems You are attempting to run kind of dangerous command. Continue? [y/n]" ; ok && eval "${this_command}" || return
  elif [[ "${this_command}" =~ ^(anotherSomethingdangercommand$|anotherSomethingdangercommand[[:space:]]) ]]; then warn "It seems You are attempting to run kind of dangerous command. Continue? [y/n]" ; ok && eval "${this_command}" || return
elif [[ "${this_command}" =~ ^(rm[[:space:]][-rf]{3}$|rm[[:space:]][-rf]{3}[[:space:]]) ]]; then warn "It seems You are attempting to run kind of dangerous command. Continue? [y/n]" ; ok && eval "${this_command}" || return
  else eval "${this_command}"
    fi

        # // filtering

    return 1 # This prevent executing of original command
}

trap 'preexec_invoke_exec' DEBUG

上記コードを${HOME}/.bashrcへ追記し、試しに未定義のコマンド、somethingdangercommandコマンドを実行してみてください。未定義のコマンドなので通常だったら未定義ですよというエラーが出るところですが、プロンプトでnをエンターすることでコマンド自体が実行されないので、エラーが出ずに終了します。

trap 'preexec_invoke_exec' DEBUG … 毎コマンド実行前にpreexec_invoke_exec()を実行

elif [[ "${this_command}" =~ ^(rm[[:space:]][-rf]{3}$|rm[[:space:]][-rf]{3}[[:space:]]) ]]; then warn "It seems You are attempting to run kind of dangerous command. Continue? [y/n]" ; ok && eval "${this_command}" || return … 実行されたコマンドがrm -rfrm -frのとき警告をする

コマンドを実行する前にコマンドが正規表現に一致するかなど、フィルタをし、フィルタにかかった場合に警告をし、それ以外の場合には通常通り渡されたコマンドをevalしている感じですね。

参考

Linux trap command help and examples
linux – Modify all bash commands through a program before executing them – Unix & Linux Stack Exchange

結論

https://yuis.xsrv.jp/data/MjQuC3Aeoxgi4YZvwYxCRCXfuwXbGI1H.png

rm -rfの事故はエクスクラメーション展開で起こってしまうことが多いようです。使わない場合は無効化しておくといいですね。

# disable/turn off history expansion `!` altogether
set +H

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です