コマンド履歴の達人を目指してみる

自分も知らなかった...。まだまだ知らないことって、いっぱいある。

それにしてもコマンド履歴というのは、自分はよく使う。直接入力するより、過去の履歴を探し出して、必要な修正してからコマンド実行するパターンが多い。長〜いファイルパスやオプション指定のあるコマンドなんて、ほとんどがコマンド履歴の修正と実行である。コマンド履歴が使えるからこそ、快適なコマンド操作ができると思っている(自分の場合)。

コマンド履歴は質実剛健なコマンドの世界に、選択して実行するというGUI的なゆるい操作感を与えてくれる。コマンドを忘れる心配や、スペルを間違う心配から、救ってくれる。それほどまでにコマンド履歴に頼りきっている状況なのに、今日も上下の矢印キーを激しく連打するしかない自分は、一体どうしたら良いものか?

もっと効率的に目的のコマンド履歴に辿り着く技が必要である。

コマンド履歴の仕組み

  • bashは、実行したコマンドを履歴として記録しておく。
  • そのコマンド履歴は ~/.bash_history というファイルに保存される。(デフォルトの設定では)
  • 但し、コマンドを実行する度に ~/.bash_history に書き込まれる訳ではない。
  • bashが起動中は、bashごとに用意されるコマンド履歴のキャッシュ(一時的な保存場所)に保存される。
  • bashが終了する時、コマンド履歴のキャッシュの内容が ~/.bash_history に書き込まれる。
  • つまり、コマンド履歴を2段階に分けて保存するという、ちょっとややこしい仕組みなのだ。

しかし、この仕組みのおかげで、ちょっとした恩恵もある。

  • 今やターミナルはタブ管理があたり前になり、通常いくつものタブを開いて作業している。(ウィンドウも複数開いている)
  • いくつものタブが開いているということは、いくつものbashが起動している状態でもある。
  • もし、それぞれのbashがコマンド実行毎に共通の ~/.bash_history に書き込んでしまうと、すべてのbashの履歴がごちゃ混ぜになってしまう...。
    • おそらく、とっても扱いづらいコマンド履歴になること必至。
  • 一方、それぞれのbashが起動中は個別のキャッシュに記録しておけば、bashごとにコマンド履歴は区別される。
  • そして、bashが終了する時にまとめて ~/.bash_history に書き込むことで、タブごとにグループ化されたコマンド履歴となるのだ。
  • その後、新しいタブを開く時は、bashは ~/.bash_history のコマンド履歴をキャッシュに読み込む。
  • つまり、新しいタブは、既存のタブ以外のすべてのコマンド履歴にアクセスできるのだ。
  • 別の言い方をすれば、過去に閉じられたタブのすべてのコマンド履歴にアクセスできるのだ。
  • そして閉じる時は、自らのタブで新たに実行されたコマンドの履歴のみ(差分のみ)を ~/.bash_history に書き込む。
  • その時 ~/.bash_history には書き込まれるけど、既存のタブのコマンド履歴のキャッシュには影響しない。
  • bashは起動時のみ、~/.bash_history から、コマンド履歴のキャッシュへ、履歴をコピーするだけである。
  • ~/.bash_historyの内容が、リアルタイムにbashごとのコマンド履歴のキャッシュに反映される訳ではない。
  • そのようにして記録されるコマンド履歴は、上下の矢印キーで1履歴ごとに移動して、コマンド行で確認できるようになっている。
    • control-P・NでもOK。
  • コマンド行に表示されたコマンド履歴は、そのまま実行することもできるし、必要な修正をしてから実行することもできる。

履歴を確認するコマンド

.bash_historyについて

特別なコマンドは用意されていないので、既存のコマンドで覗いてみる。

$ cat ~/.bash_history
  • 最新の10件を見たい。
$ cat ~/.bash_history|tail
historyコマンドについて

