カテゴリー
【シェルスクリプトツール作成の基本】引数指定で動作するシェルスクリプトを自作する
※ 当ページには【広告/PR】を含む場合があります。
2021/03/28
シェルコマンドを予めファイルに記述しておくことで、高度なシェルスクリプトとして作成しておけば、まさに一生モノの財産となることでしょう。
今回は自作スクリプトの基本形となるスクリプトのテンプレートに関して解説します。
はじめに
当サイトではオフィス業務のComputer-Aidedなハイブリッドな方法を模索し、より効率的なExcel業務を実現したい多忙なオフィスワーカー向けの主にAwkとSedを使うシェル講座です。
シェルスクリプトはどこでもどんなOSでも基本的に使えて、しかも一度使い方を覚えると、Excelと組み合わせて最高に効率の良いオフィスワークツールが作れることでしょう。

引数を順番に呼び出すスクリプト
まずはあまり実用性が無いのですがもっとも簡単なスクリプト作成例として、コマンド引数を順番で呼び出す方法から見ていきます。
#!/bin/bash
noarg_err() {
echo "ERROR: must provide both of arg1 and arg2!" 1>&2
exit 1
}
noinputfile_err() {
echo "ERROR: not allowed an input file to be empty!" 1>&2
exit 1
}
if [ -z "$1" ] || [ -z "$2" ]; then
noarg_err
fi
if [ -z "$3" ]; then
noinputfile_err
fi
#👇実行したプログラム名(相対フォルダパス付き)
echo "PROGRAM: $0"
#👇実行したプログラム名(プログラム名のみ)
echo "PROGRAM: $(basename $0)"
#👇引数部分の表示
echo "$@"
echo "$*"
#👇引数の数(スペース文字切り)
echo "$#"
#👇引数の個別の読み出し
echo "FILE: $3, ARG1: $1, ARG2: $2"
このスクリプトは以下のように利用します。
$ chmod +x my_simple_script.sh
$ ./my_simple_script.sh piyo fuga hoge.csv
./1.sh piyo fuga hoge.csv
PROGRAM: ./my_simple_script.sh
PROGRAM: my_simple_script.sh
piyo fuga hoge.csv
piyo fuga hoge.csv
3
FILE: hoge.csv, ARG1: piyo, ARG2: fuga
このシェルスクリプトの挙動を理解する上で、ダラー文字($)で制御される変数は重要になります。
もちろんこの単純なスクリプトを作成する方法でも問題なく機能するわけで、気にならないならそのまま使うと良いのですが、コマンドの引数を
$1 $2 $3 ...
そこで以降ではさらなるシェルスクリプトの使い心地を求めて、オプションで引数を渡す方法を検討してみます。
getoptsを使って引数をオプションにしたスクリプト
引数をオプションとして指定するもっとも知られた方法としては、
getopts
またgetoptsは第一引数に
使用したいオプション文字列
コマンドに指定されたオプション文字
-a hoge
a:
OPTARG
この変数において、クエッションマーク(?)が返ってきたときは無効なオプションが指定されたことを示しており、breakにてcase文を抜けさせるか、help関数を作成しておいて利用方法を示しながら一旦プロセスをexitさせる処理が定石です。
とりあえず説明文だけでは理解しづらいので、以下のような具体例を示します。
#!/bin/bash
usage_exit() {
echo "USAGE: $(basename $0) [-1 arg1] [-2 arg2] [-h help] [input_file]" 1>&2
exit 1
}
noarg_err() {
echo "ERROR: must provide both of arg1 and arg2!" 1>&2
exit 1
}
noinputfile_err() {
echo "ERROR: not allowed an input file to be empty!" 1>&2
exit 1
}
while getopts 1:2:h OPT; do
case $OPT in
1 ) ARG1="$OPTARG"
;;
2 ) ARG2="$OPTARG"
;;
h ) usage_exit
;;
\? ) usage_exit
;;
esac
done
#👇①不要となったオプション部分をshiftコマンドで切り捨て
shift $((OPTIND - 1))
if [ -z "$ARG1" ] || [ -z "$ARG2" ]; then
noarg_err
fi
if [ -z "$1" ]; then
noinputfile_err
fi
echo "FILE: $1, ARG1: ${ARG1}, ARG2: ${ARG2}"
またオプションありの引数とオプション無しの引数を混合して使う場合には、上のスクリプトでの①の箇所で、オプションあり引数の数(
$OPTIND
$1...
これを実行してみます。
$ chmod +x my_interactive_script.sh
$ ./my_interactive_script.sh -h
USAGE: my_interactive_script.sh [-1 arg1] [-2 arg2] [-h help] [input_file]
$./my_interactive_script.sh -1 piyo -2 fuga hoge.csv
FILE: hoge.csv, ARG1: piyo, ARG2: fuga
getoptsコマンドを使用したスクリプトでは引数を
-a hoge -b piyo -c
ですが、getoptsコマンドには以下の項目に示す弱点があり、場合によっては完璧とは言えません。
弱点① ~ ロングオプションが作れない
たとえば、シェルコマンドにおいてプログラムのバージョンを表示させたい場合に、
--version
--version
-v
-V
そして残念ながらgetoptsコマンドでは、ショートオプション形式でしか扱えません。
なので、
--verbose
--version
-v
弱点② ~ オプションを指定する順序に制限がある
たとえばgetoptsでオプションありの引数とオプション無しの引数を混合して使う場合、
$ ./hoge.sh -1 piyo -2 fuga -3 mofu awesome1.csv awesome2.csv
として先にオプション有りの引数を指定しておいて、後付でオプション無し引数を取るような作法でコマンドを書くことになります。
当然、以下のコマンドはNGです。
$ ./hoge.sh awesome1.csv -1 piyo -2 fuga awesome2.csv -3 mofu
つまりはコマンドオプションを柔軟に使用できないという制限があるのを理解して使わないといけません。
引数の解析を自前でカスタマイズする方法
究極的に、自由度の高い引数の指定ルールを組み込んだ自作スクリプトを作成するには、引数部分を
$@
#!/bin/bash
PROGNAME="$(basename $0)"
usage() {
cat << EOS >&2
Usage: ${PROGNAME} [--hoge] [-1, --fuga [VALUE]] [-2, --piyo VALUE]
A sample script of parsing on bash.
Options:
--hoge A single option.
--fuga A option with optional value.
--piyo A option with required value.
-h, --help Show usage.
EOS
exit 1
}
#👇オプションのパース結果の格納
PARAM=()
for opt in "$@"; do
case "${opt}" in
#👇ヘルプ表示は単独で利用
'-h' | '--help' )
usage
;;
#👇オプションに指定の値が無い場合
'--hoge' )
HOGE=true; shift
;;
#👇オプションに指定する値を任意にする場合
'-1' | '--fuga' )
FUGA=true; shift
if [[ -n "$1" ]] && [[ ! "$1" =~ ^-+ ]]; then
FUGA_VALUE="$1"; shift
fi
;;
#👇オプションに指定する値を必須にする場合
'-2' | '--piyo' )
if [[ -z "$2" ]] || [[ "$2" =~ ^-+ ]]; then
echo "${PROGNAME}: Option needs an argument -- $( echo $1 | sed 's/^-*//' )" 1>&2
exit 1
fi
PIYO=true; PIYO_VALUE="$2"; shift 2
;;
#👇オプションの終端を検出(このオプション以降に指定したオプションは値として解釈される)
'--' | '-' )
shift
PARAM+=( "$@" )
break
;;
#👇上記で定義した'-<文字>'以外のオプションは許容しない
-* )
echo "${PROGNAME}: Invalid option detected -- '$( echo $1 | sed 's/^-*//' )'" 1>&2
exit 1
;;
#👇'-'無しのオプションは値としてキャプチャされる
* )
if [[ -n "$1" ]] && [[ ! "$1" =~ ^-+ ]]; then
PARAM+=( "$1" ); shift
fi
;;
esac
done
#👇オプション無しの値を使う場合の処理
INPUT_FILE="${PARAM}"; PARAM=("${PARAM[@]:1}")
[[ -z "${INPUT_FILE}" ]] && usage
#👇無効なオプションがある場合にusageで抜ける
if [[ -n "${PARAM[@]}" ]]; then
usage
fi
#👇結果を出力
cat << EOS
HOGE > ${HOGE:-false} : FUGA > ${FUGA:-false} : PIYO > ${PIYO:-false}
FUGA_VALUE > ${FUGA_VALUE} : PIYO_VALUE > ${PIYO_VALUE}
INPUT_FILE < ${INPUT_FILE}
EOS
ではこれを
my_custom_script.sh
まずはヘルプ表示で
-h
--help
$ chmod +x my_custom_script.sh
$ ./my_interactive_script.sh -h
Usage: my_custom_script.sh [--hoge] [-1, --fuga [VALUE]] [-2, --piyo VALUE]
A sample script of parsing on bash.
Options:
--hoge A single option.
--fuga A option with optional value.
--piyo A option with required value.
-h, --help Show usage.
$ ./my_interactive_script.sh --help
#先ほどと同じ出力
では実際にスクリプトを利用するケースをいくつか考えて試してみましょう。
$ ./my_custom_script.sh --hoge --fuga abc --piyo 123 awesome.csv
HOGE > true : FUGA > true : PIYO > true
FUGA_VALUE > abc : PIYO_VALUE > 123
INPUT_FILE < awesome.csv
$ ./my_custom_script.sh awesome.csv --hoge --fuga abc --piyo 123
HOGE > true : FUGA > true : PIYO > true
FUGA_VALUE > abc : PIYO_VALUE > 123
INPUT_FILE < awesome.csv
$ ./my_custom_script.sh awesome.csv -1 abc --piyo 123
HOGE > false : FUGA > true : PIYO > true
FUGA_VALUE > abc : PIYO_VALUE > 123
INPUT_FILE < awesome.csv
$ ./my_custom_script.sh --hoge awesome.csv -2 123
HOGE > true : FUGA > false : PIYO > true
FUGA_VALUE > : PIYO_VALUE > 123
INPUT_FILE < awesome.csv
$ ./my_custom_script.sh awesome.csv --piyo
my_custom_script.sh: Option needs an argument -- piyo
ここから分かるように、この方法でスクリプトを作成すると、ロングオプションも利用でき、オプションの順序も柔軟な位置で呼び出すことが可能です。
まとめ
以上、自作のシェルスクリプトに引数を与える方法を三通り紹介していきました。
項目が進むにつれて引数を与える自由度が大きくなっていきますが、今度はシェルコマンドの用法や構文にある程度の理解と知識が無いと解読も難しくなっていきます。
ここらへんはご自分の学習深度に併せてどのやり方でシェルスクリプトを組みのかを選択すると良いと思います。
参考サイト
記事を書いた人
ナンデモ系エンジニア
主にAngularでフロントエンド開発することが多いです。 開発環境はLinuxメインで進めているので、シェルコマンドも多用しております。 コツコツとプログラミングするのが好きな人間です。
カテゴリー