前書き

自動化Script作成時に、Bash (Shell Script)ではなく、RubyやPython3を用いた方がScriptのメンテナンス負荷が低くなります。自動化Scriptに使用するプログラミング言語変更を目的として、各言語の実装を比較します。

本記事では、オプション解析する方法を比較します。比較では、実装例および実行例をそれぞれ示します。

Bashではなく、RubyやPython3を使った方が好ましい理由は、以下の記事に記載しています。この記事には、各プログラミング言語の様々な実装(ディレクトリ操作やファイル操作など)を比較した他記事へのリンクを一覧にまとめています。

各言語のVersion

  • Bash:GNU bash, バージョン 5.0.3(1)-release
  • Ruby:ruby 2.5.5p157 (2019-03-15 revision 67260)
  • Python:Python 3.7.3

比較:オプション解析する方法

自動化Scriptでは、他のコマンドと同様に、オプションに応じて処理を変更する実装にする機会が多いです。例えば、ログ出力先をユーザがオプションで指定したディレクトリに変更したり、ログレベルを柔軟に変更したりする場合などが、オプションが必要な例として挙げられます。

以下に、各言語の実装例および実行例を示します。

Bashの場合

Bashのオプション解析には、getopts(bashビルトインコマンド)を用いた方法、getoptコマンド(外部コマンド)を用いた方法の二通りがあります。

getoptsによるオプション解析 と異なり、getoptはロングオプションも使用出来るメリットがあります。 ただし、デメリットとして、getoptはBSD実装(Mac、OpenBSD等)とGNU実装(Debian系、RedHas系)で差異があります。

BSD実装の場合はロングオプションが使用できず、引数の後にオプションを指定できないデメリットがあります。今回の例では、Linux環境を前提としているのでgetoptを使用した方法を示します。getoptsを使用した方法は、番外として、本記事の最後に示します。

#!/bin/bash

# getoptコマンドを用いたオプション解析
# getopts(bashビルトインコマンド)と異なり、getoptはロングオプションも使用可能。
# ただし、getoptはBSD実装(Mac、OpenBSD等)/GNU実装(Debian系、RedHas系)で差異があり、
# BSD実装の場合はロングオプション使用不可、引数の後にオプション指定不可。

# -oオプション:ショートオプションとする英字を指定する。
#               今回の例では、"-h"、-m"をオプションとしている。
#               英字の後に":"を書いた場合は、その英字はオプション引数を必要とする。
# -lオプション:ロングオプションとする英文字列を指定する。
#               オプション引数が必要な場合、英文字列の後に":"を指定する。
# -nオプション:エラーメッセージに出力するスクリプト名
#               指定がない場合は、"getopt:〜"という形式でエラー出力される。
# --オプション:"--"の後にパラメータが続く必要がある。
# $@          :Shell Scriptに渡された全ての引数
OPT=$(getopt -o hm: -l help,message: -n $0 -- "$@")
if [ $? != 0 ] ; then
    exit 1
fi
eval set -- "$OPT"

while true
do
    # 引数を解析した後、必ずshiftで引数をずらす必要がある。
    case $1 in
        -h | --help)
            echo "helpメッセージのつもり"
            shift
            ;;
        -m | --message)
            echo "messageオプションの処理(オプション引数:$2)"
            shift 2
            ;;
        --)
            shift
            break
            ;;
        *)
            echo "不正なオプションです。" 1>&2
            exit 1
            ;;
    esac
done

Rubyの場合

#!/usr/bin/env ruby

require 'optparse'

option = {}
OptionParser.new do |opt|
  # opt.on()にオプションを指定する。
  # オプション引数なしの場合、第一引数はオプション名(例:-z)を指定する。
  # オプション引数ありの場合、第一引数はオプション名と変数名(例:-l var)を指定する。
  # 省略可能なオプション引数ありの場合、変数名を[]で囲む。
  # {}部分は、オプション指定時の処理。今回は、hashに変数を保持している。
  opt.on('-m', '--message MSG', '出力するメッセージを指定する') {|m| option[:message] = m}

  begin
    # parse!()は、実行時に登録していない引数を指定された場合や、
    # 必須のオプション引数がなかった場合に例外を出す。
    # 例外発生時は、helpメッセージを手動で表示する。
    opt.parse!(ARGV)
  rescue => exception
    puts(opt)
    exit 1
  end
