実際にご覧ください
#!/usr/bin/env bash
set -u # 未定義変数でエラーにする
v='string'
echo "01. \$v is '$v'" # > 01. $v is 'string'
echo "02. \$v is '${v:-replaced}'" # > 02. $v is 'string'
echo "03. \$v is '${v-replaced}'" # > 03. $v is 'string'
echo "04. \$v is '${v:-}'" # > 04. $v is 'string'
echo "05. \$v is '${v-}'" # > 05. $v is 'string'
unset v
# echo "06. \$v is '$v'" # > v: unbound variable
echo "07. \$v is '${v:-replaced}'" # > 07. $v is 'replaced'
echo "08. \$v is '${v-replaced}'" # > 08. $v is 'replaced'
echo "09. \$v is '${v:-}'" # > 09. $v is ''
echo "10. \$v is '${v-}'" # > 10. $v is ''
v=''
echo "11. \$v is '$v'" # > 11. $v is ''
echo "12. \$v is '${v:-replaced}'" # > 12. $v is 'replaced'
echo "13. \$v is '${v-replaced}'" # > 13. $v is ''
echo "14. \$v is '${v:-}'" # > 14. $v is ''
echo "15. \$v is '${v-}'" # > 15. $v is ''
01~05 と 11 はただの基本動作の確認。
06 は set -u
の効果で意図した通りエラーになるのでコメントアウトしている。
このオプションを切れば 06. $v is ''
となる。
07 と 08、09 と 10、14 と 15 はそれぞれ同じだが、12 と 13 だけはコロンの有無で結果が違うことがわかる。
筆者環境
- Windows 10 Home 64-bit 22H2
- Git for Windows 64-bit v2.45.2
- Git Bash v5.2.26(1)-release
なぜこうなるか
Bash のマニュアルの日本語訳を見てみる。
- Manpage of BASH - JM Project
http://surf.ml.seikei.ac.jp/~nakano/JMwww/html/GNU_bash/man1/bash.1.html- > パラメータの展開
関わりのある 2 つの記法について、引用しつつ述べる。
1. パラメータ展開によるデフォルト値の設定記法
- ${parameter:-word}
- デフォルトの値を使います。parameter が設定されていないか空文字列であれば、word を展開したものに置換されます。そうでなければ、parameter の値に置換されます。
これは $v
に対する ${v:-replaced}
または ${v:-}
の挙動を説明している。
変数 $v
が未設定(未定義)か空文字列である場合、${v:-replaced}
と書くと :-
より後の内容で置換されて文字列 replaced
になる。
もちろん、変数 $v
に空でない文字列が代入されていれば特に何もしない。
:-
より後の部分は空文字列でも良いので、置換される場合に ${v:-}
と書くと空文字列になる。
v='string'
echo "02. \$v is '${v:-replaced}'" # > 02. $v is 'string'
echo "04. \$v is '${v:-}'" # > 04. $v is 'string'
unset v
echo "07. \$v is '${v:-replaced}'" # > 07. $v is 'replaced'
echo "09. \$v is '${v:-}'" # > 09. $v is ''
v=''
echo "12. \$v is '${v:-replaced}'" # > 12. $v is 'replaced'
echo "14. \$v is '${v:-}'" # > 14. $v is ''
空文字列を偽とする言語において、論理和の短絡評価でデフォルト値を用意するようなものだろう。
my $v = 何らかの値; # 未定義かもしれないし定義済みの偽値かもしれない
$v = $v || 'replaced'; # 定義済みの偽値だとしても置き換わる
$v ||= 'replaced'; # 簡潔な記法
let v = 何らかの値; // 未定義や null かもしれないし定義済みの偽値かもしれない
v = v || 'replaced'; // 定義済みの偽値だとしても置き換わる
v ||= 'replaced'; // 簡潔な記法
で、私は引用元のページで上記の説明を眺めながら「${v-replaced}
とか ${v-}
みたいなコロン無しの記法についてはどこに載ってるんだ…?」と悩んでいた。
コロンありの記法と何が違うのかという以前に、そもそもなぜコロン無しの記法が認められているのかがわからなかった。
答えはその箇所ではなく、ひとつ手前の段落にあった。
2. コロンの無い記法
コロンを省略した場合には設定されているかどうかのみを調べます。
これが、ハイフン -
の前にコロン :
がある時と無い時の違いを説明している。
コロン無しで ${v-replaced}
と書くと、変数 $v
が未設定の場合にのみ文字列 replaced
への置換を行う一方、変数 $v
にあえて空文字列が代入されている場合には置換を行わない。
コロンありの ${v:-replaced}
では変数 $v
が空文字列だったとしても元の値が保持されず置換されてしまうので、わずかな場合分けの差ではあるが決定的に異なる挙動をする。
v='string'
echo "02. \$v is '${v:-replaced}'" # > 02. $v is 'string'
echo "03. \$v is '${v-replaced}'" # > 03. $v is 'string'
unset v
echo "07. \$v is '${v:-replaced}'" # > 07. $v is 'replaced'
echo "08. \$v is '${v-replaced}'" # > 08. $v is 'replaced'
v=''
echo "12. \$v is '${v:-replaced}'" # > 12. $v is 'replaced'
echo "13. \$v is '${v-replaced}'" # > 13. $v is ''
${v-}
と書けば、「未定義なら空文字列に、空文字列なら元のままに、その他何らかの値なら元のままに」と指定していることになる。
比較として、${v:-}
と書いた時には「未定義なら空文字列に、空文字列なら新しい空文字列に、その他何らかの値なら元のままに」と指定していることになるだろう。
結果的には ${v-}
と ${v:-}
は完全に同一となる。
今回の例の 11 以外で空文字列となったものに無理矢理にでも違いを見出すとすれば、13・15 には置換前の空文字列が、09・10・14 には置換後の空文字列が出力されていると言える。
unset v
echo "07. \$v is '${v:-replaced}'" # > 07. $v is 'replaced'
echo "08. \$v is '${v-replaced}'" # > 08. $v is 'replaced'
echo "09. \$v is '${v:-}'" # > 09. $v is ''
echo "10. \$v is '${v-}'" # > 10. $v is ''
v=''
echo "12. \$v is '${v:-replaced}'" # > 12. $v is 'replaced'
echo "13. \$v is '${v-replaced}'" # > 13. $v is ''
echo "14. \$v is '${v:-}'" # > 14. $v is ''
echo "15. \$v is '${v-}'" # > 15. $v is ''
コロン無しの置換は要するにヌル合体演算子のようなものであり、より影響範囲を絞ったデフォルト値の供給方法と思われる。
my $v = 何らかの値; # 未定義かもしれないし定義済みの偽値かもしれない
$v = $v // 'replaced'; # 未定義の場合のみ置き換わる
$v //= 'replaced'; # 簡潔な記法
let v = 何らかの値; // 未定義や null かもしれないし定義済みの偽値かもしれない
v = v ?? 'replaced'; // 未定義か null の場合のみ置き換わる
v ??= 'replaced'; // 簡潔な記法
じゃあコロンの有無はどっちが良いのか
普通のプログラミング的な発想であれば、未設定と空文字列が一緒くたの例外になる ${v:-replaced}
よりは、未設定のみを例外とする ${v-replaced}
の方がより厳密で安全そうだと思う。
しかし Bash はシェル言語である以上、シェル特有の文字列取り扱いの癖や使われ方についても考慮せざるを得ない。
例えば次のような対話的な入力待ちをするとして…
read -p '値を入力して下さい: ' v
ユーザーがいきなり Enter を押した場合、それを「何も入力されなかった」としたいか「空文字列が入力された」としたいかと考えると、どちらにも一理ある気がする。
それに、言っちゃ何だがシェル言語なんてのは小規模な手抜きをするためのものでもあるので、その目的に適うなら行儀の良い厳密性より書き捨てられる雑さの方が愛されることも絶対にある。
(スクリプトファイルとしてでなくターミナルに打ち込むコマンドとしてなら特に!)
そう考えると、月並みな結論だが、単にどちらが良い悪いというよりはそれぞれの違いを把握して選択することが大事だろう。
ただ、${v:-}
と ${v-}
については単純に短さで後者の勝ち。
まとめ
仰々しく図や表に起こすならこんな感じか。
元の $v
|
${v:-replaced} |
${v-replaced} |
${v:-} |
${v-} |
---|---|---|---|---|
未設定 (未定義) |
'replaced' |
'replaced' |
'' |
'' |
空文字列 ( '' ) |
'replaced' |
'' |
'' |
'' |
空でない 文字列 |
$v |
$v |
$v |
$v |
評価 | より曖昧 | より厳密 |
より 長いだけ |
より 短い |
余談:なんでこんな記事を書いたか
Git for Windows の git-prompt.sh
を見ていたらコロン無しの方が出てきて、知らない記法だったから。
- git/contrib/completion/git-prompt.sh at v2.45.2.windows.1 · git-for-windows/git
https://github.com/git-for-windows/git/blob/v2.45.2.windows.1/contrib/completion/git-prompt.sh- > 144 行目等
シェルの変数置換記法、ググりにくすぎ…。
おわり