Bourne/Ash シェルで変数・環境変数が未定義・空かを確認・判定する【Alpine Linux など】

> 変数の未定義チェックに `Bash` シェルの `-v` オプションを `Bourne` シェルで使うと、`unknown operand` エラーが発生する。
>
> `Alpine Linux` などの **`Bourne` 系シェル（`Ash`）で変数や環境変数の未定義をチェックできないか**。

[「`bourne` `シェル` `未定義` `確認`」とググって](https://www.google.com/search?q=bourne+%E3%82%B7%E3%82%A7%E3%83%AB+%E6%9C%AA%E5%AE%9A%E7%BE%A9+%E7%A2%BA%E8%AA%8D)も、`Bash` シェルの情報ばかりだったので、自分のググラビリティとして。

## TL;DR

> シェルの変数展開（`${}`）で、代替値（`:+`）を使って検知する

```sh:未定義or空文字なら1（false）、定義済み&値入りなら0（true）
# Bourne/Ash/Bash/Dash コンパチ（互換のある）構文
[ "${VAR:+defined}" ] || {
    echo '変数 VAR が未定義もしくは空です。'
    exit 1
}
[ "${VAR:+defined}" ] && {
    echo "変数 VAR は定義されています： ${VAR}"
    exit 0
}
```
- [オンラインで動作を見る](https://paiza.io/projects/IkTkfUQAmI5S0tHcHPsx8Q) @ paiza.IO
- 参考文献：[bashで[ -v "$VAR" ]を使わずに変数が未定義か判定する](https://qiita.com/ymdymd/items/51bf4145ec58654eaffc) @ Qiita
- 参考文献：[mattintosh4 さんのコメント](#comment-056efcdd24cb03ec0cea)
 
## TS;DR

確かに Alpine Linux で `bash` を使いたいならインストールすればいいだけなのです。しかし、Docker での利用なので、なるべく余計なものを入れたくない（レイヤーを作りたくない）のです。

Docker コンテナ起動時に[ホストからコンテナに環境変数を渡す](https://qiita.com/KEINOS/items/518610bc2fdf5999acf2)必要がある場合、**必須の環境変数がセットされていないとエラーでコンテナが起動しないようにしたい**のです。

「未定義ならエラーで終了」だけなら[コメントいただいた](#comment-056efcdd24cb03ec0cea)ように `set -u` をスクリプトなどに記載するのが簡単です。[ポカヨケ](https://ja.wikipedia.org/wiki/%E3%83%9D%E3%82%AB%E3%83%A8%E3%82%B1)をしたい場合は、関連するスクリプトファイルに `: ${VAR?}` を記載しておくのもいいかもしれません。

### 任意のエラーメッセージ

ただ、今回は<strong>「未定義の場合もしくは必須項目が空の場合は任意のメッセージを表示して終了」させたい</strong>のです。

任意のメッセージとは「どのファイルに環境変数を記載すべきか」などのユーザーへの指示メッセージです。そのため判定式が必要だったのです。

下記は、変数定義の判定に `-v` もしくは `-z` オプションを使った方法と `set` コマンドを使った方法を説明しています。

おすすめは、コメントにいただいた [TL;DR の `set` コマンドを使った方法](#tldr)で、シンプルで可読性の高い書き方だと思います。

<details><summary>検証環境情報</summary><div><br>

```shell-session:Alpine3.10.2
~ # cat /etc/os-release
NAME="Alpine Linux"
ID=alpine
VERSION_ID=3.10.2
PRETTY_NAME="Alpine Linux v3.10"
HOME_URL="https://alpinelinux.org/"
BUG_REPORT_URL="https://bugs.alpinelinux.org/"
```

```shell-session:shのバージョン（Bourneというより正確にはAsh）
~ # sh --help
BusyBox v1.30.1 (2019-06-12 17:51:55 UTC) multi-call binary.

Usage: sh [-/+OPTIONS] [-/+o OPT]... [-c 'SCRIPT' [ARG0 [ARGS]] / FILE [ARGS] / -s [ARGS]]

Unix shell interpreter
```

```shell-session:bashのバージョン（検証用に別途インストール）
~ # bash --version
GNU bash, version 5.0.0(1)-release (x86_64-alpine-linux-musl)
Copyright (C) 2019 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>

This is free software; you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
```

</div></details>

## `-v` オプションの場合

ググると、変数の定義確認で一番出てくる方法です。問題は Bourne/Ash シェルでは使えないことです。

```shell:sample1.sh
~ # cat sample1.sh
#!/bin/sh

[ -v HOGE ] || {
    echo 'Not defined HOGE'
    exit 1
}

echo 'HOGE is defined with value:' $HOGE
```
```shell-session:環境変数を未定義でテスト
~ # unset HOGE

~ # # Bash シェルで実行（正常）
~ # bash sample1.sh
Not defined HOGE

~ # # Bourne シェルで実行（エラー）
~ # sh sample1.sh
sh: HOGE: unknown operand
Not defined HOGE
```
```shell-session:環境変数を定義してテスト
~ # export HOGE=FUGA

~ # # Bash シェルで実行（正常）
~ # bash sample1.sh
HOGE is defined with value: FUGA

~ # # Bourne シェルで実行（エラー）
~ # sh sample1.sh
sh: HOGE: unknown operand
Not defined HOGE
```
```shell-session:空の環境変数を定義してテスト
~ # export HOGE=''

~ # # Bash シェルで実行（正常）
~ # bash sample1.sh
HOGE is defined with value:

~ # # Bourne シェルで実行（エラー）
~ # sh sample1.sh
sh: HOGE: unknown operand
Not defined HOGE
```

### `-z` オプションの場合

`-v` オプションが使えない場合に `-z` オプションを使った bash/bourne/ash 互換の代替法です。定義されているか否かの判定だけで、値が空の場合は真（`true`）になっていることに注意ください。

```shell:sample2.sh
~ # cat sample2.sh
#!/bin/sh

[ -z "$HOGE" ] && [ "${HOGE:-A}" = "${HOGE-A}" ] && {
    echo 'Not defined HOGE'
    exit 1
}

echo 'HOGE is defined with value:' $HOGE
```
```shell-session:どちらも同じように動いた
~ # unset HOGE
~ # bash sample2.sh
Not defined HOGE
~ # sh sample2.sh
Not defined HOGE

~ # export HOGE=''
~ # bash sample2.sh
HOGE is defined with value:
~ # sh sample2.sh
HOGE is defined with value:

~ # export HOGE=FUGA
~ # bash sample2.sh
HOGE is defined with value: FUGA
~ # sh sample2.sh
HOGE is defined with value: FUGA

~ # # 🎉
```

## シェルの変数展開で代替値を使った場合

この方法は、**変数が定義済みだけでなく値が空の場合も検知できる**ので、必須の環境変数をチェックする際に有用だと思います。

シェルの変数展開、つまり `echo "The parameter is: ${parameter}"` のように `${}` で変数を展開する際に `:+` を使うアイデアです。

`${parameter:+word}` と `:+` を使うと `${parameter}` が `NULL` 以外の場合 `word` に置換されます。

```shell:sample3.sh
~ # cat sample3.sh
#!/bin/sh

[ "${HOGE:+dummy}" ] || {
    echo 'Not defined HOGE or empty value'
    exit 1
}

echo 'HOGE is defined with value:' $HOGE
```
```shell-session:どちらも同じように動いた
~ # unset HOGE
~ # bash sample3.sh
Not defined HOGE or empty value
~ # sh sample3.sh
Not defined HOGE or empty value

~ # export HOGE=''
~ # bash sample3.sh
Not defined HOGE or empty value
~ # sh sample3.sh
Not defined HOGE or empty value

~ # export HOGE=FUGA
~ # bash sample3.sh
HOGE is defined with value: FUGA
~ # sh sample3.sh
HOGE is defined with value: FUGA

~ # # 🎉
```

## 参考文献

- [bashで[ -v "$VAR" ]を使わずに変数が未定義か判定する](https://qiita.com/ymdymd/items/51bf4145ec58654eaffc) @ Qiita
- [Bourne Shell Tutorial](http://www.grymoire.com/Unix/Sh.html) @ The Grymoire
- [Embedded Linux](https://en.wikipedia.org/wiki/Almquist_shell#Embedded_Linux) | Almquist shell @ Wikipedia
- [シェルの変数展開](https://qiita.com/bsdhack/items/597eb7daee4a8b3276ba)@ Qiita
- [【 set 】コマンド（応用編2）――定義されていない変数の扱いやコマンドのエラーへの対応を変更する](https://www.atmarkit.co.jp/ait/articles/1805/18/news038.html) | Linux基本コマンドTips @ atmarkIT