bash のバージョンは5.0.11(1)-release (x86_64-apple-darwin18.6.0)。
動機
Git のコミットメッセージに、その差分を生み出したコマンドを書くことがよくある。
例えば以下のように。
$ yarn run lint:fix $ git commit -am '$ yarn run lint:fix'
コミットメッセージに何を書くべきかについてはいろんな意見があるが、何らかのコマンドを実行した結果をコミットする場合は、そのコマンドを書いておくのは悪くないと思う。何をした結果としてその差分が生まれたのかが明確で、デバッグや調査のときに役に立つ。
そして、その時に直近のコマンドをコピペするわけだが、これが面倒くさい。よく行う作業だけに、もっと効率化したい。
ワンライナーで書ける
ただ単にタイトルの内容を実現するだけなら、ワンライナーで書ける。
$ history -a && tail -n 2 ~/.bash_history | head -n 1 | tr -d '\n' | pbcopy
これで出来る。
history -a
実行したコマンドの履歴は~/.bash_historyというファイルに書かれている。
だが随時更新されているわけではない。そのため、何も考えずに~/.bash_historyの内容を取りに行くと、意図とは違う結果になる可能性がある。
任意のタイミングで~/.bash_historyを更新するにはhistory -aというコマンドを実行すればよい。
tail -n 2 ~/.bash_history | head -n 1
~/.bash_historyへの書き込みが終わったら、tail -n 2 ~/.bash_historyを実行して~/.bash_historyの末尾2行を取得する。
なぜ2行なのかというと、最後の1行は今実行したコマンド、つまりhistory -a && tail -n 2 ~/.bash_history | head -n 1 | tr -d '\n' | pbcopyになっているから。そのため、目的のコマンドはその1つ前のものということになる。
そして、取得した2行の1行目をhead -n 1で取得することで、目的のコマンドを取得できる。
| tr -d '\n'
これで目的のコマンドの文字列を取得できたが、この文字列の末尾には改行がついている。このままだとターミナル上でペーストするのに不都合が生じる。
trは文字列操作のためのコマンドで、対象の文字列 | tr -d '削除する文字列'とすると、対象の文字列から削除する文字列を除去した文字列を出力する。
これを使って改行文字(\n)を取り除く。
| pbcopy
最後にpbcopyで、クリップボードにコピーする。
試しに以下の操作をすると、クリップボードにecho 1がコピーされているのを確認できる。
$ echo 1 1 $ history -a && tail -n 2 ~/.bash_history | head -n 1 | tr -d '\n' | pbcopy
エイリアスへの設定
いちいちhistory -a && tail -n 2 ~/.bash_history | head -n 1 | tr -d '\n' | pbcopyを実行するのはそれこそ手間なので、エイリアスに登録する。
エイリアスに登録するためにはまず、~/.bashrcに登録する。このファイルがなければ作る。
以下の内容を書き込んで保存する。clcというのは CopyLastCommand の略なので、これは好きな名前を使えばよい。
alias clc=`history -a && tail -n 2 ~/.bash_history | head -n 1 | tr -d '\n' | pbcopy`
次に、~/.bash_profileが~/.bashrcを読み込むようにする。source ~/.bashrcという内容を書き込めばいいので、以下を実行する。
$ echo 'source ~/.bashrc' >> ~/.bash_profile
これで、次に bash にログインしたときからclcを使えるようになる。すぐに使いたい場合は$ source ~/.bash_profileを実行する。
だがここで問題が発生した。エイリアスに登録すると、なぜか改行の除去が上手くいかない。このままだと不便だが、シェルスクリプトにすると意図通りに動いたので、やむを得ずシェルスクリプトにすることにした。
シェルスクリプト化
copy_last_bash_historyという名前で、以下の内容のファイルを作る。
#!/bin/bash tail -n 2 ~/.bash_history | head -n 1 | tr -d '\n' | pbcopy
ファイルに実行権限を付与する。
$ chmod +x ファイルのパス
既にシェルスクリプト用のディレクトリがある場合はそこに置く。
まだない場合は、ディレクトリの作成と、パスを通す作業が必要になる。
ディレクトリの作成
今回は~/bin/というディレクトリを作り、そこに自作のシェルスクリプトを置くようにした。
パスを通す
~/bin/をコマンド検索パスに含めるための作業。
以下の内容を実行する。
$ echo 'export PATH="$HOME/bin:$PATH"' >> ~/.bash_profile $ source ~/.bash_profile
これで、copy_last_bash_historyというコマンドを実行できるようになった。
エイリアスの設定を、以下に書き換える。
alias clc='history -a && copy_last_bash_history'
これで、$ clcを実行すると直前のコマンドがクリップボードにコピーされるようになった。
改良
せっかくシェルスクリプトにしたので、もう少し改良してみた。
#!/bin/bash argument=$(($1)) if [ $argument = 0 ]; then argument=1 fi tail -n $(($argument+1)) ~/.bash_history | head -n 1 | tr -d '\n' | pbcopy pbpaste echo ''
まず、直前のコマンド以外もコピーできるようにした。対象を引数で指定する。
clcあるいはclc 1で直前のコマンドをコピー、clc 2で2つ前のコマンドをコピー、clc 3で3つ前、という具合。
引数を受け取れるように、エイリアスの末尾に$1を追加する。
alias clc='history -a && copy_last_bash_history $1'
~/.bashrcを更新したときは$ source ~/.bash_profileか再ログインをしないと変更が反映されないので、忘れずに。
また、どのコマンドをコピーしたか分かるように、コピーしたコマンドをpbpasteを使って出力するようにした。最後にecho ''で改行を出力する。
例えば以下のように動作する。
$ echo foo foo $ echo bar bar $ clc 2 echo foo
このとき、クリップボードにはecho fooという文字列が格納されている。