【2015/07/16 追記】優れた dotfiles を設計する - TELLME.TOKYO
この記事では書かなかった全体のロジックについて書きました
Dotfiles Driven Development
dotfiles とは Unix 系 OS で俗に言う設定ファイルのことです。.vimrc
や .zshrc
など、設定ファイルの多くは隠しファイルとしてファイル名の頭にドットがつくことからそう呼ばれています。
ほとんどのエンジニアは CLI 環境での開発は避けては通れないものに思います。CLI 環境は「黒い画面」として敬遠されがちで、CLI になると格段に作業効率がダウンする人も少なく無いです。その作業を効率化するキーとなるのは、設定ファイルの習熟度にあると思います。GUI 開発環境と比べてこちらはテキストベースでカスタマイズできるため、究極まで自分好みに合わせることが可能です。こうした dotfiles のカスタマイズ駆動で開発をすることで効率性は大きく向上します。
また、基本的にソフトウェアの設定は時間的コストと学習コストがかかるものです。これらを失うのは開発効率で大変なディスアドバンテージとなるので、変更履歴の保存とバックアップを兼ねて GitHub で管理するのがマストです。
この dotfiles について管理方法などは様々かと思いますが、個人的には dotfiles の設定はほとんど自動化しています。今回は、dotfiles の運用についての一般的な方法1、と 筆者が dotfiles 関連のタスクをどのように自動化しているのかについて紹介したいと思います。
リポジトリ
筆者のリポジトリは公開しています。
冒頭のスクリーンショットにあるとおり、ワンコマンドで環境構築が可能です。ターミナルを立ち上げて以下のコマンドをコピペするだけです。
bash -c "$(curl -fsSL dot.b4b4r07.com)"
筆者だけに限らず、この手の記事はたくさんリファレンスがあるので、参考として引用しておきます(やはり、普段使いの環境についてなのでそれぞれ拘りを感じます)。
参考:
- ナウなヤングのためのgithub入門講座 -基本機能からdotfiles管理まで- - 2011年12月12日
- iTerm2 + zsh + tmux + vim で快適な256色ターミナル環境を構築する - 2012年2月11日
- dotfilesをgithubで管理する! - 2012年3月5日
- dotfilesをgithubで管理し、Vundleを導入する - 2012年3月24日
- githubとhomesickでドットファイルをオシャレに管理する - 2013年4月9日
- GitHub と homesick を使って複数 Mac 間で dotfiles を同期する - 2014年1月10日
- DotfilesをGitHubで管理する - 2014年2月7日
- dotfilesをgithubで管理 - 2014年3月9日
- dotfilesをGitHubで管理する方法 - 2014年5月11日
- dotfileの管理をGithubに置いて一元化したりどこでもセットアップできるようにする - 2014年7月13日
- Homeshickでdotfilesを管理することにした - 2014年8月20日
- GitHubにdotfilesを登録してみたよ - 2014年8月20日
- イケてると思う dotfiles の管理方法 - 2014年12月21日
- dotfilesを整理した - 2015年4月5日
- dotfilesの設定を自動化する - 2015年4月9日
- githubとrcmを使ったモダンdotfiles管理 - 2015年4月23日
dotfiles リポジトリの作成手順
(GitHub のアカウント取得から ssh の設定などは趣旨からそれそうなので割愛します)
- GitHub にて username/dotfiles というリポジトリ名で作成
mkdir dotfiles && cd dotfiles
- ホームディレクトリに転がっているドットファイルを
dotfiles/
にmv
する - シンボリックリンク用のスクリプトファイルを用意する
- あとは
git init
からgit push
まで
以上であなただけの dotfiles リポジトリは完成します。しかし今回は筆者がしばらくこの dotfiles を運用してみてわかったことや便利だと思ったことを後述する運用方法にて説明します。
dotfiles の運用方法
dotfiles リポジトリを作り、開発などをしつつ普段使いの環境の不満などを逐一改善しながら、しばらく運用してきて分かったことがあります。
それは以下のことに沿ってリポジトリを整備することです。
それでは一つずつ見ていきます。
ワンコマンドでインストールできる
GitHub にある dotfiles リポジトリの多くは以下のようなワンコマンドですべてがインストールできるような構成になっていることが多いです。
curl https://{URL}/install.sh | bash
wget
や curl
のワンライナーですべての作業(ダウンロード、デプロイ2、イニシャライズ3)が完結できるようにしましょう。たかがターミナルの設定とはいえ、環境の再構築は簡単にできるに越したことはないです。ワンコマンドですぐに再構築できるのは環境が壊れることを恐れさせない強みになります。
ワンコマンドではなく、
git clone https://github.com/username/dotfiles
cd dotfiles
./install.sh
のような決まり文句で導入を促すケースも多いです。やっていることは同じなので、README.md
などにワンコマンドでインストール出来るようなコピペ用の記述があると優しいと思われます。
デプロイとイニシャライズは切り分ける
この手の環境セットアップに仕方について大きく二通りあると思っています。
-
デプロイ とは
多くのソフトウェアはホームディレクトリにあるドットファイルを設定ファイルとして起動時に読み込みます(例:
.vimrc
)。つまり、ダウンロードした dotfiles リポジトリにあるドットファイルをホームディレクトリにリンクする必要があります。単にファイルをコピーするだけでも同じことですが、シンボリックリンクを貼ることでホームディレクトリにあるドットファイルに追記したり、書き換えたときに自動的に本家(リポジトリでホスティングされているドットファイル)も同期されるので、git push
するだけで更新をアップロードすることが出来ます。このリンクを貼ることを便宜上、デプロイと呼ぶことにします。
デプロイは各ドットファイル毎にln -s dotfiles_dotfile homedir_dotfile
するのはものすごい手間になるので、デプロイ用にスクリプトファイルなどを用意することが多いです。また、このデプロイ用スクリプトは大抵install.sh
やlink.sh
、setup.sh
といった名前でホスティングされています。-
古典的な
install.sh
#!/bin/bash ln -s .vimrc ~/.vimrc ln -s .zshrc ~/.zshrc ln -s .tmux.conf ~/.tmux.conf
これでは、新たにドットファイルがリポジトリに追加するたびに
install.sh
を書き換える必要性があります -
一般化した
install.sh
#!/bin/bash for f in .??* do [[ "$f" == ".git" ]] && continue [[ "$f" == ".DS_Store" ]] && continue echo "$f" done
カレントディレクトリ
"."
と、親ディレクトリ".."
以外のドットファイルを正規表現的にリストアップしています。この場合、新しい設定ファイルなどを追加した際に書き換え不要です。その代わり予め、不要なリンクがドットファイルを省く必要があります(continue
)。
-
-
イニシャライズ とは
イニシャライズとは単にソフトウェアのセットアップ時に必要な初期化処理を指します。dotfiles リポジトリには多くの場合、ドットファイルの他に環境設定用の
*.sh
ファイルが同梱されていたり、実行を促すようなinstall.sh
のオプションやサブコマンドが定義されています(もしくはvim_setup.sh
といった具体的な実行ファイルである場合もある)。
例えば、エディタのカスタムビルドや、パッケージマネージャを通したソフトウェアのインストールやその設定などです。便宜上、これら設定の実行をイニシャライズと呼びます。
このイニシャライズと、デプロイは完全に切り分けるべきです。なぜなら、イニシャライズはその名の通り、リポジトリをダウンロードしたときにしか実行しないからです。一方でデプロイの場合、シンボリックリンクが切れてしまったり、ホストしているリポジトリに新規ファイルがコミットされたときなど、デプロイし直す必要が出てくることが多々あります。
また、どこかのサーバを借りて開発するときなど、大抵の場合は設定ファイルのデプロイさえできればよく、イニシャライズをする必要はありません。
dotfiles のルートディレクトリのディレクトリ構造は簡潔にする
dotfiles のルートディレクトリは GitHub でのファーストビューです。ゴロゴロと規則性もなくディレクトリが多数配置されていては、どこに何があるのかがわかりにくくなり、管理も煩雑化しナンセンスです。UNIX 系 OS の慣習に倣ったディレクトリ命名で役目を明確化させるのがスマートです(例:bin
)。
実際の dotfiles を例に解説
では上記で説いた理論について、筆者の環境を例に紹介します。
ワンコマンドでインストールできる
筆者の dotfiles のセットアップは以下のように、curl
などのダウンローダを使ってワンコマンドで完了できます。
bash -c "$(curl -L dot.b4b4r07.com)"
筆者の場合、タイプ数を減らすための工夫2をしていますが、
bash -c "$(curl -L raw.githubusercontent.com/b4b4r07/dotfiles/master/etc/install)"
と同じことです。
(注:URL を短くするために dot.b4b4r07.com は GitHub の dotfiles リポジトリにリダイレクトされるようになっています)
このワンライナーが一体何をするかは README.md にもありますが、
- リポジトリのダウンロード
- ドットファイルのデプロイ
- (任意で)イニシャライズ
です。
複数の処理ありますが、ワンライナーをコピペ ENTER するだけで完了するのでめっちゃ簡単です。
dotfiles のプロジェクトページを用意しているので見てみてください。「CURL」か「WGET」のボタンを押すとクリップボードにワンライナーがコピーされます。
デプロイとイニシャライズは切り分ける
筆者の場合、install.sh
や link.sh
のようなデプロイ用のスクリプトファイルの代わりに、Make を使用しています。Make を使うのは環境依存性の排除を重視してのことです。
dotfiles のセットアップ時は、環境が整っていない状態なので、なるべくツールの依存性を少なくしなければなりません。Make であれば、だいたいの Unix ライクシステムでは入っていますし、インストールも簡単です。
環境依存性を少なくするために、Bourne Shell、Make を使うのは王道です。
また、この切り分けについて、Make はプログラムのビルド作業を自動化するツールですが、決まったプロトコルを順番に実行する用途にはもってこいの代物です。これを make deploy
と make init
として利用することで、簡単に切り分けることが出来ます。
DOTFILES_EXCLUDES := .DS_Store .git .gitmodules .travis.yml
DOTFILES_TARGET := $(wildcard .??*) bin
DOTFILES_DIR := $(PWD)
DOTFILES_FILES := $(filter-out $(DOTFILES_EXCLUDES), $(DOTFILES_TARGET))
deploy:
@$(foreach val, $(DOTFILES_FILES), ln -sfnv $(abspath $(val)) $(HOME)/$(val);)
init:
@$(foreach val, $(wildcard ./etc/init/*.sh), bash $(val);)
ドットファイルとして、$(wildcard .??*)
を指定しています。これはワイルドカードによって .
や ..
などの要素を除いて、すべてのドットファイルを指定しています。しかし、このままでは関係のないドットファイル(.DS_Store
、.git
など)も含まれてしまうので、ホワイトリスト方式でこれをフィルタリングします。
ドットファイルを一括でターゲットとすることで、新規ドットファイルが追加されてもこの Makefile を修正する必要がないので便利です(ファイルの追加に追従してリンクファイルの修正することを忘れたり、ファイルリストから漏れたりすることで、リンクされないようなバグやそれに関するコンフリクトはよくあります)。
また、イニット用の *.sh
ファイルはまとめて、etc/init/
以下に配置しています。こうすることで、Makefile から foreach
でまとめて実行することができます。
ちなみに、etc/init/
には以下の様なスクリプトファイルが保存されています。
- Vim のカスタムビルド
- Vim plugins を NeoBundle 経由でインストール
- ホームディレクトリにあるディレクトリ名の英語化
- Zsh プラグインマネージャ Antigen のインストール
- シンタックスハイライタ Pygments のインストール
- OS X の
defaults
コマンド群の実行 - Ruby の開発環境を整備
- Gem のインストール
- ...(などの新しい環境で毎回セットアップするうち自動化出来そうなこと)
dotfiles のルートディレクトリのディレクトリ構造は簡潔にする
筆者の場合、dotfiles のリポジトリルートには以下のディレクトリしか配置していません(ドットファイルなディレクトリを除く)。
やはり、GitHub でのファーストビューにあたるディレクトリ階層は綺麗に役割をもたせた構成にするのが吉です。ただし、役割を分けすぎて第1階層にディレクトリがあふれるのは好ましくないです。見づらさが増すだけです。大別して何個かのディレクトリを作成し、その中で役割のあるディレクトリ構成にしましょう。
-
bin/
自作のコマンドスクリプトやバイナリなどの保管場所。
.zshrc
などではPATH=$PATH:~/bin
などとしてパスを通します。ドットファイルに加えてbin/
もデプロイのリストにあるので、ホームディレクトリにリンクされます。make list
でデプロイされるファイル・ディレクトリの列挙が可能です。 -
doc/
ドキュメンテーションがメインですが、ある意味エクストラファイルの保存場所です。設定ファイルでも、自作コマンドでもないもの(マニュアル他、README 用の画像など)が配置されます。
-
etc/
設定用のスクリプトファイルや、コード関連のファイルの保存場所です。また、
etc/init
以下にあるスクリプトは dotfiles のインストール時のイニシャライズに使用されます。doc/
との違いは、コード片か非コード片かだけです。
以上の3ディレクトリと Makefile
、README.md
以外はすべてドットファイルです。見渡し良好でどのディレクトリに何があるか、どんな役割のディレクトリかがすぐに分かります。
各種プラグインについて
Vim や Zsh などプラグインで設定をカスタマイズするソフトウェアも多く、そのプラグインの管理や扱いについても注意すべきでしょう。各種プラグインはリポジトリから切り離すことが必要です。
案1: ただのディレクトリとして取り込む
ダウンロードしたプラグインを、それぞれのプラグインディレクトリ(~/.vim/bundle
や ~/.antigen
)にコピーするだけです。しかし、この方法は次のような欠点があります
- プラグインのアップデートの取得が大変。自分で更新を確認して上書きコピーする必要がある
- 大きいプラグインの場合、自分のリポジトリのサイズも肥大化する
- プラグイン数が多いとダウンロードに時間が掛かる
案2: Git のサブモジュールとして取り込む
プラグインは GitHub などで管理されていることが多いので、アップデートなどの更新を取得するために Git のサブモジュールとして取り込む方法です。
しかし、これも自分のリポジトリサイズを大きくしたりダウンロードに時間が掛かるだけでなく、複雑な Git 操作を強いられるため初心者にはお勧めできません。
案3: プラグインマネージャを使用する
Vim も Zsh もそれぞれ、NeoBundle や Antigen といった有名なプラグインマネージャがあるのでそれを利用しましょう。
-
Vim
筆者の場合、Vim の起動時に NeoBundle がなかった場合にのみ実行できる
:NeoBundleInit
というコマンドを定義しています。.vimrclet $VIMBUNDLE = '~/.vim/bundle' let $NEOBUNDLEPATH = $VIMBUNDLE . '/neobundle.vim' if stridx(&runtimepath, $NEOBUNDLEPATH) != -1 " If the NeoBundle doesn't exist. command! NeoBundleInit try | call s:neobundle_init() \| catch /^neobundleinit:/ \| echohl ErrorMsg \| echomsg v:exception \| echohl None \| endtry function! s:neobundle_init() redraw | echo "Installing neobundle.vim..." if !isdirectory($VIMBUNDLE) call mkdir($VIMBUNDLE, 'p') echo printf("Creating '%s'.", $VIMBUNDLE) endif cd $VIMBUNDLE if executable('git') call system('git clone git://github.com/Shougo/neobundle.vim') if v:shell_error throw 'neobundleinit: Git error.' endif endif set runtimepath& runtimepath+=$NEOBUNDLEPATH call neobundle#rc($VIMBUNDLE) try echo printf("Reloading '%s'", $MYVIMRC) source $MYVIMRC catch echohl ErrorMsg echomsg 'neobundleinit: $MYVIMRC: could not source.' echohl None return 0 finally echomsg 'Installed neobundle.vim' endtry echomsg 'Finish!' endfunction autocmd! VimEnter * redraw \ | echohl WarningMsg \ | echo "You should do ':NeoBundleInit' at first!" \ | echohl None endif
これは、NeoBundle がないときに Vim を起動すると
:NeoBundleInit
を実行するように促し、するとgit clone
で NeoBundle をインストールし、そのまま大量のプラグインをインストールします。 -
Zsh
Zsh ではログイン時に起動されるべき項目などを
zsh_at_startup
として関数化してあります。antigen の初期化や tmux の起動などです。tmux については別記事で詳細に解説してあるのでそちらも参考にしてみてください。.zshrcantigen_plugins=( "brew" "zsh-users/zsh-completions" "zsh-users/zsh-history-substring-search" "zsh-users/zsh-syntax-highlighting" "hchbaw/opp.zsh" #"tarruda/zsh-autosuggestions" "b4b4r07/enhancd" "b4b4r07/favdir" #"b4b4r07/zsh-vi-mode-visual" ) function zsh_at_startup() { # ...(省略)... tmux_automatically_attach # Antigen if [[ -f ~/.antigen/antigen.zsh ]]; then echo -e "=> $fg[blue]Setup antigen....$reset_color" local plugin source ~/.antigen/antigen.zsh for plugin in "${antigen_plugins[@]}" do echo "checking... $plugin" antigen bundle "$plugin" done antigen apply echo -e "=> $fg[blue]done$reset_color\n" fi # Hello, Zsh!! echo -e "\n$fg_bold[cyan]This is ZSH $fg_bold[red]${ZSH_VERSION}$fg_bold[cyan] - DISPLAY on $fg_bold[red]$DISPLAY$reset_color\n" } if zsh_at_startup; then zsh_set_utilities zsh_set_prompt zsh_set_completion zsh_set_setopt zsh_set_alias zsh_set_keybind fi
プラグインを自分のリポジトリに取り込む利点は、ワンコマンドで dotfiles をインストールしたときにソフトウェアの設定を完全に再現できることにあります。しかし、プラグインの数が増えるごとに容量も増え、複雑になるのが欠点でした。
そこで重要なのが、シェルやエディタなどの CLI のインフラにあたるところはプラグインなしでも、最低限に快適な作業ができるような設定を心がけるべきです。筆者の場合、エディタに必ず必要と感じているプラグイン(MRU; 最近開いたファイルリストを表示する)を自分製に小型化し、設定ファイルに関数として記述しています。
そして、プラグインはリポジトリから切り離し、それぞれのソフトウェアの中でギリギリまで設定を高めるのがおすすめです。そうすることで、プラグインをインストール出来ない環境や、まったりとダウンロードやインストールが終わるのを待っていられない状況などに即座に開発が開始できます。
まとめ
- dotfiles のインストールはワンコマンドでする
- 3ステップのタスク、後者2つの独立性
- 1.ダウンロード
- 2.デプロイ
- 3.イニシャライズ
- デプロイは簡単にできる
- プラグインはリポジトリから切り離す
- そのため設定ファイルでのカスタマイズを怠らない
シェルスクリプトと Makefile
筆者の環境を例に説明してきましたが、筆者の場合はシェルスクリプトと Makefile のみでデプロイやイニシャライズを行っています。しかし、GitHub で「dotfiles」で検索してみると、Rakefile(Ruby)や他のスクリプト言語系(Python、Lua、Prolog)などで行っているケースが多々見られます。OS X などのデフォルトで Ruby ツールがインストールされている環境でのみのセットアップを想定しているのなら問題はないですが、あらゆるほとんどの環境でもセットアップできるようにするためには、なるべく枯れた技術・汎用性の高い技術を使用するべきです。また、シェルスクリプトにも方言やシェルの種類などもあるので、セットアップ用のシェルスクリプトは、汎用性を高めるため POSIX に準拠した /bin/sh
で書くのがベターでしょう(それ以外のシェルスクリプトは bash で書いちゃってもいいと思います4)。
参考:
- どの環境でも使えるシェルスクリプトを書くためのメモ ver3.20
- ポータブルなシェルスクリプトを書く - 拡張 POSIX シェルスクリプト Advent Calendar 2013 - ダメ出し Blog
dotfiles の設定は楽しい
冒頭にも述べたとおり、設定ファイルのカスタマイズは CLI 環境の生産性を高めることにつながります。とても便利な CLI ライフが手に入るので、GitHub「dotfiles」でスターの高いリポジトリを参考にしてみたり、筆者の環境 b4b4r07/dotfiles を例にカスタマイズしてみてください。
す
コメント
@b4b4r07リンクをコピー このコメントを報告 1
@tay7リンクをコピー このコメントを報告 0
@b4b4r07(編集済み) リンクをコピー このコメントを報告 0
@tay07212(編集済み) リンクをコピー このコメントを報告
0
@okamosリンクをコピー このコメントを報告 - dotfilesをGithubで管理するときの3つのポイント
2
@jhrkYリンクをコピー このコメントを報告 0
@b4b4r07リンクをコピー このコメントを報告 0
@yoshimasa_iリンクをコピー このコメントを報告 0
@sachin21 ここを見てみてください。独自ドメインと Bitly の組み合わせです:dotfiles を curl -L dot.hoge.com | sh でインストールする方法
dotfilesをインポートした後、vimの起動時に固まってしまうので、
NeoBundleInit
がシェルスクリプトでも実行できるとありがたいです。(brew gradeup vim 済)@tay7
これはエラーで、ということでしょうか。それとも
NeoBundleInstall
が走って Vim が操作できなくなるという意味の「固まってしまう」でしょうか。ソースを見られたかとは思いますが
NeoBundleInit
は neobundle.vim のインストールと、NeoBundleInstall
を同時にやるだけのコマンドです。シェルスクリプトにすることは出来ますが、結局NeoBundleInstall
は neobundle.vim にやってもらうしかないので、そうなると結局しばらくは操作できなくなります(bg
にまわすなどを除き)。前者なら修正の余地はありそうです。
NeoBundleInitを起動する前の状態で、
:
キーを打っても無視されます。なぜか、
!
キーを先に打つと、:
に反応するので、その後に出てくるごみをバックスペースで消して、NeoBundleInit
の起動に成功しました。なお、NeoBundleのインストールスクリプトに、vimproc_mac.soのmakeが抜けているようです。
(.vim/bundle/vimprocのところで手動でmakeしたあと、vi(m)を起動したら無事動きました。)
vimの設定後、やはり
:
キーが単独で反応しなくなっていて、!:
の組み合わせでとりあえず使っています。Dotfilesの参考としているリンク
がフィッシングサイトにリダイレクトしています
@b4b4r07
すいません、初心者ですが、
bash -c "$(curl -L raw.githubusercontent.com/b4b4r07/dotfiles/master/etc/install)"
を実行するとき、以下のエラーが出ました:
➜ Restarting your shell...
bash: /root/.dotfiles/etc/lib/vital.sh: そのようなファイルやディレクトリはありません
cannot vitalize, cannot start /bin/bash
bash-4.2#
これは理解出来ませんので、解決方法があれば教えていただけますでしょうか?
@jhrkY この記事を書いたときからだいぶinstallファイルの中身が変わっているせいだと思います。記事を参考にしたとしても鵜呑みにされず、ご自身にあった設定を模索するのが良いかもしれません。すみません。
@b4b4r07
見やすいプロンプトがないかなと調べているとここへたどり着きました。
設定を真似させてほしいのですが、
プロンプトの設定はどのファイルになりますか?