0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

《シェルスクリプト》意外と難しい!オプションをそのまま子コマンドに渡す方法

0
Last updated at Posted at 2026-03-08

普通に書くと後で問題が発覚するオプション指定

シェルスクリプトに指定されたオプションを、そのままシェルスクリプトから呼び出すコマンドに渡すときは、次のようなコードを想像するでしょう。

example.sh のコード:

if [ "$1" == "--message" ]; then
    Options_Message="$2"
fi

echo  example.sh  --message "${Options_Message}"  #// オプションをそのまま渡します

最初のうちはこのコードで期待通り動きます。 しかし、このコードには問題があります。 それは、オプションが指定されていないときでも、オプションを渡してしまっている問題です

実行コマンド:

$ code example.sh  #// Visual Studio Code の場合
$ chmod +x example.sh

$ ./example.sh --message "Hello, world."
example.sh --message Hello, world.

$ ./example.sh
example.sh --message  #// オプションを渡してしまっている

シェルスクリプト example.sh に渡された --message オプションをそのまま echo コマンドに渡しています。しかし、example.sh に --message オプションが渡されていなくても、echo コマンドに --message オプションを渡してしまっています

渡している値は空文字列なので、実質 --message オプションを指定してないと解釈することもできますが、--message オプションを指定しないときのデフォルトの値がある場合、echo コマンドが受け取る値は、example.sh が受け取った値(デフォルト値)と異なる値(空文字列)になってしまいます。 他にも問題がありますが、それらの問題を全て無くすには工夫が必要です。

本記事では、シェルスクリプトに指定されたオプションを、そのままコマンドに渡すには、どう書いたらいいかを説明します。また、簡単に対処できる関数も提供します。 値を取らないフラグだけのオプションにも対応します。

ダウンロード

本書で紹介しているスクリプトのファイルは、GitHub からダウンロードできます。Linux のシェルを開くか、Windows の Git bash を開いて、以下のコマンドを実行してください。

$ cd  ${HOME}
$ git clone https://github.com/Takakiriy/shell-script-tutorial

オプション解析部分

オプションを解析する部分のコードは、こちらの記事に掲載しています。

空白文字を含む値への対応

空白文字を含む値が指定される可能性があることに対応するためには、オプションの値が入った変数を参照するときに、ダブルクォーテーションで囲む必要があります。 たとえば、"${Options_Message}" は良いですが、${Options_Message} はダメです。 なぜなら、空白文字が値の終わりであると解釈されて、空白より後の値がコマンドの次の引数になってしまうからです。

実行コマンド:

(zsh では違う動きをします)

$ Options_Message="Hello, world."

$ ./echo-q  --message "${Options_Message}"  #// OK
'./echo-q'  '--message'  'Hello, world.'

$ ./echo-q  --message ${Options_Message}  #// NG
'./echo-q'  '--message'  'Hello,'  'world.'  #// Hello, と world. が別れてしまいます

変数を展開して書くとこうなります。

$ ./echo-q  --message "Hello, world."  #// OK
'./echo-q'    '--message'  'Hello, world.'

$ ./echo-q  --message Hello, world.  #// NG
'./echo-q'  '--message'  'Hello,'  'world.'  #// Hello, と world. が別れてしまいます

オプションを渡さないことへの対応

コマンドに引数を渡さないようにするには、オプションの値が入った変数を参照するときに、ダブルクォーテーションで囲まないように書く必要があります。 たとえば、${Options_Message} は良いですが、"${Options_Message}" はダメです。 なぜなら、もし、囲むと、オプションが指定されなかったときに、空文字列の引数を渡すことになってしまうからです。

実行コマンド:

(zsh では違う動きをします)

$ Options_Message=""
$ Options_Target="main"
$ TargetOption="--target ${Options_Target}"

$ ./echo-q  ${Options_Message}  ${TargetOption}  #// OK
'./echo-q'  '--target'  'main'

$ ./echo-q  "${Options_Message}"  "${TargetOption}"  #// NG
'./echo-q'  ''  '--target main'  #// 空文字列を渡してしまいます

