設定されていない変数を使用すると何か問題がありますか?

設定されていない変数を使用すると何か問題がありますか?

次のようなコードがあるとします。

# Check if the color prompt is enabled and supported on this system
if [ -n "$force_color_prompt" ] && [ -x /usr/bin/tput ] && tput setaf 1 >&/dev/null; then
    GREEN="\033[1;32m"
    DIM="\033[2m"
    RESET="\033[00m"
fi

echo -e "Oh, ${GREEN}green${RESET} world, ${DIM}don't desert me now...${RESET}"

カラー サポートが有効になっている場合は、きれいな色の線が出力されます。カラー サポートが有効になっていない場合、 などの値は${GREEN}設定されず、テキストは通常​​どおり白地に黒の背景で出力されます。

このコードは、設定されていない変数は単に空の文字列として評価されるという事実に依存しています(私のテストではそうでした)。これは、一部のシステムでバグや問題を引き起こすでしょうか、それとも存在しない変数はすべていつも空の文字列に評価されますか? このメカニズムに頼らないほうがよい理由はありますか?

答え1

存在しない変数は、$FOOまたは (同等に)として展開されると常に空の文字列として評価されます${FOO}。特定のケースを除いて、これに依存しても問題はありません。

set -uその変数を使用する前に誰かが現在のシェルで呼び出した場合、この設定が有効になっています。

              -u パラメータ実行時に未設定の変数をエラーとして扱う
                      拡張が未設定の
                      変数が一致しない場合はシェルはエラーメッセージを出力し、一致しない場合は
                      対話型で、ゼロ以外のステータスで終了します。

つまり、他の誰かが制御するスクリプトにソースとして組み込まれるように設計された関数を作成する場合、未設定の変数の使用には細心の注意を払う必要があるかもしれません。そうしないと、set -u関数を呼び出す前に未設定の変数が使用された場合、初めて未設定の変数を展開しようとしたときに、スクリプトがエラー メッセージで終了してしまいます。

独自のスクリプトを書いている場合は、未設定の変数が空の文字列に展開されることを前提にしても問題ありません。

編集- また、ちょっと考えたのですが、terminfo カラー機能が端末で使用できるかどうかによって全体を条件付けているので、vt100 値をハードコーディングするのではなく、実際に terminfo を使用してシーケンスを生成してみてはいかがでしょうか。次のようになります。

if [ -n "$force_color_prompt" ] && type tput &>/dev/null; then
    GREEN="$(tput setaf 2)$(tput bold)"
    DIM="$(tput dim)"
    RESET="$(tput sgr0)"
fi

これにより、他の端末間での移植性がいくらか向上する可能性があります (ただし、確かに、示したコードを使用しない端末の数は少なく、減少しています)。また、terminfo 定義の正確さによっては、一部の機能が一部のプラットフォームに存在しない可能性があるため、移植性がいくらか失われる可能性もあります。結果は人によって異なります。

答え2

POSIX互換シェルスクリプト言語の最もユニークな機能の1つはパラメータ拡張変数値に通常関連付けられていないタスクを実行するために、さまざまな方法で使用できます。シェルでは、変数は単なる値以上のものになる可能性があり、実行可能な項目になることができます。変数は、それ自体をテストする可能性があります。これは、シェル オプションを設定する必要なしに明示的に行われます。

たとえば、コードは次のようになります。

N= ERR='error encountered - exiting' 
: ${force_color_prompt?"$ERR"}
/usr/bin/tput setaf >/dev/null 2>&1 || ${N:?"$ERR"}
: "${GREEN:=\033[1;32m}" "${DIM:=\033[2m}" "${RESET:=\033[00m}"
printf %b\\n \
    "Oh, ${GREEN}green${RESET} world, ${DIM}don't desert me now...${RESET}"

変数$Nは明示的に null 文字列に設定されるため、パラメータ展開の形式で評価されると、${N:?}親シェルは自動的に終了し、後続のステートメントも?展開のために評価され、その結果が に出力されますstderr。- についても同様です$force_color_prompt。設定されていない場合、スクリプトはエラーで終了し、すべて自動的に -$ERRに出力しますstderr

および$GREEN $RESET$DIM、現在設定されていない場合、または null 文字列に設定されている場合、定義した値に設定されます''。これにより、それらの値を環境変数としてスクリプトに渡すことができます。たとえば、上記のスニペットが および というスクリプト内にある場合greenworld.sh、次のように呼び出します。

GREEN="$(tput setaf 2)$(tput bold)" greenworld.sh

すると、$GREENスクリプトの内容はリセットされず、代わりに明示的に設定した値が継承されます。これにより、シェル スクリプトが柔軟になります。

godlygeek が推奨しているように、そのように使用することtputは、私も賛成です。

シェルでは、設定されていない変数は設定されている変数と同じくらい便利な場合があります。別の例を次に示します。

set -- * 
while ${1+:} false ; do
    #do stuff until all positionals are shifted away
shift ; done

この例では、最初のパラメータが定義されている限り、シェルの組み込み:null に展開され、その結果、次の呼び出しは何も実行されません。ただし、すべての位置パラメータが ed で削除されるfalseと、そのように展開されなくなり、が呼び出され、ループが終了します。これには無数のバリエーションがあります。shift${1}falsewhile

答え3

通常の使用、つまり変数を展開するだけの場合、未設定の変数はすべてのBourne/POSIXスタイルのシェルで空として扱われます。例外はset -o unsetakaset -uが有効な場合で、その場合、未設定の変数の値にアクセスしようとするとシェルはエラーを発生させます(godlygeek の回答)。

変数が設定されていないか空であるかをテストする方法はいくつかあります。たとえば、 は、変数が設定されている場合は (空であっても)${foo-bar}の値に展開され、が設定されていない場合は に展開されます。(コロンを追加して) は、空の変数を設定されていないものとして扱います。その他の同様の展開構文、、も同様に動作します。他の違いに加えて、空の変数はおよび(エクスポートされている場合)の出力にも表示されます。これらの違いは、必要な場合にのみ発生します。foobarfoo
${foo:-bar}${foo+bar}${foo?bar}${foo=bar}setexport

しかし、別の理由もあります常に変数を初期化する: 変数が本当に設定されていないことをどうやって知るのでしょうか? 呼び出し元が独自の目的で同様の変数を定義している可能性があります。呼び出し元が同じスクリプトの別の部分である場合、変数をローカルとして宣言しない限り (ksh/bash/zsh でのみ可能)、関数内での使用によって呼び出し元の変数が上書きされるため、変数名の衝突はいずれにしても問題になります。ただし、他の誰かが同じ変数名を選択したために、変数が環境内に存在する可能性もあります。シェル変数には小文字の名前を使用し、環境変数には大文字の名前を使用するという一種の慣例がありますが、すべての衝突が解決されるわけではなく、普遍的に従われているわけでもありません。

$ エクスポート DIM=1 SUM=42
$ バッシュ
ああ、緑の世界よ、今は私を見捨てないで...

関連情報