Solist Work Blog

Software engineer note

.zsh_historyとMELPAの管理方法

ソフトウェアエンジニアにとって .zsh_history や .bash_history は財産です。私はzshがメインシェルなので以下 .zsh_history で統一して書いていきますが、bashでも基本的には同じ内容です。 履歴は多いほど役に立つのでシェルの履歴は10万を越えても消すべきではないでしょう。わたしはll ls la cd man scp vim nvim less ping open file which whois drill uname md5sum tracerouteなどの履歴を残さないように設定してあまり価値のない履歴が残らないように努力しています。

.zshrc

zshaddhistory() {
    local line=${1%%$'\n'}
    local cmd=${line%% *}

    # Only those that satisfy all of the following conditions are added to the history
    [[ ${#line} -ge 5
       && ${cmd} != ll
       && ${cmd} != ls
       && ${cmd} != la
       && ${cmd} != cd
       && ${cmd} != man
       && ${cmd} != scp
       && ${cmd} != vim
       && ${cmd} != nvim
       && ${cmd} != less
       && ${cmd} != ping
       && ${cmd} != open
       && ${cmd} != file
       && ${cmd} != which
       && ${cmd} != whois
       && ${cmd} != drill
       && ${cmd} != uname
       && ${cmd} != md5sum
       && ${cmd} != pacman
	   && ${cmd} != xdg-open
       && ${cmd} != traceroute
       && ${cmd} != speedtest-cli
    ]]
}

このコマンドから始まる場合は履歴に残らないようにしています。 これで無闇に履歴が増えなくなってよいです。

保持している履歴が多くなるにつれてその価値がますます高まっていきますし、ソフトウェアエンジニアを続ける限り使い続けるものですから、もし消えてしまったらほとんどの仕事で支障をきたすことでしょう。履歴を増やすためには長い年月をかけてまた育てなければいけないので非常につらい状態に追い込まれることになります。 コマンドを全て丸暗記しているソフトウェアエンジニアはおそらく存在しないので、ほとんどのソフトウェアエンジニアは.zsh_historyをCtrl-rで呼び出すことで対処しているのではないでしょうか。 以下のように設定するとCtrl-rを押すとfzfで .zsh_history が検索できるようになります。

.zshrc

function select-history() {
  BUFFER=$(history -n -r 1 | fzf-tmux -d --reverse --no-sort +m --query "$LBUFFER" --prompt="History > ")
  CURSOR=$#BUFFER
}
zle -N select-history
bindkey '^r' select-history

fzfで.zsh_historyを検索

.zsh_history は火事があっても守らなければいけない資産であることに疑いの余地はないでしょう。 商人の家が火事になったとき持って逃げるものの筆頭は顧客リストであると聞きました。 ソフトウェアエンジニアが火事の時もって逃げるべきものは自分のパソコンであることに異論はないでしょう。しかし、災害は火事だけではありません。津波がこようがミサイルが飛んでこようが火山が噴火しようが泥棒にパソコンをもっていかれようが .zsh_history は失いたくないものです。わたしは子供時代に川が氾濫しそうになって一番大事なものを持って逃げなさいと言われたときに持って逃げたものはレゴブロックでしたが、持って逃げることのできるものは重さや状況によって制限されてしまいます。FBIが対象者を尾行するときに一番効果的な方法は尾行しないことであると何かの本で読みました。対象者の行動を分析して先回りするのが成功率が最も高い尾行方法なのだそうです。尾行しなくてもよい状況を作ることが最善であるならば、ソフトウェアエンジニアは災害の時に何も持たずに避難しても問題がない状況をつくることが最善なのではないでしょうか。1 .zsh_historyは機密情報を含むこともあるので、GitHubで公開すべきものではないでしょう。ではどのようにして.zsh_historyを災害から守っていくのか考えたいと思います。

.zsh_historyを守る仕組み

ディレクトリを作っておきます。

mkdir -p ~/Dropbox/zsh/backup/

まず、Dropboxのzshディレクトリのbackupフォルダにローテートした履歴を作っておきます。 次のコマンドで履歴を世代管理したい回数だけ実行します。8世代管理したいなら8回実行します。

ノーマルバージョン

tar cfz ${HOME}/Dropbox/zsh/backup/`date '+%Y%m%d%H%M%S'`.tar.gz -C ${HOME}/ .zsh_history

暗号化バージョン

gpg -c ${HOME}/.zsh_history; mv ${HOME}/.zsh_history.gpg ${HOME}/Dropbox/zsh/backup/`date '+%Y%m%d%H%M%S'`.gpg

暗号化バージョンは機密情報2を二段階認証しているとはいえDropboxにアップロードすることはまかりならんという頭の硬い組織に属している人向けです。私はノーマルバージョンを使っています。暗号化の安全性についてはこちらの記事で確認してください。暗号化バージョンはパスワードを聞かれるので12文字以上のパスワードを入力してください。毎朝打てるパスワードで強度が強そうなものを選びましょう。

私は8世代の .zsh_history を管理しているので8日分の履歴があります。 この状態で以下のコマンドを毎朝実行すると次のような動きをするようになっています。

ノーマルバージョン

rm -rf ${HOME}/Dropbox/zsh/backup/`ls -rt ${HOME}/Dropbox/zsh/backup | head -n 1`; tar cfz ${HOME}/Dropbox/zsh/backup/`date '+%Y%m%d%H%M%S'`.tar.gz -C ${HOME}/ .zsh_history

暗号化バージョン

rm -rf ${HOME}/Dropbox/zsh/backup/`ls ${HOME}/Dropbox/zsh/backup | grep gpg | head -n 1`; gpg -c ${HOME}/.zsh_history; mv ${HOME}/.zsh_history.gpg ${HOME}/Dropbox/zsh/backup/`date '+%Y%m%d%H%M%S'`.gpg

8世代管理している履歴のうちから一番古いものを削除して、現在の .zsh_history をバックアップフォルダにバックアップします。ローテートしながらbackupしているということです。なぜローテートしながらバックアップしているのかというと、バックアップが一つしかないとファイルが破損した場合復元できなくなるからです。ファイルが破損した時にどれか一つでもファイルを救い出せれば、被害は最大でも一週間分の履歴を失うだけですみます。これくらいなら許容範囲でしょう。 Dropboxはおそらく複数のコピーが複数のサーバーに離れて管理されているでしょうが、DropboxのソフトウェアエンジニアがヘマをしたときのためにDropboxGoogle Driveを同期しましょう。この目的のためrcloneを使用します。

sudo pacman -S rclone
rclone config

Google Driveを選んでセットアップしましょう。Google Driveの接続名をdriveに設定します。クライアントID、認証キーの入力は省略できます。auto configはNを選択します。

rclone config
Current remotes:

e) Edit existing remote
n) New remote
d) Delete remote
r) Rename remote
c) Copy remote
s) Set configuration password
q) Quit config
e/n/d/r/c/s/q> n
name> drive
Type of storage to configure.
Enter a string value. Press Enter for the default ("").
Choose a number from below, or type in your own value
1 / 1Fichier
\ "fichier"
2 / Alias for an existing remote
\ "alias"
3 / Amazon Drive
\ "amazon cloud drive"
4 / Amazon S3 Compliant Storage Provider (AWS, Alibaba, Ceph, Digital Ocean, Dreamhost, IBM COS, Minio, etc)
\ "s3"
5 / Backblaze B2
\ "b2"
6 / Box
\ "box"
7 / Cache a remote
\ "cache"
8 / Dropbox
\ "dropbox"
9 / Encrypt/Decrypt a remote
\ "crypt"
10 / FTP Connection
\ "ftp"
11 / Google Cloud Storage (this is not Google Drive)
\ "google cloud storage"
12 / Google Drive
\ "drive"
13 / Google Photos
\ "google photos"
14 / Hubic
\ "hubic"
15 / JottaCloud
\ "jottacloud"
16 / Koofr
\ "koofr"
17 / Local Disk
\ "local"
18 / Mega
\ "mega"
19 / Microsoft Azure Blob Storage
\ "azureblob"
20 / Microsoft OneDrive
\ "onedrive"
21 / OpenDrive
\ "opendrive"
22 / Openstack Swift (Rackspace Cloud Files, Memset Memstore, OVH)
\ "swift"
23 / Pcloud
\ "pcloud"
24 / Put.io
\ "putio"
25 / QingCloud Object Storage
\ "qingstor"
26 / SSH/SFTP Connection
\ "sftp"
27 / Union merges the contents of several remotes
\ "union"
28 / Webdav
\ "webdav"
29 / Yandex Disk
\ "yandex"
30 / http Connection
\ "http"
31 / premiumize.me
\ "premiumizeme"
Storage> 12
** See help for drive backend at: https://rclone.org/drive/ **