変数を展開して書くとこうなります。

$ ./echo-q    --target main  #// OK
'./echo-q'  '--target'  'main'

$ ./echo-q  ""  "--target main"  #// NG
'./echo-q'  ''  '--target main'  #// 空文字列を渡してしまいます

矛盾する両方の要求に対応する

以上のように、ダブルクォーテーションで囲む必要性と、ダブルクォーテーションで囲まない必要性が発生してしまいました。 どちらかの必要性を諦めなければならないのでしょうか。

別の解決方法があります。 それは、配列を使う方法です。 呼び出すコマンドに渡すときは配列に追加して、呼び出すコマンドに渡さないときは配列に追加しないようにすればいいのです。

example.sh のコード:

if [ "$1" == "--message" ]; then
    Options_Message="$2"
    shift 2
fi
if [ "$1" == "--target" ]; then
    Options_Target="$2"
    shift 2
fi

options=()
if [ "${Options_Message}" != "" ]; then
    options+=("--message" "${Options_Message}")
fi
if [ "${Options_Target}" != "" ]; then
    options+=("--target" "${Options_Target}")
fi

./echo-q  "${options[@]}"

シェルスクリプトに渡された全ての引数を、スクリプトから呼び出すコマンドに渡すときに "$@" と書きますが、ユーザーが定義した配列でも同様のことができます。

実行コマンド:

$ code example.sh  #// Visual Studio Code の場合
$ chmod +x example.sh

$ ./example.sh --message "Hello, world."
'./echo-q'  '--message'  'Hello, world.'

$ ./example.sh --target main
'./echo-q'  '--target'  'main'

$ ./example.sh --message "Hello, world." --target main
'./echo-q'  '--message'  'Hello, world.'  '--target'  'main'

$ ./example.sh
'./echo-q'

AddOptionToArray 関数を使う(フラグにも対応)

options 配列を作る処理を書きやすく読みやすくする AddOptionToArray 関数を紹介します。 オプションの値を取らない フラグ タイプ のオプションにも対応しています。

動作する完全なコードは、本記事の最初で示した場所からダウンロードできます。

書き方のサンプル

function  Main() {
    local  options=()
    AddOptionToArray  options  "--message"  "${Options_Message}"
    AddOptionToArray  options  "--target"  "${Options_Target}"  --default "local"
    AddOptionToArray  options  "--watch"  "${Options_Watch}"  --flag

    ./echo-q  "${options[@]}"
}

実行コマンド:

$ cd  ~/shell-script-tutorial/6-option

$ ./3-delegate.sh --message "Hello, world."
'./echo-q'  '--message'  'Hello, world.'

$ ./3-delegate.sh --target main
'./echo-q'  '--target'  'main'

$ ./3-delegate.sh --message "Hello, world." --target main
'./echo-q'  '--message'  'Hello, world.'  '--target'  'main'

$ ./3-delegate.sh --watch
'./echo-q'  '--watch' 

$ ./3-delegate.sh
'./echo-q'

デフォルト値

編集しているスクリプトに指定できるオプション(例:--target)を指定しなかったときに、そのオプションの値を格納する変数(例:Options_Target)に格納する値(デフォルト値、例:local)は、以下のように設定します。

AddOptionToArray 関数に渡す --default オプション:

    AddOptionToArray  options  "--target"  "${Options_Target}"  --default "local"

変数が定義されていないときに代入する値:

if ! [[ -v Options_Target ]]; then  Options_Target="local"  ;fi

変数が定義されていないときに代入する値については、本記事の最初に示したオプション解析をするコードを紹介する記事に詳しく書いてあります。

常に指定

呼び出すコマンドに常にオプションを指定する場合、次のように書きます。 常に指定することで、呼び出すコマンドのデフォルト値の変更に影響されなくなります。

    AddOptionToArray  options  "--target"  "${Options_Target}"  --always