コマンド履歴のキャッシュを便利に扱うためのhistoryコマンドが用意されていた。

  • bashごとのコマンド履歴のキャッシュの内容を見たい。
$ history
  • 最新の10件を見たい。
$ history 10
  • 現在のコマンド履歴のキャッシュを ~/.bash_history へ保存する。
    • 実は、bashを終了しなくても、意図的に ~/.bash_history に保存できるのだ。
$ history -a
  • 現在の ~/.bash_history からコマンド履歴をキャッシュに読み込む。
    • 実は、bashの起動時以外でも、意図的に ~/.bash_history から読み込めるのだ。
$ history -n
  • コマンド履歴のキャッシュをクリア(消去)する。
    • キャッシュはクリアされるが、~/.bash_history には影響しない。(~/.bash_historyはしっかり残る)
$ history -c

historyコマンドの底力

  • ところでhistoryコマンドが出力する、コマンド履歴左側の番号は、飾りじゃない!
$ history 10
  462  cd ~/Document
  463  > じじい.txt
  464  > シンガポール.txt
  465  > ノーライフキング.txt
  466  echo じじい シンガポール ノーライフキング > spotlight_test.txt
  467  man mdutil
  468  mdutil -i off /Volumes/名称未設定\ 10 
  469  mdutil -i on /Volumes/名称未設定\ 10 
  470  rm -f /Volumes/名称未設定\ 10/Spotlight.txt
  471  mdutil -i off /Volumes/名称未設定\ 10 
  • historyコマンドは履歴の閲覧だけでなく、コマンド履歴の実行もできる。
  • 以下のように!に続けて番号を指定することで、そのコマンド履歴が実行されるのだ。
$ !467
man mdutil

素晴らしい!

  • ちなみに、!!で直前のコマンドが実行される。
$ echo "履歴のテスト"
履歴のテスト
$ !!
echo "履歴のテスト"
履歴のテスト

環境設定

  • コマンド履歴を記録する時の便利な設定がいろいろある。
  • 以下の変数を設定することで、コマンド履歴をコントロールできる。
