2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

今年のコマンド使用頻度ランキングを算出して1年を振り返ってみよう

Posted at

今年も終わりが近づいてきたので、自分がよく使うコマンドをランキング形式にして振り返ることができたら面白そうと思ったのでやってみました。

結論

各種コマンドの使い方が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 \
  | head

Bashの場合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 \
  | head

Zshで期間指定する場合は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 zshbuiltinshistoryの項目とfc -lの項目を見てください。
そう、Zshのhistoryfc -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 sed
sed 's/|/\'$'\n/g'
# GNU sed
sed '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コマンドのインストール方法に関しては、こちらの記事がとてもよくまとまっていました。

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?