Google Application Client Id
Setting your own is recommended.
See https://rclone.org/drive/#making-your-own-client-id for how to create your own.
If you leave this blank, it will use an internal key which is low performance.
Enter a string value. Press Enter for the default ("").
client_id>
Google Application Client Secret
Setting your own is recommended.
Enter a string value. Press Enter for the default ("").
client_secret>
Scope that rclone should use when requesting access from drive.
Enter a string value. Press Enter for the default ("").
Choose a number from below, or type in your own value
1 / Full access all files, excluding Application Data Folder.
\ "drive"
2 / Read-only access to file metadata and file contents.
\ "drive.readonly"
/ Access to files created by rclone only.
3 | These are visible in the drive website.
| File authorization is revoked when the user deauthorizes the app.
\ "drive.file"
/ Allows read and write access to the Application Data folder.
4 | This is not visible in the drive website.
\ "drive.appfolder"
/ Allows read-only access to file metadata but
5 | does not allow any access to read or download file content.
\ "drive.metadata.readonly"
scope> 1
ID of the root folder
Leave blank normally.
Fill in to access "Computers" folders. (see docs).
Enter a string value. Press Enter for the default ("").
root_folder_id>
Service Account Credentials JSON file path
Leave blank normally.
Needed only if you want use SA instead of interactive login.
Enter a string value. Press Enter for the default ("").
service_account_file>
Edit advanced config? (y/n)
y) Yes
n) No
y/n> n
Remote config
Already have a token - refresh?
y) Yes
n) No
y/n> y

