作業を始める前に、特定の環境変数が設定されていることを確認する必要があるUnixシェルスクリプトがいくつかあります。そのため、次のようにします。
if [ -z "$STATE" ]; then
echo "Need to set STATE"
exit 1
fi
if [ -z "$DEST" ]; then
echo "Need to set DEST"
exit 1
fi
これは多くの入力です。一連の環境変数が設定されていることを確認するためのより洗練された慣用句はありますか?
編集:私はこれらの変数は意味のあるデフォルト値を持っていないことに言及する必要があります - 設定されていない場合はスクリプトがエラーになるはずです。
明白な答えは、パラメータ展開の特別な形式の1つを使うことです。
: ${STATE?"Need to set STATE"}
: ${DEST:?"Need to set DEST non-empty"}
または、より良い(下記の「二重引用符の位置」の項を参照)。
: "${STATE?Need to set STATE}"
: "${DEST:?Need to set DEST non-empty}"
最初の変種(?
だけを使う)ではSTATEを設定する必要がありますが、STATE = ""(空の文字列)は問題ありません - あなたが望むものではなく、代わりの古い表記法です。
2番目の変種(:?
を使用)はDESTが設定されていて空でないことを要求します。
メッセージを入力しなかった場合、シェルはデフォルトのメッセージを表示します。
${var?}
構文は、Version 7 UNIXおよびBourne Shell(1978年頃)に移植可能です。 ${var:?}
構文はもう少し最近のものです。私はそれが1981年頃のSystem III UNIXにあったと思いますが、それ以前はPWB UNIXにあったかもしれません。それはそれゆえKornシェル、そして特にBashを含むPOSIXシェルにあります。
通常、シェルのmanページの パラメータ展開 というセクションに文書化されています。たとえば、bash
マニュアルには次のように記載されています。
${parameter:?word}
NullまたはUnsetの場合はエラーを表示します。 parameterがnullまたは未設定の場合、Wordの展開(またはWordが存在しない場合はその旨のメッセージ)が標準エラーに書き込まれ、シェルが対話的でない場合は終了します。そうでなければ、parameterの値が代入されます。
コロンコマンドは単に引数を評価してから成功することを付け加えるべきです。これは元のシェルのコメント表記です( '#
'から行末まで)。長い間、Bourne Shellスクリプトには最初の文字としてコロンがありました。 Cシェルはスクリプトを読み、最初の文字を使ってCシェル( '#
'ハッシュ)用かBourneシェル( ':
'コロン)用かを判断します。それからカーネルはその行為に乗り込み、 '#!/path/to/program
'のサポートを追加し、Bourne Shellは '#
'のコメントを得ました、そしてコロンの慣習は脇を通りました。しかし、コロンで始まるスクリプトに遭遇した場合は、その理由がわかります。
この議論についての考えはありますか? https://github.com/koalaman/shellcheck/issues/380#issuecomment-145872749
議論の要旨は次のとおりです。
…しかし、(バージョン0.4.1で)それを
shellcheck
すると、このメッセージが出ます。In script.sh line 13: : ${FOO:?"The environment variable 'FOO' must be set and non-empty"} ^-- SC2086: Double quote to prevent globbing and Word splitting.
この場合に私がすべきことについて何かアドバイスはありますか?
簡単な答えは「shellcheck
が示唆するようにする」です。
: "${STATE?Need to set STATE}"
: "${DEST:?Need to set DEST non-empty}"
その理由を説明するために、以下を検討してください。 :
コマンドは引数をエコーしません(しかしシェルは引数を評価します)。引数を見たいので、以下のコードではprintf "%s\n"
の代わりに:
を使用します。
$ mkdir junk
$ cd junk
$ > abc
$ > def
$ > ghi
$
$ x="*"
$ printf "%s\n" ${x:?You must set x} # Careless; not recommended
abc
def
ghi
$ unset x
$ printf "%s\n" ${x:?You must set x} # Careless; not recommended
bash: x: You must set x
$ printf "%s\n" "${x:?You must set x}" # Careful: should be used
bash: x: You must set x
$ x="*"
$ printf "%s\n" "${x:?You must set x}" # Careful: should be used
*
$ printf "%s\n" ${x:?"You must set x"} # Not quite careful enough
abc
def
ghi
$ x=
$ printf "%s\n" ${x:?"You must set x"} # Not quite careful enough
bash: x: You must set x
$ unset x
$ printf "%s\n" ${x:?"You must set x"} # Not quite careful enough
bash: x: You must set x
$
式全体が二重引用符で囲まれていない場合、$x
の値が最初の*
、次にファイル名のリストに展開されることに注意してください。これがshellcheck
が推奨していることは修正されるべきです。式が二重引用符で囲まれている形式に反対しないことを確認したことはありませんが、それが問題ないことは合理的な前提です。
これを試して:
[ -z "$STATE" ] && echo "Need to set STATE" && exit 1;
あなたの質問はあなたが使っているシェルに依存しています。
Bourne Shellは、あなたがしていることの邪魔になるものをほとんど残しません。
しかし...
それはどこでも動作します。
ただ試して、cshから離れてください。それはそれが追加した鐘と笛のために良かったです、Bourneシェルと比較して、しかしそれは今本当にひどいです。あなたが私を信じていないのなら、cshでSTDERRを試してみてください。 ( - :
ここに2つの可能性があります。上記の例、すなわち、
${MyVariable:=SomeDefault}
初めて$ MyVariableを参照する必要があります。これはenvを取ります。 var MyVariable。現在設定されていない場合は、後で使用するためにSomeDefaultの値を変数に割り当てます。
あなたはまたの可能性があります。
${MyVariable:-SomeDefault}
これは、この構文を使用している変数をSomeDefaultに置き換えるだけです。 SomeDefaultという値は変数に割り当てられず、このステートメントが検出された後もMyVariableの値はnullになります。
確かに最も簡単な方法は、bash
を使用していると仮定して、-u
スイッチをShebang(スクリプトの先頭にある行)に追加することです。
#!/bin/sh -u
これにより、バインドされていない変数が隠れている場合にスクリプトが終了します。
${MyVariable:=SomeDefault}
MyVariable
が設定されていてnullでない場合は、変数の値をリセットします(=何も起こりません)。
それ以外の場合、MyVariable
はSomeDefault
に設定されます。
上記は${MyVariable}
を実行しようとするので、変数を設定したいだけならdo:
MyVariable=${MyVariable:=SomeDefault}
私の意見では、最も単純で最も互換性のあるチェック は#!/ bin/sh のものです。
if [ "$MYVAR" = "" ]
then
echo "Does not exist"
else
echo "Exists"
fi
繰り返しになりますが、これは/ bin/sh用であり、古いSolarisシステムでも互換性があります。
bash
4.2では、名前が any valueに設定されているかどうか、空の文字列であってもテストする-v
演算子が導入されました。
$ unset a
$ b=
$ c=
$ [[ -v a ]] && echo "a is set"
$ [[ -v b ]] && echo "b is set"
b is set
$ [[ -v c ]] && echo "c is set"
c is set
私はいつも使っていました:
if [ "x$STATE" == "x" ]; then echo "Need to set State"; exit 1; fi
それほど簡潔ではない、私は怖い。
CSHの下には$?STATEがあります。
私のような将来の人々のために、私は一歩進んでvar名をパラメータ化したかったので、変数名の可変サイズのリストをループすることができます:
#!/bin/bash
declare -a vars=(NAME GITLAB_URL GITLAB_TOKEN)
for var_name in "${vars[@]}"
do
if [ -z "$(eval "echo \$$var_name")" ]; then
echo "Missing environment variable $var_name"
exit 1
fi
done
たくさんの変数を一度にチェックするためのNiceアサーションを書くことができます。
#
# assert if variables are set (to a non-empty string)
# if any variable is not set, exit 1 (when -f option is set) or return 1 otherwise
#
# Usage: assert_var_not_null [-f] variable ...
#
function assert_var_not_null() {
local fatal var num_null=0
[[ "$1" = "-f" ]] && { shift; fatal=1; }
for var in "$@"; do
[[ -z "${!var}" ]] &&
printf '%s\n' "Variable '$var' not set" >&2 &&
((num_null++))
done
if ((num_null > 0)); then
[[ "$fatal" ]] && exit 1
return 1
fi
return 0
}
サンプル呼び出し
one=1 two=2
assert_var_not_null one two
echo test 1: return_code=$?
assert_var_not_null one two three
echo test 2: return_code=$?
assert_var_not_null -f one two three
echo test 3: return_code=$? # this code shouldn't execute
出力:
test 1: return_code=0
Variable 'three' not set
test 2: return_code=1
Variable 'three' not set
これも方法です。
if (set -u; : $HOME) 2> /dev/null
...
...
http://unstableme.blogspot.com/2007/02/checks-whether-envvar-is-set-or-not.html
上記の解決策はどれも私の目的のためにはうまくいきませんでした。それは、長いプロセスを開始する前に設定する必要がある変数のオープンエンドのリストについて環境をチェックしたためです。私はこれで終わりました:
mapfile -t arr < variables.txt
EXITCODE=0
for i in "${arr[@]}"
do
ISSET=$(env | grep ^${i}= | wc -l)
if [ "${ISSET}" = "0" ];
then
EXITCODE=-1
echo "ENV variable $i is required."
fi
done
exit ${EXITCODE}