bashの変数展開(パラメータ展開)とは、${変数名...} の形で、変数の値を取り出すと同時に「既定値を補う・一部を切り出す・削除する・置換する」といった加工を行う機能です。 sed や cut などの外部コマンドを呼ばずに、シェルだけで文字列を扱えます。たとえば ${name:-guest} は「name が未定義か空なら guest を使う」、${file%.*} は「拡張子を取り除く」という意味です。

この記事では、変数展開の全記法を 「記法・意味・入力・実行結果」の4列の早見表 にまとめました。結果はすべて bash 5.2.21 で実行して確認したものです。さらに、:- と - の違い(未定義と空文字の扱い)を総当たりで比べた表、sh(dash)で使えない記法を実際に試した表、set -u との組み合わせ、クォートを省くと起きる単語分割まで、手元で試せるコードつきで説明します。

bash変数展開の早見表

入力の列は、展開前に変数へ代入した値です。結果は < > を除いた部分が実際の出力です。

既定値・必須チェック

記法 意味 入力 実行結果
${v:-X} 未定義または空なら X を使う(v は変えない) v 未定義 X
${v-X} 未定義のときだけ X を使う v="" (空文字)
${v:=X} 未定義または空なら X を代入して使う v 未定義 X(v も X になる)
${v:?msg} 未定義または空なら msg を表示して終了 v 未定義 v: msg、終了コード 1
${v:+X} 値があるときだけ X を使う v=abc X
${v+X} 定義済みなら(空でも)X を使う v="" X

長さ・切り出し

