30歳からのプログラミング

30歳無職から独学でプログラミングを開始した人間の記録。

ターミナルで直前に実行したコマンドをクリップボードにコピーする

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という文字列が格納されている。

参考資料