dotfilesの管理を楽にしてくれそうなシェルスクリプト書いた


## はじめに

普段から複数マシンを使っていると、設定ファイルを共有したいことがよくあります。簡単な方法としてGitHubにdotfilesとして設定ファイル群を上げておいて、必要なときにpullしてきてシンボリックリンクを張るというのが普通ではないでしょうか?
今どき流行りのツールを入れて勝手にシンボリックリンクを張ってもらうやり方もあるでしょうが、結局その設定ファイルを書くことに追われるだろうなぁと思っていたので、初めにdotfiles内のファイルとホームディレクトリに配置する位置の関係を書いておいて、それを参照してシンボリックリンクを張っていくシェルスクリプトを書いてみました。

## 使い方

使い方は簡単で、設定ファイル`setup_config_link`にリンクの対応を載せておいた状態で(予めコメントアウトすることもできます)、setup.shを実行するだけです。該当するディレクトリが無い場合には、そこまでディレクトリを作成するか聞いてから作成したり、すでにシンボリックリンクが張ってある時には無視したり、すでにファイルが存在する場合には、その差分を表示するか、強制的にシンボリックリンクを張るか、バックアップファイルを作ってリンクを張るか、何もしないかを選択することができます。

![optimised.gif](https://qiita-image-store.s3.amazonaws.com/0/54869/28c42ccc-7b4e-b966-4df9-423b485964a1.gif)

**(追記: 2015/05/23)**
実際に使ってみて、結局一番面倒なのはドットファイルに登録する操作なのだと気がついたので、簡単なシェルスクリプトを追加しておきます([dotmv](https://github.com/ssh0/dotfiles/blob/master/bin/dotmv))。任意のディレクトリ(今のところホームフォルダ以下である必要があります)内で、

```bash
$ dotmv hoge.config
```

もしくは

```bash
$ dotmv -m "for application hoge" hoge.config
```

などとすれば、dotfiles直下にファイルが移動され、それまでそのファイルがあった位置にシンボリックリンクが張られます。また、別のマシンでも同じ操作をするために、`setup_config_link`の最後に対応関係を追記するようにしました。オプション`-m`をつければ、リンクについてコメントをつけることができます。

![optimised.gif](https://qiita-image-store.s3.amazonaws.com/0/54869/f2d2e0ee-ae25-4e5e-8f40-e12dfb546b13.gif)


## スクリプト

以下にスクリプト全体を載せておきますが、難しいこと考えずに使いたい人は、GitHubからダウンロードしてみてください。

ターミナルからなら

```bash
$ curl https://raw.githubusercontent.com/ssh0/dotfiles/master/setup.sh -o setup.sh
$ curl https://raw.githubusercontent.com/ssh0/dotfiles/master/setup_config_link -o setup_config_link
$ curl https://raw.githubusercontent.com/ssh0/dotfiles/master/bin/dotmv ~/bin/dotmv
```


もしくはブラウザから表示してコピペしてください。
- [setup.sh](https://raw.githubusercontent.com/ssh0/dotfiles/master/setup.sh)
- [setup_config_link](https://raw.githubusercontent.com/ssh0/dotfiles/master/setup_config_link)
- [dotmv](https://raw.githubusercontent.com/ssh0/dotfiles/master/bin/dotmv)

```bash:setup.sh
#!/bin/sh
# written by Shotaro Fujimoto (https://github.com/ssh0)
# read link configuration from ./setup_config_link

dotdir=$(cd "$(dirname "$0")"; pwd)
home=$HOME

red=31
# green=32
yellow=33
# blue=34
# cyan=36

cecho() {
  color=$1
  shift
  echo "\033[${color}m$@\033[m"
}

info() {
  # verbose confirmation
  echo ""
  echo "${1} -> ${2}"
}

for l in $(grep -Ev '^#' setup_config_link | grep -Ev '^$'); do
    dotfile="${dotdir}/$(echo "$l" | awk 'BEGIN {FS=","; }  { print $1; }')"
    orig="${home}/$(echo "$l" | awk 'BEGIN {FS=","; }  { print $2; }')"
  if [ ! -e "${dotfile}" ]; then
    echo ""
    cecho $red "dotfile '${dotfile}' doesn't exist."
    continue
  fi

  # if dir is not exist: mkdir
  origdir=$(dirname "${orig}")
  if [ ! -d "${origdir}" ]; then
    info "${dotfile}" "${orig}"
    flag=false
    cecho $red "'${origdir}' doesn't exist."
    echo "[message] mkdir '${origdir}'? (y/n):"
    while read yn; do
      case $yn in
        [Yy] ) mkdir -p "${origdir}"; break ;;
        [Nn] ) flag=true; break ;;
        * ) echo "Please answer with y or n." ;;
      esac
    done
    if $flag; then
      continue
    fi
  fi

  # if file already exists: open interaction menu
  if [ -e "${orig}" ]; then
    if [ -L "${orig}" ]; then
      continue
    else
      info "${dotfile}" "${orig}"
      cecho $yellow "file or directory already exists."
      while true; do
        echo "(d):show diff, (f):overwrite, (b):make backup, (n):do nothing"
        read line
        case $line in
          [Dd] ) diff -u "${dotfile}" "${orig}"
            echo "" ;;
          [Ff] ) if [ -d "${orig}" ]; then
                    rm -r "${orig}"
                  else
                    rm "${orig}"
                  fi
                  echo "'${orig}' -> '${dotfile}'"
                  ln -s "${dotfile}" "${orig}"
                  break ;;
          [Bb] ) ln -sbv --suffix '.orig' "${dotfile}" "${orig}"
            break ;;
          [Nn] ) break ;;
          # ? ) echo "Please answer with d, f, b or n." ;;
        esac
      done
    fi
  else
    # otherwise make symbolic file normally
    echo "'${orig}' -> '${dotfile}'"
    ln -si "${dotfile}" "${orig}"
  fi
done
```


```bash:setup_config_link
#!/bin/sh <- this is for easy writing with editor.
# This file is config file for setup.sh
# You can comment out by adding "#" at the beginning of the line.
# And an empty line is ignored by setup.sh.
#
# Format:
# <dotfile>,<linkto>
#
# the script automatically add directory information to the file path.
# So, you should write like below
#     .vimrc,.vimrc

# ~/.Xmodmap:
# xmodmap is a utility for modifying keymaps and pointing button mappings in Xorg.
.Xmodmap,.Xmodmap

# ~/.Xdefaults
.Xdefaults,.Xdefaults

# ~/.Xresources:
# Xresources is a user-level configuration dotfile
# https://wiki.archlinux.org/index.php/X_resources#XScreenSaver_resources
.Xresources,.Xresources

# ~/.xscreensaver:
# XScreenSaver Preferences File
.xscreensaver,.xscreensaver

# ~/.profile:
# executed by the command interpreter for login shells.
.profile,.profile

# [vim](http://www.vim.org/):
# Vim is an advanced text editor that seeks to provide the power of the de-facto
# Unix editor 'Vi', with a more complete feature set.
vimfiles/vimrc,.vimrc
vimfiles/ftplugin/tex_quickrun.vim,.vim/ftplugin/tex_quickrun.vim
vimfiles/ftplugin/markdown_quickrun.vim,.vim/ftplugin/markdown_quickrun.vim
# markdown template
vimfiles/template/template.md,.vim/template/template.md
vimfiles/template/template.mkd,.vim/template/template.mkd
# Tex template
vimfiles/template/template.tex,.vim/template/template.tex
# Python template
vimfiles/template/template.py,.vim/template/template.py
# Shell script template
vimfiles/template/template.sh,.vim/template/template.sh

# [zsh](http://zsh.sourceforge.net/):
# Zsh is a shell designed for interactive use, although it is also a powerful
# scripting language.
zshfiles/zshrc,.zshrc
zshfiles/zshrc.mine,.zshrc.mine
zshfiles/aliases.mine,.aliases.mine
zshfiles/aliases.zsh,.oh-my-zsh/lib/aliases.zsh
zshfiles/themes/agnoster.zsh-theme,.oh-my-zsh/themes/agnoster.zsh-theme
zshfiles/history.zsh,.oh-my-zsh/lib/history.zsh

# [ranger](http://ranger.nongnu.org/):
# ranger is a file manager with VI key bindings.
ranger/colorschemes/mycolor.py,.config/ranger/colorschemes/mycolor.py
ranger/rc.conf,.config/ranger/rc.conf
ranger/rifle.conf,.config/ranger/rifle.conf
ranger/scope.sh,.config/ranger/scope.sh
ranger/commands.py,.config/ranger/commands.py

# [compton](https://github.com/chjj/compton):
# Compton is a compositor for X, and a fork of xcompmgr-dana.
compton/compton.conf,.config/compton/compton.conf

# [ipython](http://ipython.org/):
# IPython provides a rich architecture for interactive computing with:
#     - Powerful interactive shells (terminal and Qt-based).
#     - A browser-based notebook with support for code, rich text, mathematical
#       expressions, inline plots and other rich media.
#     - Support for interactive data visualization and use of GUI toolkits.
#     - Flexible, embeddable interpreters to load into your own projects.
#     - Easy to use, high performance tools for parallel computing.
ipython/nbextensions/livereveal/main.css,.ipython/nbextensions/livereveal/main.css
ipython/profile_default/static/custom/custom.css,.ipython/profile_default/static/custom/custom.css
ipython/profile_slide/static/custom/custom.css,.ipython/profile_slide/static/custom/custom.css
ipython/nbextensions/livereveal/main.css,ipython/nbextensions/livereveal/main.css
ipython/profile_default/static/custom/custom.css,ipython/profile_default/static/custom/custom.css
ipython/profile_slide/static/custom/custom.css,ipython/profile_slide/static/custom/custom.css

# [tudu](http://cli-apps.org/content/show.php/TuDu?content=129325):
# TuDu is a comand line interface to manage hierarchical todos. Each task has a
# title, a long text description, a deadline (tudu warns you when the date is
# close), and a scheduled date. There are categories and priorities.
tudurc,.tudurc

# [latexmk](http://www.ctan.org/pkg/latexmk/):
# Latexmk completely automates the process of generating a LaTeX document.
.latexmkrc,.latexmkrc

# [notify-osd](https://wiki.ubuntu.com/NotifyOSD):
# Canonical's on-screen-display notification agent, implementing the
# freedesktop.org Desktop Notifications Specification with semi-transparent
# click-through bubbles.
.notify-osd,.notify-osd

# [vimperator](https://addons.mozilla.org/ja/firefox/addon/vimperator/):
# Make Firefox look and behave like Vim
.vimperatorrc,.vimperatorrc

# [mpv](http://mpv.io/):
# mpv is a fork of mplayer2 and MPlayer. It shares some features with the former
# projects while introducing many more.
.mpv,.mpv

# [cmus](https://cmus.github.io/):
# cmus is a small, fast and powerful console music player for Unix-like
# operating systems.
cmus,.cmus

# [mutt](http://www.mutt.org/):
# E-Mail Client
mutt/muttrc,.mutt/muttrc

# [xmonad](http://xmonad.org/):
# xmonad is a dynamically tiling X11 window manager that is written and
# configured in Haskell. In a normal WM, you spend half your time aligning and
# searching for windows. xmonad makes work easier, by automating this.
.xmonad/xmonad.hs,.xmonad/xmonad.hs
.xmonad/xmobarrc,.xmonad/xmobarrc
```


```bash:dotmv
#!/bin/sh
#
set -e

usage() {
  echo "Move the file to dotfiles dir, make the link, and edit setup config"
  echo "Usage: $0 [OPTION] SOURCE DEST(must be set into dotfiles)"
  echo ""
  echo "OPTION:"
  echo "  -m: Add your message for setup configuration file."
  echo "  -h: Show this message."
  exit 1
}

# TODO: crop $HOME string automatically. (Now it is done manually, so you
# should replace the below command by your name.)
home_pattern='s/\/home\/shotaro\///'
dotfile_pattern='s/\/home\/shotaro\/\.dotfiles\///'

# set dotfiles config file
dotfiles_config_file="$HOME/.dotfiles/setup_config_link"

# default message
message=""

trap 'usage' 1 2 3 15

# option handling
while getopts m:h OPT
do
  case $OPT in
    "m" ) message="${OPTARG}";;
    "h" ) usage ;;
  esac
done

# two arguments must be taken
shift $((OPTIND-1))
if [ ! $# = 2 ];then
  usage
  exit 1
fi

name="$(basename $1)"
orig="$(cd $(dirname $1); pwd)"/"${name}"
dot="$2"

original="$(echo "$orig" | sed -e "${home_pattern}")"
dotfile="$(echo "$dot" | sed -e "${dotfile_pattern}")"

# mv from original path to dotfiles dir
dotdir="$(dirname "${dot}")"
if [ ! -e "${dotdir}" ]; then
  mkdir -p "${dotdir}"
fi
mv -i "$orig" "$dot"

# link to orig path from dotfiles
ln -siv "$dot" "$orig"

# add the cofigration to the config file.
if [ ! "${message}" = "" ]; then
  echo "# ${message}" >> "${dotfiles_config_file}"
fi
echo "${dotfile},${original}" >> "${dotfiles_config_file}"

# Open config file
vim + "${dotfiles_config_file}"
```


## まとめ

簡単ながらも応用の効きそうなスクリプトが書けた。自分のdotfiles見なおしてみたら使ってないのとか結構ごちゃごちゃしてたから、これを機に整理してみようと思います。