記法 意味 入力 実行結果
${#s} 文字数 s="hello world" 11
${s:2} 2文字目(0始まり)以降 s=0123456789 23456789
${s:2:3} 位置2から3文字 同上 234
${s: -3} 末尾3文字(コロンの後に空白が必要) 同上 789
${s: -3:2} 末尾3文字目から2文字 同上 78
${s:2:-2} 位置2から、末尾2文字の手前まで 同上 234567

前方・後方の削除

記法 意味 入力 実行結果
${p#*/} 先頭から最短一致で削除 p=/var/log/app/access.2026.log.gz var/log/app/access.2026.log.gz
${p##*/} 先頭から最長一致で削除(ファイル名) 同上 access.2026.log.gz
${p%/*} 末尾から最短一致で削除(ディレクトリ) 同上 /var/log/app
${p%.*} 末尾から最短一致で削除(最後の拡張子を除く) 同上 /var/log/app/access.2026.log
${p%%.*} 末尾から最長一致で削除 同上 /var/log/app/access
${p##*.} 最後の拡張子だけ残す 同上 gz

置換・大文字小文字

記法 意味 入力 実行結果
${t/foo/X} 最初の1か所を置換 t="foo bar foo baz" X bar foo baz
${t//foo/X} すべて置換 同上 X bar X baz
${t/#foo/X} 先頭に一致したときだけ置換 同上 X bar foo baz
${t/%baz/X} 末尾に一致したときだけ置換 同上 foo bar foo X
${t//foo} すべて削除(置換後を省略) 同上 bar baz
${w^} 先頭1文字を大文字に w="hello World" Hello World
${w^^} すべて大文字に 同上 HELLO WORLD
${w,} 先頭1文字を小文字に w="Hello World" hello World
${w,,} すべて小文字に w="hello World" hello world

間接参照・配列・変換

記法 意味 入力 実行結果
${!key} key の値を変数名とみなして参照 APP_PORT=8080; key=APP_PORT 8080
${!APP_*} APP_ で始まる変数名の一覧 APP_HOST、APP_PORT を定義 APP_HOST APP_PORT
"${arr[@]}" 配列の全要素(要素ごとに1単語) arr=("a b" c d e) a b/c/d/e の4つ
${#arr[@]} 配列の要素数 同上 4
${!arr[@]} 配列の添字の一覧 同上 0 1 2 3
"${arr[@]:1:2}" 配列の一部(位置1から2要素) 同上 c/d
${q@Q} シェルに再入力できる形にクォート q="it's a \$test" 'it'\''s a $test'
${w@U} すべて大文字に w="hello World" HELLO WORLD

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「bash変数展開の一覧|${var:-default}と文字列の置換・切り出し」

検証環境と確認用スクリプト

項目 内容
bash GNU bash 5.2.21
sh dash 0.5.12(Ubuntu の /bin/sh)
ロケール C.UTF-8(日本語の文字数を数える箇所)

早見表の結果は、次の小さな関数で式と結果を並べて出力し、確認しました。手元で試すときにも使えます。

#!/usr/bin/env bash
# e '式' で「式 => <結果>」を表示する
e() {
  local r
  r=$(eval "printf '%s' \"$1\"" 2>&1)
  printf '%-22s => <%s>\n' "$1" "$r"
}

s="0123456789"
e '${s:2:3}'
e '${s: -3}'
${s:2:3}               => <234>
${s: -3}               => <789>

変数や配列そのものの基本は、既存記事「シェルスクリプトの書き方|基本構文の早見表と安全なテンプレート」で扱っています。この記事は ${ } の中に書ける加工の記法に絞って掘り下げます。

既定値を使う:${var:-default}とコロンの有無

未定義と空文字を総当たりで比べる

既定値系の4つの記法は、コロンを付けるかどうかで「空文字」の扱いが変わります。 未定義・空文字・値ありの3つの状態で、8通りすべてを実行しました。

unset u; empty=""; val="abc"
for v in u empty val; do
  for op in ':-' '-' ':+' '+'; do e "\${$v${op}X}"; done
done
記法 未定義(unset v) 空文字(v="") 値あり(v=abc)
${v:-X} X X abc
${v-X} X (空文字) abc
${v:+X} (空文字) (空文字) X
${v+X} (空文字) X X

コロンあり(:-)は「未定義または空」を対象にし、コロンなし(-)は「未定義」だけを対象にします。 「空文字も有効な値として受け取りたい」ときはコロンなし、「空なら既定値にしたい」ときはコロンあり、と選びます。迷ったらコロンありが多くの場面に合います。

やりたいこと 記法 例
環境変数が無ければ既定値 ${v:-X} port="${PORT:-8080}"
引数が省略されたら既定値 ${1:-X} target="${1:-.}"
既定値を変数にも入れておく ${v:=X} : "${LOG_DIR:=/tmp/logs}"
必須の変数が無ければ止める ${v:?msg} : "${DB_HOST:?DB_HOST を設定してください}"
値があるときだけオプションを足す ${v:+X} cmd ${verbose:+--verbose}
定義されているかを調べる ${v+x} [ -n "${DEBUG+x}" ]

:= は代入も行う

unset u; : "${u:=init}"; echo "u=<$u>"
empty=""; : "${empty=init}";  echo "empty=<$empty>"
          : "${empty:=init}"; echo "empty=<$empty>"
u=<init>
empty=<>
empty=<init>

:- との違いは、展開のあと変数自体にも値が残ること です。行頭の : は「何もしないコマンド」で、展開だけを行わせるために使います。2行目のとおり、コロンなしの = は空文字を「定義済み」とみなすので代入しません。なお $1 などの位置パラメータには := で代入できないため、引数には :- を使います。

:? で必須の変数を確認する

( unset u; echo "${u:?u is required}"; echo "not reached" ); echo "exit=$?"
( unset u; echo "${u:?}" ); echo "exit=$?"
all.sh: line 13: u: u is required
exit=1
all.sh: line 14: u: parameter null or not set
exit=1

メッセージは標準エラー出力に出て、スクリプト(非対話シェル)はその場で終了します。メッセージを省くと parameter null or not set という既定の文言になります。スクリプトの冒頭に必須の環境変数を並べておくと、設定漏れにすぐ気づけます。

#!/usr/bin/env bash
set -u
: "${DB_HOST:?環境変数 DB_HOST を設定してください}"
echo "connect to $DB_HOST"
$ bash req.sh
req.sh: line 3: DB_HOST: 環境変数 DB_HOST を設定してください
$ echo $?
1
$ DB_HOST=db01 bash req.sh
connect to db01

文字列の長さと切り出し

長さ ${#var}

s="hello world"; ja="こんにちは"; arr=(a b c)
記法 実行結果 説明
${#s} 11 文字数
${#ja} 5 UTF-8 のロケール(C.UTF-8 や ja_JP.UTF-8)では文字数
${#ja}(LC_ALL=C) 15 C ロケールではバイト数になる
${#arr[@]} 3 配列の要素数
${#arr[0]} 1 要素0の文字数

日本語の文字数はロケールによって結果が変わります。 cron など、環境変数が最小限の状態で動くスクリプトでは LC_ALL を明示しておくと、手元と同じ結果になります。

切り出し ${var:位置:長さ}

位置は 0 から数えます。s="0123456789" で試した結果です。

記法 実行結果 説明
${s:2} 23456789 位置2から最後まで
${s:2:3} 234 位置2から3文字
${s:0:1} 0 先頭1文字
${s: -3} 789 末尾から3文字
${s:(-3)} 789 かっこで囲んでも同じ
${s:-3} 0123456789 既定値の :- と解釈される(s に値があるのでそのまま)
${s: -3:2} 78 末尾3文字目から2文字
${s:2:-2} 234567 長さに負数:末尾から2文字手前まで
${s: -5:-2} 567 末尾5文字目から、末尾2文字の手前まで
${s:20} (空文字) 範囲外はエラーにならず空になる

負の位置を使うときは、コロンの後に空白を入れる(${s: -3})か、かっこで囲みます(${s:(-3)})。 空白が無い ${s:-3} は「未定義なら 3」という既定値の記法になり、エラーも出ないので気づきにくい箇所です。

長さに負数を指定して、開始位置より手前になった場合は次のエラーになります。

$ echo "${s:2:-9}"
bash: -9: substring expression < 0

前方・後方の削除:パスと拡張子を取り出す

4つの記号の覚え方

記号 削除する側 一致のしかた
# 先頭(前方) 最短一致
## 先頭(前方) 最長一致
% 末尾(後方) 最短一致
%% 末尾(後方) 最長一致

キーボードの並びで # は $ の左、% は $ の右にある ので、「左(先頭)を削るのが #、右(末尾)を削るのが %」と覚えられます。記号を2つ重ねると最長一致です。パターンには glob(*、?、[...])が使えます。正規表現ではない点に注意してください。

パスの分解レシピ

path="/var/log/app/access.2026.log.gz" を分解します。

取り出したいもの 記法 実行結果
ファイル名(basename 相当) ${path##*/} access.2026.log.gz
ディレクトリ(dirname 相当) ${path%/*} /var/log/app
最後の拡張子 ${path##*.} gz
最後の拡張子を除いたパス ${path%.*} /var/log/app/access.2026.log
最初のドット以降をすべて除く ${path%%.*} /var/log/app/access
先頭の / を除く ${path#*/} var/log/app/access.2026.log.gz
ファイル名の、最初のドットより前 f="${path##*/}"; ${f%%.*} access
ファイル名の、最初のドットより後 f="${path##*/}"; ${f#*.} 2026.log.gz

同じ値で basename "$path" は access.2026.log.gz、dirname "$path" は /var/log/app を返し、結果が一致することも確認しました。

覚えておきたい境界の動き

入力 記法 実行結果 説明
nopath="file.txt" ${nopath%/*} file.txt / が無いと何も削除されない(dirname は . を返す)
nopath="file.txt" ${nopath##*/} file.txt 一致しなければ元の値のまま
p2="/usr/local/" ${p2%/} /usr/local 末尾の / を1つだけ除く
url="https://example.com/a/b?x=1" ${url#*://} example.com/a/b?x=1 スキーム部分を除く
同上 ${url%%\?*} https://example.com/a/b ? 以降を除く(? は glob 文字なので \? と書く)

パターンに一致しないときは、エラーにならず元の値がそのまま返ります。 ${nopath%/*} のように「ディレクトリ部分が無いパス」を扱う可能性があるなら、dirname コマンドを使うか、/ を含むかを先に確かめる書き方が安全です。

拡張子を付け替える

for f in report.md notes.md; do
  echo "$f -> ${f%.md}.html"
done
report.md -> report.html
notes.md -> notes.html

${f%.md} で拡張子を取り除き、その後ろに新しい拡張子を続けて書くだけです。ファイルを順に処理するループの書き方は、別記事「シェルスクリプトのfor・whileループ」でまとめています。

文字列を置換する

t="foo bar foo baz" で試した結果です。

記法 実行結果 説明
${t/foo/X} X bar foo baz 最初に一致した1か所
${t//foo/X} X bar X baz 一致したすべて
${t/#foo/X} X bar foo baz 先頭が一致したときだけ
${t/%baz/X} foo bar foo X 末尾が一致したときだけ
${t/#bar/X} foo bar foo baz 先頭が bar ではないので変化なし
${t//foo} bar baz 置換後を省略すると削除
${t// /_} foo_bar_foo_baz 空白をすべて _ に
${t//[aeiou]/*} f** b*r f** b*z 文字クラスも使える
${t/f*o/X} X baz * は最長一致(foo bar foo 全体に一致)

スラッシュ1つは最初の1か所、2つはすべて、/# は先頭、/% は末尾 です。パターンは削除のときと同じく glob で、最長一致になります。最後の行のように * を使うと思ったより広く一致することがあるので、結果を確かめながら使うとよいでしょう。

スラッシュや特殊文字を置換する

入力 記法 実行結果 説明
p="a/b/c" ${p//\//-} a-b-c パターン中の / は \/ と書く
amp="name" ${amp/name/[&]} [name] bash 5.2 では、置換後の & が一致した文字列を表す

2つ目は bash 5.2 で加わった動作です(patsub_replacement オプションが既定で有効)。置換後の文字列に & そのものを入れたいときは \& と書きます。置換後の文字列を変数で渡す場合は、"${t//foo/"$rep"}" のように変数をクォートすると & が文字どおりに扱われます。

sedとの使い分け

状況 向いている方法
変数に入った短い文字列を1回置換する 変数展開(プロセスを起動しないので速い)
ループの中で何度も置換する 変数展開
正規表現(+、\d、グループ参照など)が必要 sed または [[ =~ ]]
ファイル全体を置換する sed

大文字・小文字を変換する

w="hello World" で試した結果です。

記法 実行結果 説明
${w^} Hello World 先頭1文字を大文字
${w^^} HELLO WORLD すべて大文字
${w,}(w="Hello World") hello World 先頭1文字を小文字
${w,,} hello world すべて小文字
${w^^[lo]} heLLO WOrLd パターンに一致する文字だけ大文字
${w@U} HELLO WORLD ^^ と同じ結果
${w@u} Hello World ^ と同じ結果
${w@L} hello world ,, と同じ結果

入力の表記ゆれをそろえて比較するときに便利 です。たとえば、確認の入力を小文字にそろえてから判定できます。

answer="Yes"
case "${answer,,}" in
  y|yes) echo "続行します" ;;
  *)     echo "中止します" ;;
esac

^^ と ,, は bash 4.0 以降の機能です。macOS に標準で入っている bash は 3.2 系のため、そちらでは tr '[:lower:]' '[:upper:]' を使います(macOS での動作は一般に知られた仕様で、この記事では未実行です)。

間接参照:変数名を変数で指定する

APP_HOST=localhost; APP_PORT=8080; key=APP_PORT

echo "${!key}"            # 8080
echo "${!APP_*}"          # APP_HOST APP_PORT

for name in "${!APP_@}"; do
  echo "$name=${!name}"
done
8080
APP_HOST APP_PORT
APP_HOST=localhost
APP_PORT=8080
記法 意味
${!key} key の値(APP_PORT)を変数名として、その値を取り出す
${!APP_*} APP_ で始まる変数名を、1つの文字列にまとめて返す
"${!APP_@}" APP_ で始まる変数名を、1つずつ別の単語として返す(ループ向き)
${!arr[@]} 配列の添字(連想配列ならキー)の一覧

${!...} は、書き方によって「間接参照」「変数名の一覧」「配列の添字」の3つの意味を持ちます。 接頭辞をそろえた設定用の変数をまとめて表示する、といった用途に向いています。

間接参照する変数名が決まっているなら、名前参照(nameref)も使えます。

declare -n ref=APP_HOST
echo "nameref: $ref"      # nameref: localhost

配列と位置パラメータの展開

配列

arr=("a b" c d e) を、要素ごとに < > で囲んで表示しました。

show() { printf '<%s>' "$@"; echo; }
show "${arr[@]}"
記法 実行結果 説明
"${arr[@]}" <a b><c><d><e> 要素ごとに1単語(4個)
"${arr[*]}" <a b c d e> 全要素を IFS の先頭文字でつないだ1単語
${arr[@]}(クォートなし) <a><b><c><d><e> 単語分割されて5個になる
"${!arr[@]}" <0><1><2><3> 添字の一覧
"${arr[@]:1:2}" <c><d> 位置1から2要素
${arr[-1]} e 最後の要素
$arr a b 添字を省くと要素0だけ
"${arr[@]/#/x-}" <x-a b><x-c><x-d><x-e> 全要素の先頭に文字を足す
"${f[@]%.*}"(f=(a.txt b.md)) <a><b> 全要素から拡張子を除く
"${arr[@]^^}" <A B><C><D><E> 全要素を大文字に
(IFS=,; echo "${arr[*]}") a b,c,d,e カンマ区切りで連結

削除・置換・大文字小文字の記法は、[@] を付けると配列の全要素に一度に適用されます。 ループを書かずに一括で加工できるので、ファイル名の一覧を扱うときに重宝します。

位置パラメータ(引数)

set -- one two three four で引数を4つ設定して試しました。

記法 実行結果 説明
${1} one 1番目の引数
${#} 4 引数の個数
"${@:2}" two three four 2番目以降
"${@:2:2}" two three 2番目から2個
"${@: -1}" four 最後の引数
$10 と ${10}(引数11個のとき) a0 と j 2桁以上は波かっこが必要

最後の行は、引数を a から k まで11個にして確認しました。$10 は「$1 の後ろに 0」と解釈される ので、10番目以降は ${10} と書きます。

${var@Q}などの変換演算子

${変数@演算子} の形で、値を別の表現に変換できます。

記法 入力 実行結果 説明
${q@Q} q="it's a \$test" 'it'\''s a $test' シェルにそのまま渡せる形にクォート
"${arr[@]@Q}" arr=("a b" c d e) 'a b' 'c' 'd' 'e' 配列の各要素をクォート
${esc@E} esc='a\tb\n' a(タブ)b(改行) \t などのエスケープを解釈
${ps@P} ps='\h:\W' ホスト名:現在のディレクトリ名 プロンプト文字列として展開
${q@A} 同上 q='it'\''s a $test' 変数を再作成できる代入文
${num@A} declare -i num=5 declare -i num='5' 属性つきの宣言文
${num@a} 同上 i 変数の属性(i=整数、r=読み取り専用、x=エクスポート、a=配列、A=連想配列)
${m[@]@K} declare -A m=([k1]=v1 [k2]=v2) k1 "v1" k2 "v2" 連想配列のキーと値の組(順序は不定)
${w@U} / ${w@u} / ${w@L} w="hello World" HELLO WORLD / Hello World / hello world 大文字・先頭大文字・小文字

よく使うのは @Q です。 実行するコマンドをログに残すときや、ssh や eval に渡す文字列を組み立てるときに、空白や引用符を含む値を安全な形にできます。

args=("my report.txt" "it's")
echo "実行: cp ${args[*]@Q} /backup/"
実行: cp 'my report.txt' 'it'\''s' /backup/

@U、@u、@L、@K は bash 5.1 以降で使える演算子として知られています(この記事では 5.2.21 で動作を確認しました)。

sh(dash)で使えない記法を実際に試す

#!/bin/sh のスクリプトや、Docker の軽量イメージ、sh -c で実行されるコマンドでは、bash ではなく dash などの POSIX シェルが動きます。同じ式を bash 5.2.21 と dash 0.5.12 の両方で実行しました(v="hello World"、path="/a/b/c.tar.gz"、u は未定義)。

記法 bash dash 分類
${u:-def} def def POSIX
${u:=def} def def POSIX
${u:+alt} (空文字) (空文字) POSIX
${u:?need u} エラーで終了 u: need u で終了 POSIX
${#v} 11 11 POSIX
${path#*/} a/b/c.tar.gz a/b/c.tar.gz POSIX
${path##*/} c.tar.gz c.tar.gz POSIX
${path%.*} /a/b/c.tar /a/b/c.tar POSIX
${path%%.*} /a/b/c /a/b/c POSIX
${v:0:5} hello Bad substitution bash 拡張
${v: -3} rld Bad substitution bash 拡張
${v/World/X} hello X Bad substitution bash 拡張
${v//l/L} heLLo WorLd Bad substitution bash 拡張
${v^^} HELLO WORLD Bad substitution bash 拡張
${v,,} hello world Bad substitution bash 拡張
${!v} (間接参照) Bad substitution bash 拡張
${v@Q} 'hello World' Bad substitution bash 拡張
a=(1 2 3) 配列 Syntax error: "(" unexpected bash 拡張

既定値系(:- := :? :+)、長さ(${#v})、前方・後方の削除(# ## % %%)は sh でも使えます。切り出し・置換・大文字小文字・間接参照・@ 演算子・配列は bash の拡張 です。Bad substitution と表示されたら、スクリプトが bash ではなく sh で実行されていないかを確認してください。

sh で動かすときの代わりの書き方

次の書き方は dash で実行して結果を確認しました。

やりたいこと bash sh でも動く書き方 結果
先頭5文字 ${v:0:5} echo "$v" | cut -c1-5 hello
先頭5文字(外部コマンドなし) ${v:0:5} ${v%"${v#?????}"} hello
置換(1か所) ${v/World/X} echo "$v" | sed "s/World/X/" hello X
置換(すべて) ${v//l/L} echo "$v" | sed "s/l/L/g" heLLo WorLd
大文字に ${v^^} echo "$v" | tr "[:lower:]" "[:upper:]" HELLO WORLD

set -uと組み合わせる

set -u(set -o nounset)は、未定義の変数を参照するとエラーで止める設定です。変数名の打ち間違いに気づけるので、スクリプトの冒頭に書くことが多い設定です。既定値系の変数展開を使うと、set -u のもとでも「未定義かもしれない変数」を安全に参照できます。

#!/usr/bin/env bash
set -u
echo "1: ${NAME:-guest}"
echo "2: ${NAME-}"
echo "3: <${1:-}>"
if [ -n "${DEBUG+x}" ]; then echo "DEBUG is set"; else echo "4: DEBUG is unset"; fi
[[ -v NAME ]] || echo "5: -v で未定義を判定"
arr=()
echo "6: 空配列の要素数=${#arr[@]} 展開=<${arr[@]}>"
echo "7: $NAME"
echo "ここには来ない"
1: guest
2: 
3: <>
4: DEBUG is unset
5: -v で未定義を判定
6: 空配列の要素数=0 展開=<>
setu.sh: line 10: NAME: unbound variable

終了コードは 1 でした。7行目のように既定値なしで参照したところで止まり、最後の echo は実行されていません。

場面 書き方
省略できる引数を受け取る arg="${1:-}"
環境変数が無ければ既定値 "${NAME:-guest}"
無ければ空文字として扱う "${NAME-}"
定義されているかどうかだけ知りたい [ -n "${DEBUG+x}" ] または [[ -v DEBUG ]]
必須なので、無ければ分かりやすい文言で止める : "${DB_HOST:?DB_HOST を設定してください}"

${DEBUG+x} は「定義されていれば x、未定義なら空」になるので、空文字が入っている場合も「定義済み」と判定できる のが -n "$DEBUG" との違いです。

クォートしないと起きる単語分割

変数展開の結果は、ダブルクォートで囲まないと「空白での分割(単語分割)」と「glob の展開」が行われます。 引数の個数を表示する関数で確かめました。カレントディレクトリには a.txt と b.txt があります。

#!/usr/bin/env bash
file="my report.txt"; pat="*.txt"; blank=""
count() { echo "引数 $# 個: $(printf '<%s>' "$@")"; }

count $file
count "$file"
count $pat
count "$pat"
count $blank
count "$blank"
引数 2 個: <my><report.txt>
引数 1 個: <my report.txt>
引数 2 個: <a.txt><b.txt>
引数 1 個: <*.txt>
引数 0 個: <>
引数 1 個: <>
クォートなしで起きたこと 影響の例 直した書き方
空白で2つに分かれた rm $file が my と report.txt を対象にする "$file"
*.txt がファイル名に展開された 意図しないファイルが引数になる "$pat"
空文字が引数ごと消えた [ $blank = "x" ] が構文エラーになる "$blank"
連続した空白が1つになった echo $v で a b が a b と表示される echo "$v"

3つ目は、実際に次のエラーになります。

$ [ $blank = "x" ]
bash: [: =: unary operator expected
$ [ "$blank" = "x" ]; echo $?
1

既定値に空白が含まれる場合も同じ です。${ } を使っていても、外側のダブルクォートは省略できません。

dir="${TARGET:-/tmp/my demo}"
count ${dir}      # 引数 2 個: </tmp/my><demo>
count "${dir}"    # 引数 1 個: </tmp/my demo>
クォートが必要な場所 クォートが不要な場所
コマンドの引数(cp "$src" "$dst") 代入の右辺(a=$b、a=${b:-x})
[ ] の中([ "$a" = "$b" ]) [[ ]] の左辺([[ $a == x ]])
for の一覧(for f in "${arr[@]}") (( )) の中((( n > 0 )))
値があるときだけ足す引数は、意図的に外す(${verbose:+--verbose}) case $v in

迷ったらダブルクォートで囲む、が基本 です。条件式の書き分けは、既存記事「シェルスクリプトif条件式一覧」に早見表があります。

よくある質問

Q. ${var:-default} と ${var-default} はどちらを使えばよいですか? A. 多くの場合は、コロンありの ${var:-default} が合います。未定義のときも空文字のときも既定値になるためです。「空文字を、利用者が明示的に指定した値として尊重したい」場合だけ、コロンなしの ${var-default} を選びます。

Q. ${var:-default} を使うと、変数 var の値も変わりますか? A. 変わりません。その場で既定値が使われるだけです。変数自体にも既定値を入れたいときは ${var:=default} を使います。この記事の検証でも、: "${u:=init}" のあとに u が init になることを確認しました。

Q. Bad substitution と表示されます。原因は何ですか? A. bash 拡張の記法(切り出し ${v:0:5}、置換 ${v/a/b}、${v^^} など)を、sh(dash など)で実行している場合に表示されます。スクリプトの1行目を #!/usr/bin/env bash にして、sh script.sh ではなく bash script.sh で実行してください。sh で動かす必要があるときは、この記事の代わりの書き方の表を参考にしてください。

Q. 変数展開の中で正規表現は使えますか? A. 使えません。#、%、/ のパターンは glob(*、?、[...])です。正規表現が必要なら、[[ $s =~ 正規表現 ]] と BASH_REMATCH、または sed を使います。

まとめ

  • 既定値系は :-(使う)、:=(代入もする)、:?(無ければ止める)、:+(あるときだけ使う) の4つ。コロンなしは「未定義」だけを対象にする
  • 切り出しは ${v:位置:長さ}。末尾からは ${v: -3} と空白を入れる(${v:-3} は既定値の記法になる)
  • # は先頭、% は末尾を削除し、記号2つで最長一致。${path##*/} でファイル名、${path%/*} でディレクトリ、${path##*.} で拡張子
  • 置換は / が1か所、// がすべて、/# が先頭、/% が末尾。パターンは glob で、正規表現ではない
  • ${v^^} ${v,,} で大文字小文字、${!key} で間接参照、${v@Q} で安全なクォート
  • 既定値系・長さ・# % は sh でも動く。切り出し・置換・大文字小文字・配列は bash 拡張で、sh では Bad substitution になる
  • set -u のもとでは ${1:-} や ${VAR+x} で未定義に備え、展開結果は必ずダブルクォートで囲む

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「bash変数展開の一覧|${var:-default}と文字列の置換・切り出し」

株式会社RJCでは、bashの変数展開や文字列操作の書き方を仲間と学び合っています。こうした知識を共有しながら、日々技術を磨いています。

株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。

WE ARE HIRING

RJCで一緒に開発しながら、
成長を楽しみませんか?

RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!

  • 127日年間休日
  • 12時間平均残業時間
  • 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!

ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。

ABOUT RJC RJCがどんな会社か知る 考え方、研修、働き方、福利厚生、社員の前職まで。RJCのことが丸わかり! RJC丸わかりページへ JOB DESCRIPTION 仕事内容・待遇を見てみる 仕事内容、給与・待遇、選考の流れ。経験者も未経験も!応募前に知りたいこと、まとめました! 募集要項を見る ENTRY エントリーする エントリーは1〜2分・履歴書不要です。まずは話を聞いてみたい、という方でも歓迎です! エントリーフォームへ

RJCで一緒に開発しながら、 成長を楽しみませんか?