変数名 設定例とデフォルト値 意味
HISTFILE HISTFILE=~/.bash_historyデフォルト値 履歴ファイルの保存場所は~/.bash_history
HISTSIZE HISTSIZE=500(デフォルト値 使用中のbashの履歴キャッシュに最大500件保持する
HISTFILESIZE HISTFILESIZE=500(デフォルト値 履歴ファイルに最大500件保持する
HISTCONTROL HISTCONTROL=ignoredups
HISTCONTROL=ignorespace
HISTCONTROL=ignoreboth
同じコマンドが連続する場合は1回だけ記録する
コマンドの頭にスペースを付けて実行すると記録しない
ignoredupsとignorespace上記2つどちらも設定する
HISTIGNORE HISTIGNORE=ls:history
HISTIGNORE=?:??:???:exit
lsとhistoryは、記録しない。(:は設定の区切り)
1〜3文字までのコマンドとexitは、記録しない。(?は何らかの1文字、*は0文字以上の何か)
HISTTIMEFORMAT HISTTIMEFORMAT='%Y/%m/%d %H:%M:%S '
HISTTIMEFORMAT='%y/%m/%d '
コマンド履歴に日時(例: 2013/10/26 12:34:56)を付加する
コマンド履歴に日付(例: 13/10/26)を付加する
HISTCMD この変数には設定できない。読み取りのみ。 次にコマンドを実行した際に付く履歴番号
  • ちなみに、HISTTIMEFORMATを設定すると便利そうなんだけど、履歴ファイルの中にUNIX時間を表現する数値が含まれることになる。
$ history
    1  echo tab1.1
    2  echo tab1.3
    3  echo tab2.2
    4  echo tab2.4
    5  history

$ HISTTIMEFORMAT="%Y/%m/%d %H:%M:%S "
$ history
    1  2013/10/26 14:47:05 echo tab1.1
    2  2013/10/26 14:47:05 echo tab1.3
    3  2013/10/26 14:47:05 echo tab2.2
    4  2013/10/26 14:47:05 echo tab2.4
    5  2013/10/26 14:47:05 history
...中略...

$ cat .bash_history
#1382736410
echo tab1.1
#1382764900
echo tab1.3
#1382764912
echo tab2.2
#1382764916
echo tab2.4
#1382764927
history
...中略...

なるほど、なるほど。

  • という訳で、保持する履歴数を5000に変更してみた。
    • 自分にとっては、デフォルトの500じゃ少ない。
    • 試行錯誤して何度も実行していると、あっという間に500件が同じようなコマンドになっていたので。
  • それから、繰り返しの無駄を省くためにHISTCONTROLも設定してみた。
  • あと、historyのみのコマンドも記録しないことにした。
HISTSIZE=5000       # 現在使用中のbashが保持する履歴数
HISTFILESIZE=5000   # 履歴ファイルに保存される履歴数
# HISTCONTROL=ignoredups  # 同じコマンドが連続する場合は1回だけ記録する
# HISTCONTROL=ignorespace # コマンドの頭にスペースを付けて実行すると記録しない
HISTCONTROL=ignoreboth    # ignoredupsとignorespaceどちらも設定する
HISTIGNORE=history     # historyは記録しない。
  • ~/.bash_profileで読み込むようにしておけば、bashは常に上記設定で起動するのだ。
    • もうちょっと正確に言えば、~/.bashrcに設定して、~/.bashrcを~/.bash_profileで読み込むようにしておくと、良いかもしれない。

コマンド履歴の眺め方いろいろ

画面にそのまま
$ history
...中略...
  588  git commmit -m 'Fix install method.'
  589  git commit -m 'Fix install method.'
  590  git log
  591  git tree
  592  git push
  593  git diff
  594  git add .
  595  git commit -m 'Fix install method.'
  596  git push
  597  history 10
  • しかし、履歴件数500以上が一気に表示されてしまうのは、ちょっと気が引ける。
  • これまでのコマンド出力の見通しも最悪になる...。
lessでページコントロール
  • そんな時は、パイプでlessコマンドに渡せば、既存の画面出力に影響を与えずに閲覧できる。
$ history|less
  • 移動
1行上・下へ移動 ↑・↓矢印キー
1ページ上・下へ移動 w・z または b・スペース
ファイルの先頭・末尾へ移動 g・G または <・>
  • 検索
先頭・末尾方向へ語句を検索 ?語句・/語句
次・前の語句に移動する n・N
  • 終了
lessを終了する q
  • ヘルプ
キー操作のマニュアルを表示する h
キー操作のマニュアルを終了する q
viで見る
  • viに慣れている人にとっては、viで見たくなるかもしれない。(あるはemacs
$ history|vi
GUIのエディタで見る
  • コマンドの達人になれば、GUIなんて必要ないのかもしれないけど...
  • 自分はやっぱりGUIテキストエディタでだらだら見てる方が楽。(コマンドの達人にはなれない)
  • テキストエディット.appで開く。
$ hitory|open -f
  • CotEditor.appで開く。
$ history|open -f -a coteditor
コンソールで見る
  • openコマンドは強力である。
  • 結局、-aオプションで好みのアプリを指定すれば、何でも使えるということだ。
  • ならば、コンソール.appで開いてみる。
$ history|open -f -a console

開けた!

  • コンソール.appの素晴らしいところは、検索語句を含む行しか表示しないというフィルタ機能を持っていること。
  • スペースで区切れば、二つ以上の語句を含むAND検索もできる。

履歴と修正のキー操作

目指す履歴を見つけたら、きっと修正して、実行したくなる。履歴の達人になるなら、bash環境でテキストを自由に編集したい。

履歴
履歴の先頭・末尾へジャンプ esc <・esc >
履歴を前・後に移動する ↑・↓
履歴の検索 control-r
  • control-rに続けて、検索語句を入力していくことで、その語句を含む一番最近の履歴を表示する。
    • 検索文字が増えるほどに、絞り込まれていく。(表示されるのは一番最近の履歴のみだが)
  • returnキーで、そのまま実行。
  • →キーで、その履歴にジャンプ。
    • その履歴を起点に↑・↓で前後に移動できる。
    • 目的の履歴を見つけたら、修正して、実行するのだ。
コマンドを自由に編集する
  • カーソル移動
1文字ずつ移動 ←・→
単語単位で移動 option-←・→
行頭・行末へ移動 control-a・e
  • 削除
左・右方向へ1文字削除 control-h・d
左・右方向へ1単語削除(次の記号まで) esc control-h・esc d
カーソル位置から行頭・行末まで削除 control-u・k
カーソル位置から手前のスペースまで削除 control-w
  • 操作の取消し
操作を1つ戻す(undo) control-−(controlとマイナス)あるいは control-x control-u
削除した文字をペースト control-y

パイプの連続技

ひとつ一つのコマンドは単機能なのだけど、それらをパイプで接続することで、簡潔な表現で、高度な情報処理が素早くできる。コマンドを使っていて、いつも感動するところ。

  • mdfindを含む履歴だけ抽出する。
$ history|grep mdfind|less
  • 履歴番号を外したい。
$ history|awk '{ $1=""; print $0 }'|less
  • アルファベット順に並べ替える。
$ history|awk '{ $1=""; print $0 }'|sort|less
  • 重複するコマンドは排除する。
$ history|awk '{ $1=""; print $0 }'|sort|uniq|wc -l
     503
$ history|awk '{ $1=""; print $0 }'|sort|wc -l
     645
  • よく使うコマンドのベスト10。
$ history|awk '{ $1=""; print $0 }'|sort|uniq -c|sort -r|head
  10  mdfind_all ノーライフキング
   8  echo ノーライフキング|xargs -I{} mdfind "* == {}* || kMDItemTextContent == {}*" -onlyin ~/Documents
   7  mdfind "* == ノーライフキング* || kMDItemTextContent == ノーライフキング*" -onlyin ~/Documents
   6  echo シンガポール|xargs -I{} mdfind "* == {}* || kMDItemTextContent == {}*" -onlyin ~/Documents
   5  mdfind_all シンガポール -onlyin ~/Documents
   5  mdfind '* == "シンガポール*"c || kMDItemTextContent == "シンガポール*"'c | less
   4  mdfind '* == "spotlight*"cdw'
   4  mdfind '* == "spotlight*" cdw'
   4  mdfind "* == シンガポール* || kMDItemTextContent == シンガポール*" -onlyin ~/Documents
   4  mdfind "* == シンガポール* cdw"
  • よく使うコマンドのベスト10。(引数を除いた先頭のコマンドのみで集計)
$ history|awk '{ print $2 }'|sort|uniq -c|sort -r|head
 308 mdfind
  90 echo
  25 mdfind_all
  24 sudo
  24 history
  14 ls
  14 history|grep
  14 git
  13 open
  12 man
  • よく使うコマンドのベスト10。(おっと、sudoは除外したい、引数を除いた先頭のコマンドのみで集計)
$ history|sed -e 's/sudo //g'|awk '{ print $2 }'|sort|uniq -c|sort -r|head
 308 mdfind
  90 echo
  25 mdfind_all
  24 history
  22 ls
  15 rm
  14 history|grep
  14 git
  13 open
  13 defaults


あ〜、最近はMountain LionのSpotlightで試行錯誤してたから、その傾向が顕著に出てる...。

mdfind 308回てっ、どんだけ検索してたんだ...。でも、ほとんどがコマンド履歴から実行してるはず。間違いない。