2017-05-03

bashでCLIを作ってみる

シェルスクリプトでオプション解析もごりごりやるようなCLIツールの作り方の備忘録。シェルスクリプトだけでCLIツールを作るメリットとしては可搬性と、パイプや標準入出力を用いた他のコマンドとの連携がやりやすいことです。

作り方

CLI作るときはざっくり、以下の機能が必要になると思います。 以下、テンプレを書いてみました。

#!/bin/bash

# u: error occured if undefined variable
# e: exit if error occured
set -ue

readonly PROCNAME=${0##*/}
function log() {
  echo -e "$(date '+%Y-%m-%dT%H:%M:%S') ${PROCNAME} (${BASH_LINENO[0]}:${FUNCNAME[1]}) $@"
}

function initialize() {
  ...
}

function parse_args() {
  SUBCOMMAND=$1
  case "$SUBCOMMAND" in
    "members" )
      shift
      parse_options $@
      ;;
    "songs" )
      shift
      parse_options $@
      ;;
  esac
}

function parse_options() {
  for OPT in "$@"
  do
    case "$OPT" in
      "--name" )
        check_argument $1 $2
        member_name=$2
        shift
        ;;
      "--debug" )
        debug=1
        ;;
      "--" | "-" )
        shift
        param+=( "$@" )
        break
        ;;
      -* )
        echo "$PROGRAM: illegal option -- '$(echo $1 | sed 's/^-*//')'" 1>&2
        exit 1
        ;;
      * )
        if [[ ! -z "$1" ]] && [[ ! "$1" =~ ^-+ ]]; then
          param+=( "$1" )
                    shift
        fi
        continue
        ;;
    esac
    shift
  done
}

function check_argument() {
  if [[ -z "$2" ]] || [[ "$2" =~ ^-+ ]]; then
    echo "$PROGRAM: option requires an argument -- $1" 1>&2
    exit 1
  fi
}

function run() {
  case "$1" in
    "help" )
      cat <<HELP
...
HELP
      exit 0
      ;;
    "songs" )
      cat <<SONGS
...
SONGS
      ;;
  esac
}

initialize $@
parse_args $@
run

set -uは、初期化していない変数を利用しようとしてクリティカルな障害が発生するのを防ぐためにセットします。例えば以下のコマンドにおいて、set -uを指定していない、かつHOGE_DIRECTORY変数が定義されていない場合、 rm -rf / が発動します。

$ rm -rf $HOGE_DIRECTORY/

set -uをつけると未定義の変数を利用しようとすると怒られます。

./hoge.sh:6: HOGE_DIRECTORY: parameter not set

set -eはエラー発生時に即座にスクリプトの実行を中止する設定で、予期せぬエラーで後続の処理に影響を与えないようにするためにセットします。

上記では記載していませんがデバッグ時にはset -xが有効です。この設定によって、変数展開をした実際に実行されたコマンドや変数の代入結果を参照することができます。

オプション解析はforループで引数を一つずつ解釈していきます。オプション引数が不要なタイプの場合はshiftが1回実行され、オプション引数が必要なタイプの場合はshiftが2回実行されます。shiftによって引数を適切に処理することができています。

また、オプションではない引数がループ内で解析されたとき、次の引数もオプションではない場合は、通常の引数としてparam変数に保持しています。

インストール方法

ローカルにインストールする方法はmvで移動するか

$ mv {filepath} /usr/local/bin

lnでシンボリックリンクを貼ればOK。

$ ln -s {filepath} /usr/local/bin/{command}

配布する場合はシェルスクリプトそのものをインストールすれば良いので、curlやwgetでいけます。

$ curl -sL {url} -o /usr/local/bin/{command}

補完関数を含めたい等があればtar.gzなどで固めてファイルストレージにアップロードしておき、解凍&インストールするようなシェルを書いて

$ curl -sL {url} | sh

のパターンでも良いですし、補完関数だけ別にしてcurlでインストールする方式でも良いです。

$ curl -sL {zsh completion url} -o /usr/local/share/zsh/site-functions/_{command}

参考URL

このエントリーをはてなブックマークに追加