Bashパラメータ展開チートシート

# はじめに
Bashのパラメータ展開が複雑で、毎度manを引いているので、具体的なコマンド例を付けて整理しました。

本稿執筆時点の最新リリース版 Bash 5.2まで対応しています。新しいリリースがあれば、随時アップデートしていく予定です。

## 移植性情報

各展開機能別に、それぞれ導入されたBashバージョンも記載しています。各環境間の移植性のご参考にしてください。例えばmacOSデフォルトのBashは3.2、広く使われているRHEL7系 (Amazon Linux 2含む) は4.2とそれなりに古く、一部の機能が使えません。また、「POSIX定義」列には、[POSIX.1-2008](https://pubs.opengroup.org/onlinepubs/9699919799.2008edition/utilities/V3_chap02.html#tag_18_06_02)に定義が有れば「○」、無ければ「×」を記載しています。[dash](https://manpages.debian.org/buster/dash/dash.1.en.html) など他のシェルとの移植性の参考にしてください。

機能や動作仕様については、manページや公式リファレンスに勝るものはありませんが、バージョン間の差異はmanページからは得られないので、お役に立てば幸いです。

# デフォルト値展開

| 用途                        | 構文　　　　　　　 | 評価/実行例<br>`param="param"`　　<br>の場合 | 評価/実行例<br>`param=""`<br>の場合 | 評価/実行例<br>`unset param`<br>の場合 | 
|---------------------------|-----------------|-----------------------------|------------------------|---------------------------|
| デフォルト値展開                  | ${param:-right} | param                       | right                  | right                     | 1.x            | ○            |
| デフォルト値展開                  | ${param-right}  | param                       | ""<br>(空文字列)                     | right                     | 1.x            | ○            |
| デフォルト値代入<br>※1                  | ${param:=right} | (代入なし)                    | param=right            | param=right               | 1.x            | ○            |
| デフォルト値代入<br>※1                  | ${param=right}  | (代入なし)                    | (代入なし)               | param=right               | 1.x            | ○            |
| 未定義エラー<br>※2                    | ${param:?msg}   | param                       | msgを出力してexit 1      | msgを出力してexit 1         | 1.x            | ○            |
| 未定義エラー<br>※2                    | ${param?msg}    | param                       | ""                     | msgを出力してexit 1         | 1.x            | ○            |
| 定義済み値<br>置き換え<br>(`:-`の逆※3)             | ${param:+right} | right                       | ""                     | ""                        | 1.x            | ○            |
| 定義済み値<br>置き換え<br>(`-`の逆※3) | ${param+right}  | right                       | right                  | ""                        | 1.x            | ○            |

上記全て Bash 1.xで導入済み、かつPOSIX定義有り。

(余談ですが、Qiitaでは多くの列を表に含めると[表レイアウトが崩れ](https://github.com/increments/qiita-discussions/discussions/512#discussioncomment-7962483)て見づらくなったので、このセクションだけはBash導入バージョン列と、POSIX定義列を表から独立させました)。

:::note info
※1
デフォルト値代入式は、ただの代入と異なり、`$param`の定義済み値または代入値に評価される。したがってデフォルト値代入式だけをコマンド行に記載した場合、シェルはその評価結果をコマンドとして実行しようとし、(それを期待しない場合は) command not found エラーを引き起こす。代入だけを行いたい場合は、コロンコマンド (`:`) でラップする等でエラーは回避できる。
[コロンコマンド](https://www.gnu.org/software/bash/manual/bash.html#index-_003a) は正常終了するだけのコマンドである。シェル組み込みコマンドでありプロセスを起動しないので実行時オーバーヘッドは小さい。
ただ、初見では謎めいた記述に見えるので、バッドノウハウ感は否めない。
```bash
$ unset param
$ ${param:=right}  # 代入結果の文字列がコマンドとして実行されエラーを引き起こす
bash: right: command not found
$ echo $param　 # 代入自体は有効
right
$
$ unset param
$ : ${param:=right}  # :コマンドの引数として扱えばエラーは起きない
$ echo $param
right
```
:::

:::note info
※2
未定義エラー展開は、対話シェルではexitせずに、メッセージが出力されるのみ。
対話シェルか否かによらず出力先は標準エラー出力である。
:::

:::note info
※3
`:+`、`+`は直感的にわかりづらくユースケースも少ないだろうが、パラメータに値が設定済みの場合に**それを無視**して右辺値で評価される。`:-`、`-` (パラメータが未設定の場合に右辺値が使用される) と逆の関係にあると考えれば表記は覚えやすいだろう。
:::


# 文字列操作


## 部分削除

```bash:前提
# 明示が無い場合、下表の実行例では以下の変数が定義されているものとする
param="./path/to/foo"
```

| 用途          | 構文                | 実行例                                    | 導入<br>version | POSIX<br>定義 |
|-------------|-------------------|--------------------------------------|:-------:|:-:|
| 末尾削除<br>(最短一致) | ${param%pattern}  | echo ${param%/*}<br/>#-> ./path/to   | 1.x     | ○ |
| 末尾削除<br>(最長一致) | ${param%%pattern} | echo ${param%%/*}<br/>#-> .          | 1.x     | ○ |
| 先頭削除<br>(最短一致) | ${param#pattern}  | echo ${param#*/}<br/>#-> path/to/foo | 1.x     | ○ |
| 先頭削除<br>(最長一致) | ${param##pattern} | echo ${param##*/}<br/>#-> foo        | 1.x     | ○ |

:::note info
パターンは正規表現ではなく、パス名展開相当である点に留意。
:::


## 部分文字列取得

```bash:前提
# 明示が無い場合、下表の実行例では以下の変数が定義されているものとする
param="0123456789"
```

| 用途                   | 構文                        | 実行例                               | 導入<br>version | POSIX<br>定義 |
|----------------------|---------------------------|---------------------------------|:-------:|:-:|
| 先頭オフセット              | ${param:offset}           | echo ${param:5}<br/>#-> 5678    | 2.0     | × |
| 末尾オフセット<br>(下注参照)              | ${param: -offset}         | echo ${param: -3}<br/>#-> 789   | 2.0     | × |
| 先頭オフセット<br/>+文字列長    | ${param:offset:length}    | echo ${param:5:2}<br/>#-> 56    | 2.0     | × |
| 末尾オフセット<br/>+文字列長<br>(下注参照)    | ${param: -offset:length}  | echo ${param: -3:2}<br/>#-> 78  | 2.0     | × |
| 先頭オフセット<br/>+末尾オフセット | ${param:offset:-length}   | echo ${param:5:-1}<br/>#-> 5678 | 4.2     | × |
| 末尾オフセット<br/>+末尾オフセット | ${param: -offset:-length} | echo ${param: -3:-1}<br/>#-> 78 | 4.2     | × |

:::note warn
【注】
負の `offset` を指定する場合、文法上`:`と`-offset`の間をスペースで区切る必要がある。スペースが無い場合、先に紹介したデフォルト値展開の `${param:-right}` と解釈されてしまう。
```bash
$ param=0123456789
$ echo ${param:-3} # 期待通り動作しない
0123456789
$ echo ${param: -3}
789
```
:::

:::note info
`offset` が 0 の場合、省略可能。
```bash
$ echo ${param:0:5}
01234
$ echo ${param::5}
01234
```
:::

:::note info
負の `length` は、文字列長を意味せず、終端位置 (exclusive) を示す末尾からのオフセットを意味する。例えば、`prama=0123456789; ${param::-1}`とした場合、文末から1文字目を含まない(exclusive) 形で終端し、`012345678`に展開される。
:::

## 大文字/小文字変換

```bash:前提
# 明示が無い場合、下表の実行例では以下の変数が定義されているものとする
param="param"
PARAM="PARAM"
```

| 用途                   | 構文                        | 実行例                               | 導入<br>version | POSIX<br>定義 |
|-----------------------|-------------------|---------------------------------------------------|:---:|:-:|
| 先頭大文字化                | ${param^}         | ${param^}<br/>#-> Param                           | 4.0 | × |
| 先頭大文字化<br/>(条件パターン指定) | ${param^pattern}  | ${param^[am]}<br/>#-> param                       | 4.0 | × |
| 全大文字化                 | ${param^^}        | ${param^^}<br/>#-> PARAM                          | 4.0 | × |
| 全大文字化<br/>(条件パターン指定)  | ${param^^pattern} | ${param^^[am]}<br/>#-> pArAM                      | 4.0 | × |
| 先頭小文字化                | ${param,}         | echo ${PARAM,}<br/>#-> pARAM      | 4.0 | × |
| 先頭小文字化<br/>(条件パターン指定) | ${param,pattern}  | echo ${PARAM,[am]}<br/>#-> PARAM  | 4.0 | × |
| 全小文字化                 | ${param,,}        | echo ${PARAM,,}<br/>#-> param     | 4.0 | × |
| 全小文字化<br/>(条件パターン指定)  | ${param,,pattern} | echo ${PARAM,,[AM]}<br/>#-> PaRam | 4.0 | × |
| 全大文字化<br>(`${param^^}`と同じ)              | ${param@U} | echo ${param@U}<br/>#-> PARAM                                                     | 5.1     | × |
| 先頭大文字化<br>(`${param^}`と同じ)             | ${param@u} | echo ${param@u}<br/>#-> Param                                                     | 5.1     | × |
| 全小文字化<br>(`${param,,}`と同じ)              | ${param@L} | echo ${PARAM@L}<br/>#-> param                                                     | 5.1     | × |

:::note info
`pattern` は正規表現ではなく、パス名展開相当である点に留意。
:::

## 置換

```bash:前提
# 明示が無い場合、下表の実行例では以下の変数が定義されているものとする
param=abc-abc
```

| 用途      | 構文                       | 実行例                                      | 導入<br>version | POSIX<br>定義 |
|---------|--------------------------|----------------------------------------|:-------:|:-:|
| 置換      | ${param/pattern/string}  | echo ${param/abc/x}<br/>#-> x-abc  | 2.0     | × |
| 置換 先頭一致 | ${param/#pattern/string} | echo ${param/#abc/x}<br/>#-> x-abc | 2.0     | × |
| 置換 末尾一致 | ${param/%pattern/string} | echo ${param/%abc/x}<br/>#-> abc-x | 2.0     | × |
| 全置換     | ${param//pattern/string} | echo ${param//abc/x}<br/>#-> x-x   | 2.0     | × |

:::note info
`pattern` は正規表現ではなく、パス名展開相当である点に留意。
`pattern`内にリテラルとして `/` `#` `%` 自体を含めたい場合は、`\%` のようにエスケープする。
```bash
$ param=abc-%abc-abc
$ echo ${param/\%abc/*ABC}
abc-*ABC-abc
```
:::

## 文字列操作 その他

```bash:前提
# 明示が無い場合、下表の実行例では以下の変数が定義されているものとする
param="param"
```

| 用途          | 構文                | 実行例                                    | 導入<br>version | POSIX<br>定義 |
|-------------|-------------------|--------------------------------------|:-------:|:-:|
| 文字列長取得 | ${#param}  | echo ${#param}<br/>#-> 5   | 1.x     | ○ |
| C エスケープ展開<br>(`$'...'`相当)         | ${param@E} | param="\u3042\na"<br/>echo "${param@E}"<br/>#-> あ<br/>a | 4.4     | × |
| プロンプト文字列展開<br>(`PS1`と同規則の展開)         | ${param@P} | param='\A'<br/>echo ${param@P}<br/>#-> 15:42                                      | 4.4     | × |
| 引用符付与              | ${param@Q} | echo ${param@Q}<br/>#-> 'param'                                                   | 4.4     | × |

# 変数操作

```bash:前提
# 明示が無い場合、下表の実行例では以下の変数が定義されているものとする
param="param"
declare -A map=([k0]="v0" [k1]="v1") # 連想配列変数 map を定義
```

| 用途                 | 構文         | 実行例                                                                                 | 導入<br>version | POSIX<br>定義 |
|--------------------|------------|-----------------------------------------------------------------------------------|:-------:|:-:|
| 配列キー列挙    | `${!map[*]}`<br/>`${!map[@]} `| echo ${!map[*]}<br/>#-> k0 k1 | 3.0            | ×            |
| 配列分解<br/>(値に引用符付き) | ${param@K} | echo ${map[*]@K}<br/>#-> k0 "v0" k1 "v1" | 5.1     | × |
| 配列分解<br/>(値に引用符なし) | ${param@k} | echo ${map[*]@k}<br/>#-> k0 v0 k1 v1         | 5.2     | × |
| 変数名列挙<br>(接頭辞一致) | `${!prefix*}`<br/>`${!prefix@}`   | echo ${!HO*}<br/>#-> HOME<br>HOSTNAME<br>HOSTTYPE                        | 2.02           | ×            |
| 変数属性取得            | ${param@a} | declare -Ar param<br/>echo ${param@a}<br/>#-> Ar                                  | 4.4     | × |
| 変数定義文取得            | ${param@A} | echo ${param@A}<br/>#-> param='param'                                               | 4.4     | × |

# 参考資料

* [bash(1) manpage](https://www.man7.org/linux/man-pages//man1/bash.1.html)
* [Bash Reference Manual](https://www.gnu.org/software/bash/manual/bash.html)
* [POSIX.1-2008](https://pubs.opengroup.org/onlinepubs/9699919799.2008edition/)
* [Bashソースパッケージ](https://ftp.gnu.org/gnu/bash/)