Google Driveにzshbackupフォルダを作って何かファイルが存在する状態で確認しましょう。

rclone ls drive:zshbackup

これで以下のようにGoogle Driveのzshbackupフォルダにあるファイルが見えるはずです。

rcloneでGoogleDriveに接続する

動作確認ができたらDropboxのzshディレクトリとGoogle Driveのzshbackupフォルダを同期しましょう。

rclone sync ${HOME}/Dropbox/zsh drive:zshbackup

これでDropboxのzshディレクトリがGoogle Driveのzshbackupディレクトリに同期されますので、Dropboxのソフトウェアエンジニアがヘマをしても安心です。DropboxとGoogleのソフトウェアエンジニアが同時にヘマをして、かつ私のパソコンが泥棒に盗まれるという事態が同時に起こる確率はものすごく低いと思うので考慮しなくてもよいと思います。

以上のコマンドを毎朝たたくのは面倒なのでコマンド一つにしましょう。

zshbackup

コマンド一つでこれらの処理がすべて完了するようにします。これくらいなら毎朝実行するのはつらくありません。コーヒーを入れている間などに毎日実行するようにしましょう。

.zshrcノーマルバージョン

alias zshbackup='rm -rf ${HOME}/Dropbox/zsh/backup/`ls -rt ${HOME}/Dropbox/zsh/backup | head -n 1`; tar cfz ${HOME}/Dropbox/zsh/backup/`date '+%Y%m%d%H%M%S'`.tar.gz -C ${HOME}/ .zsh_history; rclone sync ${HOME}/Dropbox/zsh drive:zshbackup'

.zshrc暗号化バージョン

alias zshbackup='rm -rf ${HOME}/Dropbox/zsh/backup/`ls ${HOME}/Dropbox/zsh/backup | grep gpg | head -n 1`; gpg -c ${HOME}/.zsh_history; mv ${HOME}/.zsh_history.gpg ${HOME}/Dropbox/zsh/backup/`date '+%Y%m%d%H%M%S'`.gpg; rclone sync ${HOME}/Dropbox/zsh drive:backup'

.zshrcのaliasで実現しています。これで .zsh_history を失うリスクを今後も心配しなくてもよさそうです。

複数のパソコンを使う場合

自宅と会社で異なるパソコンを使っている場合は.zshrcに以下のように設定してはじめからDropboxに.zsh_historyを置いておけばよいでしょう。

.zshrc

HISTFILE=~/Dropbox/zsh/.zsh_history

