読者です 読者をやめる 読者になる 読者になる

tkuchikiの日記

Linux やプログラミングについて書きます。

bash の history からコマンドを実行できなくする & コマンドの履歴は見られるようにする設定

bash で以前実行したコマンドを ↑ キーや Ctrl+p、Ctrl+r で検索すると思います。
このとき、

  • 以前実行したコマンドの引数を一部変えて実行したいから history からコマンドを探して一部だけ書き換えようと思っていたのに間違えて Enter を押してそのまま実行してしまった
  • ↑ キーや Ctrl+p で履歴を辿っている途中に間違ったコマンドを実行してしまった

などのオペレーションミスをしたことがある方、いらっしゃると思います(ローカル環境とリモート環境を行ったり来たりしているとミスしがちな気がします)。
history を保存しないようにすれば(set +o history など)上記のようなオペレーションミスは防げそうですが、コマンドの履歴が見られないのは不便です。
そこで、history からコマンドを実行できなくしつつ、コマンドの履歴は見られるようにする設定を検証しました。
検証環境は

- GNU bash, version 4.2.46(1)-release (x86_64-redhat-linux-gnu) # Amazon Linux 2016.03, 2016.09, CentOS 7
- GNU bash, version 4.3.30(1)-release (x86_64-pc-linux-gnu) # Debian 8.5

です。
CentOS6 の GNU bash, version 4.1.2(2)-release (x86_64-redhat-linux-gnu) では動作しませんでした…

#export HISTTIMEFORMAT='%FT%T%z '
#export HISTFILE=/path/to/.bash_history
shopt -u histappend
export PROMPT_COMMAND='history -a; history -c;'

PROMPT_COMMAND を使ってコマンド実行後に history -a でコマンドの履歴を HISTFILE に追記し、 history -c で history を削除しています。
history -a で追記しているので histappend は無効にしています。
時間を記録しなくてよければ HISTTIMEFORMAT は設定不要ですし、HISTFILE もデフォルトの設定で良ければ書く必要はないです。
この設定をした状態でコマンドを実行すると HISTFILE には保存されつつ、↑キーなどでコマンドを探すことはできなくなります。
コマンドの履歴を見たいときは HISTFILE を直接見れば OK です。
ただし、!!!$ のような直前のコマンドや引数を参照する機能は使えなくなりますので注意してください。

おまけ

HISTTIMEFORMAT を指定した状態の HISTFILE は以下のようになります。

$ cat $HISTFILE
#1488171696
ls
#1488171701
echo foo

これだとちょっと読みにくいので整形する関数を用意しておくと便利です。

read_history() {
    awk 'NR % 2 { gsub("#", "", $0); t=strftime("%FT%T%z", $0); next } {print t, $0}' $HISTFILE
}

read_history を実行すると以下のようになります。

2017-02-27T05:01:36+0000 ls
2017-02-27T05:01:41+0000 echo foo

読みやすくはなりましたが、毎回 grep したりするのは面倒なので peco を使っていい感じに見られるようにしてみます。

peco_history() {
    awk 'NR % 2 { gsub("#", "", $0); t=strftime("%FT%T%z", $0); next } {print t, $0}' $HISTFILE | peco
}

bind -x '"\C-r": peco_history'

これで Ctrl-r でコマンドの履歴が見られるようになります。

追記

※追記1 2017/03/01 16:38

tkuchiki.hatenablog.com

DEBUG を trap すれば CentOS6 でも似たようなことはできました。

※追記2 2017/03/01 18:50

この機能が欲しいと思ったことはないけど、履歴を書き出すときに先頭に # を追加してコメント化して保存するのではダメなんだろうか。 - n314 のコメント / はてなブックマーク
というコメントをいただいたので、

bash で特定のコマンドを実行前にキャンセルする - tkuchikiの日記

のように DEBUG を trap して # を追加するのを試してみました。
preexec でコマンドを HISTFILE に書き出すときに # を先頭につけて history -rHISTFILE を history に読み込むようにしたらできました。

preexec() {
    [ -n "${COMP_LINE}" ] && return
    [ "${BASH_COMMAND}" = "${PROMPT_COMMAND}" ] && return
    local cmd_history=$(HISTTIMEFORMAT='%F %T%z ' history 1)
    #local cmdtime=$(date -d "$(echo ${cmd_history} | perl -lane 'print join(" ", @F[1 .. 2]);')" +%s)
    local cmd=$(echo ${cmd_history} | perl -lane 'print join(" ", @F[3 .. $#F]);')
    [ "${cmd}" = "" ] && return
    (echo "#${cmdtime}" ; echo "#${cmd}") >> $HISTFILE
    history -r $HISTFILE
}

(もしかして、こんなめんどくさいことしなくてもできる?)