どこでも動く dotfiles の作り方


今年も残りわずかになりましたね。年末年始のお休みを利用して、皆さんの設定ファイルを大掃除してみてはいかがですか?

ということで、今回は dotfiles のお話です。

私流の dotfiles のサンプルは https://github.com/Gandats/dotfiles-sample に置いてあります。
この記事を読まなくても、このサンプルの README.md を読んで、必要なファイルをコピーすれば便利に管理できるかもしれません。

dotfiles とは?

dotfiles とは、普段自分が使っているソフトウェアの設定ファイルを管理するリポジトリです。Unix系のOSでは設定ファイルは慣例的に . から始まる隠しファイル( .bashrc.vimrc など)になっていることからこう呼ばれています。

これらをまとめてバージョン管理しておくことで、どこでも一瞬で自分好みの快適な環境を作り出せます!

ステップ1. dotfilesを始めよう

私流の dotfiles のサンプルは https://github.com/Gandats/dotfiles-sample に置いてあります。

まずは最小限の dotfiles

  1. GitHub で dotfiles というリポジトリを作成
  2. 作成したリポジトリをホームディレクトリ直下に clone
  3. 各種設定ファイルをリポジトリ内にコピー (例: mv ~/.bashrc ~/dotfiles/.bashrc
  4. もとの場所にシンボリックリンクを作成 (例: ln -s ~/dotfiles/.bashrc ~/.bashrc
  5. コミット & プッシュ
  6. 新しい環境では clone してシンボリックリンクを作成

dotfiles で管理すべきものとそうでないもの

管理すべきものの例

  • シェルの設定
    • .bashrc .bash_profile .zshrc .zshenv
  • エディタの設定
    • .vimrc .emacs.d/init.el
  • CLIツールの設定
    • .tmux.conf .tigrc

管理すべきでないものの例( .gitignore に追加する)

  • 常に自動で更新されるファイル(履歴やキャッシュ等)
    • .bash_history .zsh_history .zcompcache .zcompdump .viminfo .emacs.d/history
  • バックアップファイル
    • *~ *.swp
  • 環境に大きく依存する設定(参考: Tips
    • 環境変数 PATHLD_LIBRARY_PATH 等に明らかに環境固有の場所を追加するとき
    • CUDAの設定

管理してはいけないものの例

  • 認証情報が入ったディレクトリやファイル
    • .ssh .aws

ステップ2. 自動でシンボリックリンクを作成するスクリプトを書こう

私流の dotfiles のサンプルは https://github.com/Gandats/dotfiles-sample に置いてあります。

毎回、新しい環境でシンボリックリンクを作成し直すのは(特に設定ファイルが増えると)意外と大変です。
自動でシンボリックリンクを作成するスクリプトを書いてみましょう。

自動スクリプトを作成する

  1. リポジトリ直下に dotfiles というディレクトリを作って、設定ファイルをそこに移動してください。

    ターミナルにて
    cd <リポジトリのパス>
    mkdir dotfiles
    mv .* dotfiles
    
  2. リポジトリ直下に scripts ディレクトリを作成し、そこに link.sh を作成。

    ターミナルにて
    mkdir scripts
    cd scripts
    touch link.sh
    
  3. link.sh を編集

    link.sh
    #!/bin/sh
    
    dotfiles_root=$(cd $(dirname $0)/.. && pwd)
    
    # dotfilesディレクトリの中身のリンクをホームディレクトリ直下に作成
    cd ${dotfiles_root}/dotfiles
    for file in .*; do
        ln -s ${PWD}/${file} ${HOME}
    done
    
  4. 実行権限を付与

    ターミナルにて
    chmod +x link.sh
    

これでターミナルで ./link.sh を実行するとシンボリックリンクが自動的に作成されるようになります。

このときのリポジトリのディレクトリ構造の例です。

├── .gitignore
├── dotfiles  # 設定をまとめるディレクトリ
│   ├── .bashrc
│   ├── .vim
│   │   └── ...
│   └── .zshrc
└── scripts         # リンク作成等のスクリプトをまとめるディレクトリ
    └── link.sh     # リンク作成スクリプト

ステップ3. どこでも動く dotfiles にしてみよう

私流の dotfiles のサンプルは https://github.com/Gandats/dotfiles-sample に置いてあります。

展開場所を考える

ステップ2で作成したスクリプトにはまだいくつかの問題点があります。

ケース1
neovim のように設定ファイルがホームディレクトリ直下でないソフトウェアに対応できない
- neovim の設定ファイルは ~/.config/nvim/init.vim

ケース2
VSCode のようにOSごとに設定ファイルの置き場所が異なるソフトウェアに対応できない
- Linux では ~/.config/Code/User/settings.json
- macOS では ~/Library/ApplicationSupport/Code/User/settings.json

ケース1 任意の場所にシンボリックリンクを作成

ここでは、 dotfileslinklist.txt を作成して、そこに展開場所を記述しましょう。

linklist.txt の書き方

  • 各行は空白で区切られた2つのフィールド
    • 1つめはターゲット( dotfiles 内のパス)
    • 2つめはリンクのパス
      • ~ や環境変数が利用可能
  • # 以降や空行は無視される
  • 不要な設定は script/link.sh の実行前に # でコメントアウトすることでリンクが作成されない
linklist.txt
# vim
.vim                ${HOME}/.vim

# neovim
nvim                ${HOME}/.config/nvim

# zsh
.zsh.d              ${HOME}/.zsh.d
.zshenv             ${HOME}/.zshenv

link.sh を修正

linklist.txt の内容を読むように link.sh の内容を修正しましょう。

link.sh
#!/bin/sh

dotfiles_root=$(cd $(dirname $0)/.. && pwd)

# linklist.txtのコメントを削除
__remove_linklist_comment() {(
    # '#'以降と空行を削除
    sed -e 's/\s*#.*//' \
        -e '/^\s*$/d' \
        $1
)}

# シンボリックリンクを作成
cd ${dotfiles_root}/dotfiles
linklist="linklist.txt"
[ ! -r "$linklist" ] && return
__remove_linklist_comment "$linklist" | while read target link; do
    # ~ や環境変数を展開
    target=$(eval echo "${PWD}/${target}")
    link=$(eval echo "${link}")
    # シンボリックリンクを作成
    mkdir -p $(dirname ${link})
    ln -fsn ${target} ${link}
done

ケース2 OSごとにシンボリックリンクを切り替える

Unix系OSでは uname コマンドによってOSの名前( Linux Darwin FreeBSD 等)を取得できます。
ここでは、この値を利用してシンボリックリンクを切り替えてみましょう。
(より詳細に切り替えたい方は OSTYPE 環境変数などを利用しても良いかもしれません。)

linklist.txt をOSごとに作成

先程まで利用していた linklist.txt を以下のように変更します。(FreeBSD等に対応させる場合はLinuxやDarwinの部分を uname に置き換えてください。)

  • linklist.Unix.txt
    • Unix系OSに共通のもの依存しないもの( ~/.bashrc など)
  • linklist.Linux.txt
    • Linuxに固有のもの( ~/.config/Code/User/settings.json など)
  • linklist.Darwin.txt
    • macOSに固有のもの( ~/Library/ApplicationSupport/Code/User/settings.json など)
linklist.Unix.txt
# vim
.vim                ${HOME}/.vim

# neovim
nvim                ${HOME}/.config/nvim

# zsh
.zsh.d              ${HOME}/.zsh.d
.zshenv             ${HOME}/.zshenv
linklist.Linux.txt
# Visual Studio Code
Code/User/settings.json      ${HOME}/.config/Code/User/settings.json
Code/User/keybindings.json   ${HOME}/.config/Code/User/keybindings.json
Code/User/locale.json        ${HOME}/.config/Code/User/locale.json
Code/User/snippets           ${HOME}/.config/Code/User/snippets
linklist.Darwin.txt

# Visual Studio Code
Code/User/settings.json      ${HOME}/Library/ApplicationSupport/Code/User/settings.json
Code/User/keybindings.json   ${HOME}/Library/ApplicationSupport/Code/User/keybindings.json
Code/User/locale.json        ${HOME}/Library/ApplicationSupport/Code/User/locale.json
Code/User/snippets           ${HOME}/Library/ApplicationSupport/Code/User/snippets

link.sh を修正

これに対応して link.sh も修正します。

link.sh
#!/bin/sh

# ~~~ 省略 ~~~

cd ${dotfiles_root}/dotfiles
for linklist in "linklist.Unix.txt" "linklist.$(uname).txt"; do
    [ ! -r "${linklist}" ] && continue

    __remove_linklist_comment "$linklist" | while read target link; do
        # ~ や環境変数を展開
        target=$(eval echo "${PWD}/${target}")
        link=$(eval echo "${link}")
        # シンボリックリンクを作成
        mkdir -p $(dirname ${link})
        ln -fsn ${target} ${link}
    done
done

Tips

  • gitのコミットメッセージの先頭にソフトウェア名を入れると見やすくなり、revert時などに便利です。
    • 例: [vim] 行番号を表示するように変更[bash] 履歴を10,000件保存するように変更
  • link.sh 同様に unlink.sh もあると意外と便利です。
  • シェルの設定は機能ごとにファイルを分割すると管理しやすいです。
    • 環境固有の設定ファイルを example.local.sh とし、 .gitignore*.local.sh を追加すると自動的に管理対象外となります。
  • 本当にどこでも動くようにするにはシェルスクリプトをPOSIXコマンド・オプションのみで書くと良いかもしれません
    • 今回の例では ln コマンドの -n オプションはPOSIXの範囲外です。

最後に

dotfiles の作り方をいくつかのステップに分けたため、予想以上に長くなってしまいました。

私のサンプルが https://github.com/Gandats/dotfiles-sample にあるので、よかったら参考にしてみてください。