ふとした思いつきで、bashやzshのhistoryファイル(通常は環境変数のHISTFILEで指定したファイル)以外のファイルに、任意のフォーマットで実行したコマンドや実行時間、カレントディレクトリといった情報を記録できないだろうかと思ったので、試してみることにした。 今回はファイルに出力しているが、中の処理を書き換えれば実行履歴をDBに記録することもできると思うので、もし利用する場合は使い方に応じて適宜書き換えて貰えればいいだろう。

なお、この方法は単純にシェルの設定で対処しているだけの方法なので、ログの記録を強制できるというものではない(回避することは可能なはず)。 もし実行コマンドを強制的に記録させたいといった場合は、Snoopy Loggerのようなロギングツールを利用する方が良いだろう。

1. BASH_COMMANDを用いた処理方法(微妙…)

実行中のコマンドを取得するといえば、bashにはBASH_COMMANDという環境変数が用意されている。 trapで処理をしないとただ実行したコマンドが格納される(BASH_COMMANDをechoした場合、そのコマンドがそのまま格納される)というちょっと扱いにくい気のする環境変数なのだが、これを使った場合の方法を試してみる。

以下のような、trapでカレントディレクトリのファイルにBASH_COMMANDを出力するコマンドを実行して操作をしてみる。

trap 'echo "$BASH_COMMAND" >> ./test.log' DEBUG

で、結果としては以下のような事になってしまう。 実行コマンドが出力されるのでaliasが展開された状態となっており、さらにパイプライン単位で出力がされてしまい、パイプでつながってたのかどうかもここからだとわからない状態。

実行したコマンド:
  $ ll
記録された内容:
  ls --color=auto -la

実行したコマンド:
  $ ll | grep abc
記録された内容:
  ls --color=auto -la
  grep --color=auto abc

これだと、正直あまり嬉しいとは言えない。 できればパイプやセミコロン区切りをした場合でも1つのコマンド行としてログに出力してほしい。

2. accept-lineを置き換えて処理してみる(成功)

上記のように、BASH_COMMANDを使う方法だとパイプライン単位でしかコマンドを取得できないため、望んだ結果が得られない事はわかった。 じゃ他になにか方法が無いかと考えてみた結果、keybindのC-m(通常はaccept-line)の内容を置き換えて、accept-lineの実行前にREADLINE_LINE変数の内容(zshの場合はBUFFER)をログに出力させることで実行するコマンドを取得できることがわかった。

実際にサンプルを残しておく。

2-1. bashの場合

以下の内容をbashrcに記述することで、~/bash_history_$$.logに実行コマンドと時刻、カレントディレクトリが記録される。

function __accept-line() {
  if [ ! ${#READLINE_LINE} -eq 0 ]; then
    local log_file=~/bash_history_$$.log

    # mkdir
    mkdir -p ${log_dir}

    # logファイルへコマンドの出力
    date "+TimeStamp: %Y-%m-%d %H:%M:%S" >>${log_file}
    echo "CurrentDir: ${PWD}" >>${log_file}
    echo "Command: $READLINE_LINE" >>${log_file}
    echo "==========" >>${log_file}
  fi
}

# \C-mのキーバインドを変更する
bind -x '"\1299": __accept-line'
bind '"\1298": accept-line'
bind '"\C-m": "\1299\1298"'

2-2. zshの場合

zshの場合はこちら。 こちらも同様にzshrcに記載することで~/zsh_history_$$.logにその内容が記録される。

__accept-line() {
    # BUFFERのサイズに応じて処理を切り替える
    if [ ! ${#BUFFER} -eq 0 ];then
        local log_file="zsh_history$$.log"

        # mkdir
        mkdir -p ${log_dir}

        # logファイルへコマンドの出力
        date "+TimeStamp: %Y-%m-%d %H:%M:%S" >>${log_file}
        echo "CurrentDir: ${PWD}" >>${log_file}
        echo "Command: $READLINE_LINE" >>${log_file}
        echo "==========" >>${log_file}
    fi

    # accept-lineを実行
    zle .accept-line
}
zle -N accept-line __accept-line

もうちょっとカスタマイズすれば、正常終了/異常終了についても同様に記録することが可能になると思う。