会社と家を瞬間移動できる人間でない限り、Dropboxの同期の速度を越えて帰宅することはできないと思うので.zsh_historyがコンフリクトすることはないでしょう。この場合、.zsh_historyの位置が変わるのでzshbackupコマンドは適宜変更してください。私ははじめからDropboxに.zsh_historyを置いておいてzshbackupで一日一回バックアップしているので私のdotfilesが参考になると思います。

ただし頭の硬い組織に属している人はこの方法は使えません。Dropboxに生の.zsh_historyを置かなければならないので暗号化できないからです。頭の硬い組織に属している人で会社と家で異なるパソコンを使う場合はパソコンから離れる時は必ずzshbackupを実行するようにすれば.zsh_historyがコンフリクトしないでしょう。ただしパソコンを立ち上げた時は必ず暗号化したバックアップから最新の.zsh_historyを取り出して同期する必要があります。zshbackupを実行してからシャットダウンするシャットダウンラッパーコマンドと暗号化したデータから最新の.zsh_historyを取り出すコマンドを作成するなどして対処すればよいのではないでしょうか。

MELPA

同じ方法でMELPAのパッケージも管理しています。 MELPAのパッケージは誰でもMaintainerになれるわけではないので質が高く仕事にならないようなバグが長期間放置されることはほとんどありませんが、万が一Emacsが動かないようなバグで仕事に支障をきたす事態が起きないとは限らないので私が使っているMELPAパッケージはローテートで8世代分のバックアップをとっています。このような事態になったらバックアップのMELPAパッケージを適応したEmacsで起動すれば仕事に支障をきたすことはないでしょう。MELPAの品質は高いので心配はないでしょうが、このように準備をしておくと気兼ねなくMELPAのパッケージを最新に追従しておくことができます。

mkdir -p ${HOME}/Dropbox/emacs/elpa/

ディレクトリを用意しておきます。

tar cfz ${HOME}/Dropbox/emacs/elpa/`date '+%Y%m%d%H%M%S'`.tar.gz -C ${HOME}/.emacs.d elpa

まずは8世代分のMELPAパッケージを管理したいので8回このコマンドをたたきます。 パッケージ管理にCaskを使っている場合はelpaディレクトリに保存されないので適宜変更してください。

.zshrc

alias melpabackup='rm -rf ${HOME}/Dropbox/emacs/elpa/`ls -rt ${HOME}/Dropbox/emacs/elpa | head -n 1`; tar cfz ${HOME}/Dropbox/emacs/elpa/`date '+%Y%m%d%H%M%S'`.tar.gz -C ${HOME}/.emacs.d elpa'

これで毎朝

melpabackup

と叩くだけでMELPAで使っているパッケージがローテートしてバックアップされます。 私は毎朝MELPAパッケージをUpdateするのでその直前にこのコマンドを実行するようにしています。

Makefile

最後にrcloneが気に入ったのでMakefileを作っておきましょう。 rclone.confは機密情報を含んでいますので設定ファイルはDropboxで管理します。

rclone: ## Install and deploy rclone
	sudo pacman -S rclone
	chmod 600   ${HOME}/Dropbox/zsh/rclone.conf
	mkdir -p ${HOME}/.config/rclone
	ln -vsf ${HOME}/Dropbox/zsh/rclone.conf   ${HOME}/.config/rclone/rclone.conf

allinstall: ttf-cica install init initdropbox pipinstall goinstall aur mozc neomutt docker mariadb redis rbenv rustinstall nodeinstall screenkey dnsmasq desktop chromium jekyll sxiv zeal zoom toggle sylpheed rclone

これで次回のクリーンインストール時には

make rclone

だけでこの設定が適応されます。

make allinstall

を実行するとmake rcloneも実行されるので更に簡単になります。


  1. 災害時に何ももたずに逃げるためには開発環境であるパソコンがなくなってもよい状況を作らなければなりません。私の場合はThinkPad X1 Carbonを買ってくればいつでも開発環境が再現できるようにしています。詳しくはこちらの記事を参考にしてください。 ↩︎

  2. そもそもシェルの履歴にパスワードが残るのは問題外ですが、外部にどのようなコマンドが利用されているか機密にしたい組織もあるのでしょう。 ↩︎

タグ一覧

お仕事のご相談などはこちらからどうぞ

お仕事の依頼はこちらからどうぞ