今年も終わりが近づいてきたので、自分がよく使うコマンドをランキング形式にして振り返ることができたら面白そうと思ったのでやってみました。
結論
各種コマンドの使い方がBashかZshか、GNU系のOS(Linux)かBSD系のOS(Macなど)かで微妙に変わってきます。
実行コマンド例は以下のとおりです。
累計でベスト10を出してみる
# Bash x BSDの場合 (※ $HISTTIMEFORMAT='%F %T')history\
| awk'{ for (i = 4; i < NF; i++) { printf("%s ", $i) } print $NF }'\
| sed's/|/\'$'\n/g'\
| awk'{ print $1 }'\
| sort\
| uniq-c\
| sort-r\
| head# Zsh x GNUの場合history 1 \
| awk'{ for (i = 2; i < NF; i++) { printf("%s ", $i) } print $NF }'\
| sed's/|/\n/g'\
| awk'{ print $1 }'\
| sort\
| uniq-c\
| sort-r\
| headBashの場合historyに引数を指定する必要はなく、2行目のawkのiは4から始まります。(環境変数HISTTIMEFORMATが%F %Tのとき)
Zshの場合historyの引数に1が指定され、2行目のawkのiは2から始まります。
また、awkがBSDのものかGNUのものかによって4行目のawkの指定の仕方が異なります。
2019年に限定してベスト10を出してみる
単にgrepを使うだけです。細かい期間指定をしたい場合はegrepを使いましょう。
# Bash x BSDの場合 (※ $HISTTIMEFORMAT='%F %T')history\
| grep 2019- \
| awk'{ for (i = 4; i < NF; i++) { printf("%s ", $i) } print $NF }'\
| sed's/|/\'$'\n/g'\
| awk'{ print $1 }'\
| sort\
| uniq-c\
| sort-r\
| headZshで期間指定する場合はhisotryに-iオプションを追加します。
3行目のawkのiの開始は4に変わっています。
# Zsh x GNUの場合 (※ $HISTTIMEFORMAT='%F %T')history-i 1 \
| grep 2019- \
| awk'{ for (i = 4; i < NF; i++) { printf("%s ", $i) } print $NF }'\
| sed's/|/\n/g'\
| awk'{ print $1 }'\
| sort\
| uniq-c\
| sort-r\
| headちなみに私の自宅Macでの実行結果はこんな感じでした。
職場のMacでやってみるとまた結果が変わって面白いと思います。
201 vi
148 ll
111 git
96 cd
87 gc
79 kg
73 rm
72 k
66 brew
61 mv
ちなみに、aliasされたコマンドはvi='nvim'、ll='ls -al'、gc='git commit'、kg='kubectl get'、k='kubectl'です。
解説
上記のコマンドはほとんど同じですが微妙に違うところがあります。
BashかZshかでhistoryの使い方や結果が異なりますし、BSDかGNUかでsedの使い方が異なります。
history
まずはBashとZshで微妙に仕様が違うhistoryについて見ていきましょう。
Bashでそのままコマンドを打ってみるとこのように表示されます。
$ history
1 2018-09-08 17:00 brew install ricty
2 2018-09-08 17:05 cp -f /usr/local/opt/ricty/share/fonts/Ricty*.ttf ~/Library/Fonts/
3 2018-09-08 18:29 brew search thunderbird
...
日付時刻付きで全件表示されます。
一方Zshで同様に打ってみると・・・
2593 ./init.sh
2594 ll ~/Library/Application\ Support/Code/User/
2595 gc -m "Add VSCode keybindings"
...
2610 git init
日付や時刻は表示されず最新16件のみ表示されます。
ただし、Bashのhistoryでも日付や時刻が表示されない場合があります。
それは環境変数HISTTIMEFORMATが設定されていない場合です。
この場合は(途中で設定したとしても)期間ごとに絞り込むことはできないので注意してください。
以降はHISTTIMEFORMATに%F %Tが設定されているという前提で話を進めます。
Bashはデフォルトで全件出ますし、HISTTIMEFORMATを指定していれば日付も出ますのでこれ以上詳しく話す必要もないでしょう。
Zshのhistoryの場合、全件出すには引数を指定する必要があります。
# 全件表示(1件目以降全て)history 1
# 区間指定history 10 20
また、先程説明したとおり日付時刻を表示に含めるには-iオプションを付け加えます。
Zshのhistoryのオプションについてより詳しく知りたくばman zshbuiltinsでhistoryの項目とfc -lの項目を見てください。
そう、Zshのhistoryはfc -lのaliasなのだそうです。
history
Same as fc -l.
historyの結果をコマンド種別で集計する
一旦間を飛ばして、シンプルにhistoryの結果をコマンドごとに集計するにはどうすればいいか考えてみます。
Bashのhistoryで説明します。historyの1行ごとの出力は空白文字区切りで
インデックス 日付 時刻 コマンド サブコマンドやオプションなど
となっているので、パイプなどを考慮しなければawk '{ print $4 }'に渡せばよいことがわかります。
そうしたら、uniq -cで重複部分を消しつつカウントすればいいようですが、uniqは連続した重複しか消すことができないので、uniq -cする前に一旦sortします。
# Bash$ history | awk'{ print $4 }' | sort | uniq-c
...
1 c
3 cal
31 cat
116 cd
13 chmod
5 code
1 command
7 cp
60 curl
10 cut
...
あとはこれを降順にソートし直せば頻度順になります。
パイプした後のコマンドも集計に含めたい
パイプした後のコマンドも集計に含めたい場合どうすればいいでしょうか。
方法は色々あると思いますが、今回は|を改行コードに置換するという方法を取りました。
ここでsedがGNUかBSDのものかで置換の指定方法が若干異なります。
# BSD sedsed's/|/\'$'\n/g'# GNU sedsed's/|/\n/g'BSDのsedで改行コードに変換する方法を調べていたら以下の記事が見つかりましたので、詳しく知りたければ読んでみてください
ただし、普通に改行するだけではコマンド部分とインデックスやサブコマンド以降と切り分けづらいです。
なので改行する前にコマンド部分以前をカット(コマンド部分以降を抽出)してしまいたいです。
それが
awk'{ for (i = 4; i < NF; i++) { printf("%s ", $i) } print $NF }'の部分に当たります。
解説しておきながら実はawkの言語仕様については詳しくないので、forの中身がfor (i = n; i <= NF; i++)でなかったり、通常最後列の文字列が入る$NFにfor pirntfした内容が入っている理由を知っている方がいたら教えていただけると助かります。
つまり、ここまでの流れをまとめると
# Bash x BSDの場合 (※ $HISTTIMEFORMAT='%F %T')history\
| awk'{ for (i = 4; i < NF; i++) { printf("%s ", $i) } print $NF }'\
| sed's/|/\'$'\n/g'\
| awk'{ print $1 }'\
| sort\
| uniq-c\
| sort-r\
| headのコマンドはおおよそ
コマンド履歴を表示する \
| 余分な列をカットする \
| パイプ後もカウントするよう改行コードに置換する \
| 重複カウントしやすいようコマンド部分のみを抽出する \
| 重複カウントしやすいようソートして重複数をカウントする \
| 頻度順にソートする
という意味になります。
おまけ
historyの最大件数は環境変数HISTSIZEで決まっています。
コマンド履歴は今回のような使い方以外にもCtrl + Rでの検索時に重宝しますので、CUI操作をよくする人はより大きい数を設定しておくとより便利になります。
そして普段Macを使っている人でGNU sedを使いたい人はbrew install gnu-sedしてalias sed='gsed'を.bashrcか.zshrcに書き加えておきましょう。
その他GNUコマンドのインストール方法に関しては、こちらの記事がとてもよくまとまっていました。