bashの各種コマンドを編集するシェルスクリプト


bashの各種のコマンドを、簡単に編集できるようにしたい。

環境はMac。

## コマンドの種類
bashで利用できるコマンドには、以下の5種類がある。

1. エイリアス
1. 関数
1. ファイル
1. 予約語
1. シェル組み込みコマンド

このうち、自分で作成できるのは1〜3である。これらを一括して編集できるのが理想。

## typeコマンドによるコマンドの判別
コマンドが1〜5のどれであるかは、typeコマンドによって判断できる。

```
# -tオプションでタイプのみを表示
$ type somealias # エイリアス
somealias is aliased to `echo "somealias"'
$ type -t somealias
alias

$ type somefunc # 関数
somefunc is a function
somefunc ()
{
    echo "somefunc"
}
$ type -t somefunc
function

$ type ls # ファイル
ls is /bin/ls
$ type ls # ハッシュされたファイル
ls is hashed (/bin/ls)
$ type -t ls
file
$ type -p ls # パスのみを出力。whichだとパスが通っていても出ない場合がある
/bin/ls

$ type for # 予約語
for is a shell keyword
$ type -t for
keyword

$ type type # 組み込みコマンド
type is a shell builtin
$ type -t type
builtin
```

ファイルをを何度か実行するとログイン中のシェルでハッシュされる。ハッシュされたものはファイルである。

### ファイルがバイナリであるか確認
ファイルがバイナリである場合、エディタで直接編集することはできない。よって、コマンドがバイナリファイルであった場合、除外する必要がある。

あるファイルがバイナリであるかどうかは、fileコマンドで判別する。

```bash
# -bオプションで属性のみを表示
$ file -b someprog # テキストファイル（編集可能）
Bourne-Again shell script text executable

$ file -b $(type -p ls) # 'text'を含まないものはバイナリ（編集不可）
Mach-O 64-bit executable x86_64
```

## 関数化
typeコマンドの結果を取得し、それに応じて処理するシェル関数を作成する。

このスクリプト自体は、ファイルではなく関数でなければならない。  
イリアスと関数の変更を反映するために`source`コマンドを実行するが、ファイルから実行すると、`source`が別のプロセスで実行されてしまい、カレントシェルに変更が反映されないため。

編集は、VimまたはMacVimで行う。

* エイリアスまたは関数の場合、編集終了を関知してsourceするのを容易にするため、Vimを使う。
* プログラムの場合、編集と実行を平行して行えるよう、MacVimを使う。
* 色つきで出力するのにGNU sedを使用。
* 機能を増やしすぎた。

```bash
# in ~/.bashrc
# export bash_alias=~/.bash/conf/alias-init.bash
# export bash_function=~/.bash/conf/function-init.bash
# export bindir=~/shellsctipts
# export PATH=$PATH:$bindir:./bin
# source "$bash_alias"
# source "$bash_function"

ec () { #edit shell commands
  local dflag=0
  local pflag=0
  local OPTIND_OLD="$OPTIND"
  local OPTIND=1
  usage () {
    echo "Usage: ec [-dph] <comand name>" 1>&2
    echo "-d: delete command." 1>&2
    echo "-p: print aliased to/function or 'what' is file, and copy code."  1>&2
    echo "-h: print help." 1>&2
  }
  while getopts dph OPT
  do
    case $OPT in
      d)
        dflag=1
        ;;
      p)
        pflag=1
        ;;
      h)
        usage
        return 0
        ;;
      ?)
        usage
        return 1
        ;;
    esac
  done 
  shift $((OPTIND - 1))
  OPTIND="$OPTIND_OLD"
  if [ -z "$1" ]; then
    echo "ERROR: input command name to edit" 1>&2
    return 1
  fi
  if ! type "$1" >&/dev/null; then
    echo "ERROR: Undifined command." 1>&2
    return 1
  fi
  case "$(type -t "$1")" in
    "function" )
      if [ "$pflag" = 1 ]; then
        type "$1" | sed -e 1d -e 's/;$//g' | tee >(pbcopy)
        return 0
      fi
      if [ "$dflag" = 0 ]; then
        vim "$bash_function" +"/^$1 ()"
      else
        echo -e "function deleted: \033[032;m$1\033[0m" 1>&2
        sed -i '' "/^$1 () {/,/^}/d" "$bash_function" 1>&2
      fi
      source ~/.bashrc
      ;;
    "alias" )
      if [ "$pflag" = 1 ]; then
        type "$1" | sed -e 's/.* to `//' -e "s/'$// | tee >(pbcopy)" 1>&2
        return 0
      fi
      if [ "$dflag" = 0 ]; then
        vim "$bash_alias" +"/^alias $1="
      else
        gsed -n "s/^alias \($1=.*\)/alias deleted: \x1b[33;m\1\x1b[0m/p" "$bash_alias" 1>&2
        gsed -i "/^alias $1=/d" "$bash_alias"
      fi
      source ~/.bashrc
      ;;
    "file")
      local cmdpath="$(type -p "$1")"
      if file -b "$cmdpath" | grep -q 'text'; then
        if [ "$pflag" = 1 ]; then
          what "$cmdpath"
          pbcopy < "$cmdpath"
          return 0
        fi
        if [ "$dflag" = 1 ]; then
          echo -n "rm: "; rm -v "$(readlink "$cmdpath")"
          echo -n "rm: "; rm -v "$cmdpath"
          return 0
        fi
        mvim "$cmdpath"
      else
        echo "Not text/script file" 1>&2
        return 1
      fi
      ;;
    "keyword"|"builtin")
      echo "$1 is shell keyword or builtin." 1>&2
      return 1
      ;;
    * )
      echo "Unexpected error." 1>&2
      type "$1"
      return 1
      ;;
  esac
}
```

## 参考
* [Bashでコマンドの存在チェックはwhichよりhashの方が良いかも→いやtypeが最強 - Qiita](http://qiita.com/kawaz/items/1b61ee2dd4d1acc7cc94)
* [hashコマンドについて - FreeBSD初心者運用日記 - freebsdグループ](http://freebsd.g.hatena.ne.jp/py4s-tnk/20090202/1233585137)
* [Emacs - 設定ファイルを複数ファイルに分割して管理する構文のまとめ - Qiita](http://qiita.com/skkzsh/items/20af9affd5cc1e9678f8)

## 補足
* [bashの関数を削除するシェルスクリプトをVimで無理やり書いた→普通に書けた - Qiita](http://qiita.com/catfist/items/b337b0c18d39f2bc71ca)