end

puts("messageオプションの処理(オプション引数:" + option[:message] + ")")

Python3の場合

#!/usr/bin/env python3

import argparse

# argparseモジュールは、自動でhelpオプションを作成する。

# add_argument()は、一度に2種類の記法(例:"-v"、"--version")を登録できる。
# typeにはオプション引数の型、destにはオプション引数(値)の格納先となる変数名を書く。
# 引数helpには、オプションの意味合い(ヘルプ時に表示する文章)を記載する。
parser = argparse.ArgumentParser()
parser.add_argument("-m","--message", type=str, dest="msg",
                    help="出力するメッセージを指定する")
args = parser.parse_args()

if args.msg is not None:
    print("messageオプションの処理(オプション引数:%s)" % args.msg)

Bash、Ruby、Python3の実行例

Rubyは、ユーザが誤ったオプションの使い方をした場合に備えて、例外処理を作り込まないと「何が原因でエラーが発生したか」が分かりづらい印象を受けました。

$ ./bash.sh -h
helpメッセージのつもり
$ ./bash.sh --help
helpメッセージのつもり
$ ./bash.sh -m test_message
messageオプションの処理(オプション引数:test_message)
$ ./bash.sh -m
./bash.sh: オプションには引数が必要です -- 'm'
$ ./bash.sh --message test_message
messageオプションの処理(オプション引数:test_message)
$ ./bash.sh -a
./bash.sh: 無効なオプション -- 'a'

$ ./ruby.rb -h
Usage: ruby [options]
    -m, --message message            出力するメッセージを指定する
$ ./ruby.rb --help
Usage: ruby [options]
    -m, --message message            出力するメッセージを指定する
$ ./ruby.rb -m test_message
messageオプションの処理(オプション引数:test_message)
$ ./ruby.rb -m
Usage: ruby [options]
    -m, --message message            出力するメッセージを指定する
$ ./ruby.rb --message test_message
messageオプションの処理(オプション引数:test_message)
$ ./ruby.rb -a	
Usage: ruby [options]
    -m, --message message            出力するメッセージを指定する

$ ./python.py -h
usage: python.py [-h] [-m MSG]

optional arguments:
  -h, --help            show this help message and exit
  -m MSG, --message MSG
                        出力するメッセージ
$ ./python.py --help
usage: python.py [-h] [-m MSG]

optional arguments:
  -h, --help            show this help message and exit
  -m MSG, --message MSG
                        出力するメッセージ
$ ./python.py -m test_message
messageオプションの処理(オプション引数:test_message)
$ ./python.py -m
usage: python.py [-h] [-m MSG]
python.py: error: argument -m/--message: expected one argument
$ ./python.py --message test_message
messageオプションの処理(オプション引数:test_message)
$ ./python.py -a
usage: python.py [-h] [-m MSG]
python.py: error: unrecognized arguments: -a

[the_ad id=“598”]

番外:Bashでgetoptsを用いたオプション解析

実装

#!/bin/bash

# getoptsには、引数にオプションとする英字を渡す。
# そのオプション(例:"d")がオプション引数を取る場合は、英字の直後に":"を書く(例:"d:")。
# 以下の例では、"d"と"f"が引数を必要とするオプション。
while getopts d:f:h OPT
do
    case $OPT in
        d)  DIR_NAME=$OPTARG   # オプション引数は変数OPTARGに格納されている。
            ;;
        f)  FILE_NAME=$OPTARG
            ;;
        h)  echo "スクリプトの使い方はxxxです。"
            exit 0
            ;;
        *) echo "スクリプトの使い方はxxxです。"  # 指定していないオプションが来た場合
            exit 1
            ;;
    esac
done

echo "ディレクトリ名:${DIR_NAME}"
echo "ファイル名:${FILE_NAME}"

実行例

$ bash sample.sh -f file_name -d directory_name
ディレクトリ名:directory_name
ファイル名:file_name

$ bash sample.sh -f 
sample.sh: オプションには引数が必要です -- f
スクリプトの使い方はxxxです。

$ bash sample.sh -k
sample.sh: 不正なオプションです -- k
スクリプトの使い方はxxxです。