Help us understand the problem. What is going on with this article?

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

More than 1 year has passed since last update.

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

環境はMac。

コマンドの種類

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

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

このうち、自分で作成できるのは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コマンドで判別する。

# -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を使用。
  • 機能を増やしすぎた。
# 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
}

参考

補足

Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away