bashでディレクトリ移動を便利にする

bashでディレクトリ間の移動を便利にする、拡張cdコマンドを紹介します。
これを使うと、わざわざ`pushd`コマンドを使わずとも、自動的にディレクトリの履歴を保存してくれるようになります。

## 設定方法

最近の Cygwin/MSYS2 の `.bashrc` スケルトンファイル (`/etc/skel/.bashrc`) には、サンプルとして、この拡張cdコマンドが記載されています。
コメントには以下のように書かれています。

```bash:/etc/skel/.bashrc
# Some example functions:

    ...

# b) function cd_func
# This function defines a 'cd' replacement function capable of keeping, 
# displaying and accessing history of visited directories, up to 10 entries.
# To use it, uncomment it, source this file and try 'cd --'.
# acd_func 1.0.5, 10-nov-2004
# Petar Marinov, http:/geocities.com/h2428, this is public domain
```

これによると、パブリックドメインで公開されていたコードのようですが、geocitiesの閉鎖に伴い、元のサイトは見られなくなってしまっています。

`cd_func` 関数の本体は以下の通りです。これを `.bashrc` にコピー&ペーストすれば設定完了です。

```bash
cd_func ()
{
  local x2 the_new_dir adir index
  local -i cnt

  if [[ $1 ==  "--" ]]; then
    dirs -v
    return 0
  fi

  the_new_dir=$1
  [[ -z $1 ]] && the_new_dir=$HOME

  if [[ ${the_new_dir:0:1} == '-' ]]; then
    #
    # Extract dir N from dirs
    index=${the_new_dir:1}
    [[ -z $index ]] && index=1
    adir=$(dirs +$index)
    [[ -z $adir ]] && return 1
    the_new_dir=$adir
  fi

  #
  # '~' has to be substituted by ${HOME}
  [[ ${the_new_dir:0:1} == '~' ]] && the_new_dir="${HOME}${the_new_dir:1}"

  #
  # Now change to the new dir and add to the top of the stack
  pushd "${the_new_dir}" > /dev/null
  [[ $? -ne 0 ]] && return 1
  the_new_dir=$(pwd)

  #
  # Trim down everything beyond 11th entry
  popd -n +11 2>/dev/null 1>/dev/null

  #
  # Remove any other occurence of this dir, skipping the top of the stack
  for ((cnt=1; cnt <= 10; cnt++)); do
    x2=$(dirs +${cnt} 2>/dev/null)
    [[ $? -ne 0 ]] && return 0
    [[ ${x2:0:1} == '~' ]] && x2="${HOME}${x2:1}"
    if [[ "${x2}" == "${the_new_dir}" ]]; then
      popd -n +$cnt 2>/dev/null 1>/dev/null
      cnt=cnt-1
    fi
  done

  return 0
}

alias cd=cd_func
```

## 使い方

`cd` コマンドでディレクトリを移動すると、自動的に最新10個までのディレクトリの履歴が保存されます。

`cd --` で、保存されている履歴が表示されます。
例:

```shell-session
$ cd --
 0  ~
 1  /etc
 2  /usr/bin
```

<code>cd -<i>n</i></code> で、履歴の *n* 番目のディレクトリに移動することが出来ます。
この例の場合、`cd -2`で`/usr/bin`に移動します。

直前のディレクトリに戻るには、`cd -1` だけでなく、従来通り `cd -` も使えます。

## autocdの挙動を模倣

bashには `autocd` というオプションがあります。これを有効にすると、`cd` を指定せずにディレクトリ名を指定するだけでディレクトリを移動できるようになります。

```shell-session
user@host:~$ shopt -s autocd
user@host:~$ /etc
cd -- "/etc"
user@host:/etc$ 
```

しかし、このautocdオプションには上記のcd_funcが適用されず、`cd` 無しで移動したディレクトリは履歴に反映されません。

そこで以下の設定を `.bashrc` に追加することで、autocdオプションを使ったときと同様に `cd` を省略しつつ、cd_funcの履歴に登録することができるようになります。(autocdオプションは `shopt -u autocd` で無効化しておく必要があります。)

```bash
# Change PWD even without `cd`
function _err_handler {
  if [ $? -eq 126 ] && [ -d "$BASH_COMMAND" ]; then
    echo "cd_func \"$BASH_COMMAND\""
    cd_func "$BASH_COMMAND"
  fi
}
trap "_err_handler" ERR
```

例:
```shell-session
user@host:~$ /etc
-bash: /etc: Is a directory
cd_func "/etc"
user@host:/etc$ cd --
 0  /etc
 1  ~
```

(`-bash: /etc: Is a directory` というエラーが表示されてしまう問題がありますが、目をつむりましょう。)

## 参考

* [Mac - いかに素早く目的のディレクトリにcdするか、そのためのTips - Qiita](http://qiita.com/xtetsuji/items/31bc53e92d94b1602b5d "Mac - いかに素早く目的のディレクトリにcdするか、そのためのTips - Qiita")
* [Bash - cdによるディレクトリ移動を効率化 - Qiita](http://qiita.com/omega999/items/4346ae57c9ff8bb1d50d "Bash - cdによるディレクトリ移動を効率化 - Qiita")