以下のように AddOptionToArray 関数を呼び出さなくても動きますが、呼び出した方が他のオプションを指定するコードと統一感が出て綺麗に見えます。

    options+=("--target"  "${Options_Target}")

フラグ

オプションの値を取らない フラグ タイプ のオプション(例:--watch)の場合、AddOptionToArray 関数に --flag オプションを指定します。

    AddOptionToArray  options  "--watch"  "${Options_Watch}"  --flag

変数が定義されていないときに代入する値は false です。

if ! [[ -v Options_Watch ]]; then  Options_Watch="false"  ;fi

AddOptionToArray 関数の定義

function  AddOptionToArray() {
    #// Example:
    #//     local  options=()
    #//     AddOptionToArray  options  "--test"  "${Options_Test}"
    #//     AddOptionToArray  options  "--message"  "${Options_Message}"  --default "no message"
    #//     AddOptionToArray  options  "--search-path"  "${Options_SearchPath}"  --always
    #//     AddOptionToArray  options  "--verbose"  "${Options_Verbose}"  --flag
    #//     sub_script.sh  "${options[@]}"
    local -n  arrayRef=$1  #// nameref (Bash 4.3+)
    local  optionName="$2"
    local  value="$3"
    local  thisOption="${4-""}"  #// "", "--flag" or "--default".  "${1-""}" means that "$1" default is "".
    local  default="${5-""}"  #// "${1-""}" means that "$1" default is "".
    if [ "${thisOption}" != "--default" ]; then
        default=""
    fi

    if [ "${thisOption}" == "--flag" ]; then
        if [ "${value}" != "" ] && [ "${value}" != "false" ] && [ "${value}" != "no" ]; then
            arrayRef+=("${optionName}")
        #// else no add
        fi
    elif [ "${thisOption}" == "--always" ]; then
        arrayRef+=("${optionName}" "${value}")
    else
        if [ "${value}" != "${default}" ]; then
            arrayRef+=("${optionName}" "${value}")
        #// else no add
        fi
    fi
}

関数の引数を名前参照する(Call by reference)

AddOptionToArray 関数は、配列に追加する処理をするため、引数を入出力することになります。 それを実現するためには、引数に変数の値 ${options} を渡すのではなく、配列変数の名前 options を渡します。 また、関数の内部では、-n オプションを使って名前参照します。

ただし、bash 4.3以上が必要なので、mac にプリインストールされている bash では使えません。

    local -n  arrayRef=$1  #// nameref (Bash 4.3+)

名前参照を使うと、渡した変数の別名を定義することになります。 別名というと変な感じがしますが、bash では呼び出し元の関数で参照している変数を、呼び出し先の関数の中でも参照できるので(できてしまうので)、それに対する別名を定義するので別名なのです。

ただし、呼び出し元に指定した名前が、名前参照を使う変数名と同じ場合、circular name reference エラーになります。 そのエラーにならないよう、名前参照を使う変数名に限り、末尾に Ref をつけるというルールを決めると良いです。

function  Main() {
    local  arrayRef=()  #// ルールに違反した変数名
    AddOptionToArray  arrayRef  "--message"  "${Options_Message}"
}

function  AddOptionToArray() {
    local -n  arrayRef=$1  #// circular name reference エラー

呼び出すことが多いコマンドのオプションはグローバル変数にする

他の シェルスクリプト ファイル を呼び出すコードを書くことが多いときは、オプションの配列を毎回作るのではなく、グローバル変数でオプションの配列を作っておくと便利です。

function  Main() {
    sub-script  "${SubScriptOptions[@]}"
}

function  Sub1() {
    sub-script  "${SubScriptOptions[@]}"
}

function  Sub2() {
    sub-script  "${SubScriptOptions[@]}"
}

function  ModifyGlobalVariables() {
    SubScriptOptions=()  #// local が書いてないのでグローバル変数です
    AddOptionToArray  SubScriptOptions  "--message"  "${Options_Message}"
    AddOptionToArray  SubScriptOptions  "--target"  "${Options_Target}"  --default "local"
}
0
0
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?