[シェル] 位置パラメータを配列のように扱う

参考「[3.4.1 Positional Parameters - Bash Reference Manual](https://www.gnu.org/software/bash/manual/bash.html#Positional-Parameters)」
参考「[3.4.2 Special Parameters - Bash Reference Manual](https://www.gnu.org/software/bash/manual/bash.html#Special-Parameters)」
参考「[3.5.3 Shell Parameter Expansion - Bash Reference Manual](https://www.gnu.org/software/bash/manual/bash.html#Shell-Parameter-Expansion)」

# 1. 取得

参考「[[シェル] 特殊パラメータ @ のパラメータ展開 - Qiita](https://qiita.com/kerupani129/items/1f365b210961a44d7dfd)」

## 1.1. 全て

特殊パラメータを用いて全ての位置パラメータを取得することが出来ます。

|コード|意味|
|-|-|
|`$#`|位置パラメータの数|
|`"$@"`|全ての位置パラメータをそれぞれ展開|
|`"$*"`|全ての位置パラメータを展開して連結|

ちなみに、パラメータ展開 `"${parameter@Q}"` を用いて文字列をエスケープすることが出来ます。

```bash:例
# 
function length() {
	echo "$#"
}

# 
function f() {

	echo "$#"

	echo "$@"
	length "$@"
	echo "${@@Q}"
	length "${@@Q}"

	echo "$*"
	length "$*"
	echo "${*@Q}"
	length "${*@Q}"

}

f foo bar baz
```

```text:例の実行結果
3
foo bar baz
3
'foo' 'bar' 'baz'
3
foo bar baz
1
'foo' 'bar' 'baz'
1
```

## 1.2. 単独

位置が定数の場合、`"$1"` や `"${12}"` のようにして取得します。

他に間接展開や部分列を用いて単独の位置パラメータを取得する方法があります。

|コード|意味|
|-|-|
|`"$1"`|最初の位置パラメータ|
|`"${!offset}"`|`$offset` 番目の位置パラメータ|
|`"${@:$#}"`<br>`"${@: -1}"`|最後の位置パラメータ|

```bash:例
# 
function f() {

	echo "$1"
	# echo "$12" # 間違い
	echo "${12}"

}

f \'p1\' \'p2\' \'p3\' \'p4\' \'p5\' \'p6\' \'p7\' \'p8\' \'p9\' \'p10\' \'p11\' \'p12\'

# 
function g() {

	local -ri offset=2
	echo "${!offset}"

}

g foo bar baz

# 
function h() {

	echo "${@:$#}"
	echo "${@: -1}"

	local -ri offset_last=$#
	echo "${!offset_last}"

}

h foo bar baz
```

```text:例の実行結果
'p1'
'p12'
bar
baz
baz
baz
```

間接展開 `"${!#}"` に関しては、Bash で POSIX モードを無効化している場合に使用可能です。

```bash:例
set -o posix

# 
function f() {

	# echo "${!#}" # エラー発生

	set +o posix
	echo "${!#}"
	set -o posix

}

f foo bar baz
```

```text:例の実行結果
baz
```

## 1.3. 部分列

```bash:使い方
"${@:offset}"
"${@:offset:length}"
```

特殊パラメータ `@` の場合、`offset` に負の数を指定することは出来ますが、`length` に負の数を指定することは出来ません。

部分列の終端を末尾からのオフセットで指定したい場合は、特殊パラメータ `$#` を用いて計算します。

```bash:例
# 
function f() {

	echo "${@:2}"

	echo "${@: -3}"
	# echo "${@:-3}" # 間違い

	local -ri offset=2
	echo "${@:offset}"

}

f foo bar baz qux

# 
function g() {

	echo "${@:2:2}"

	echo "${@: -3:2}"
	# echo "${@:-3:2}" # 間違い

	echo "${@:2:$#-2}"
	# echo "${@:2:-1}" # エラー発生

	local -ri offset=2
	local -ri length=2
	echo "${@:offset:length}"

}

g foo bar baz qux
```

```text:例の実行結果
bar baz qux
bar baz qux
bar baz qux
bar baz
bar baz
bar baz
bar baz
```

他に以下のような部分列の使い方があります。

|部分列を含むコード|意味|
|-|-|
|`"${@:1}"`|`$@`|
|`"$1" "${@:2}"`|最初の位置パラメータを分割|
|`"${@:1:$#-1}" "${@:$#}"`<br>`"${@:1:$#-1}" "${@: -1}"`|最後の位置パラメータを分割|

```bash:例
# 
function f() {

	echo "${@:1}"
	echo "$@"

	echo "$1" '|' "${@:2}"

	echo "${@:1:$#-1}" '|' "${@:$#}"
	echo "${@:1:$#-1}" '|' "${@: -1}"

}

f foo bar baz
```

```text:例の実行結果
foo bar baz
foo bar baz
foo | bar baz
foo bar | baz
foo bar | baz
```

## 1.4. ループ

ループを用いて位置パラメータをそれぞれ取得することが出来ます。

参考「[3.2.5.1 Looping Constructs - Bash Reference Manual](https://www.gnu.org/software/bash/manual/bash.html#Looping-Constructs)」

```bash:例
# 
function f() {

	for p; do
		echo -n "$p "
	done
	echo

	local -i i
	for (( i = 1; i <= $#; i++ )); do
		echo -n "${!i} "
	done
	unset i
	echo

}

f foo bar baz

# 
function g() {

	local -i i=1
	while test $i -le $#; do
		echo -n "${!i} "
		let i++
	done
	unset i
	echo

	local -i i=1
	while [ $i -le $# ]; do
		echo -n "${!i} "
		let i++
	done
	unset i
	echo

	local -i i=1
	while [[ i -le $# ]]; do
		echo -n "${!i} "
		let i++
	done
	unset i
	echo

	local -i i=1
	while (( i <= $# )); do
		echo -n "${!i} "
		let i++
	done
	unset i
	echo

}

g foo bar baz
```

```text:例の実行結果
foo bar baz
foo bar baz
foo bar baz
foo bar baz
foo bar baz
foo bar baz
```

# 2. 設定

通常の代入文では位置パラメータの値を変更できませんが、`set` コマンドを使用することで全ての位置パラメータを置き換えることができます。

参考「[set - Bash Reference Manual](https://www.gnu.org/software/bash/manual/bash.html#index-set)」

```bash:例
set -- foo bar baz
echo "$@"

set -- FOO BAR BAZ
echo "$@"
```

```text:例の実行結果
foo bar baz
FOO BAR BAZ
```

# 3. 追加

`set` コマンドで `"$@"` を使用することで、前後に位置パラメータを追加することが出来ます。

## 3.1. 先頭

```bash:例
set -- foo bar
echo "$@"
set -- baz "$@"
echo "$@"

set -- foo bar
echo "$@"
set -- baz qux "$@"
echo "$@"
```

```text:例の実行結果
foo bar
baz foo bar
foo bar
baz qux foo bar
```

## 3.2. 末尾

```bash:例
set -- foo bar
echo "$@"
set -- "$@" baz
echo "$@"

set -- foo bar
echo "$@"
set -- "$@" baz qux
echo "$@"
```

```text:例の実行結果
foo bar
foo bar baz
foo bar
foo bar baz qux
```

# 4. 削除

## 4.1. 先頭

先頭の位置パラメータを削除する場合、`shift` コマンドを使用します。

参考「[shift - Bash Reference Manual](https://www.gnu.org/software/bash/manual/bash.html#index-shift)」

```bash:例
set -- foo bar baz qux
echo "$@"
shift
echo "$@"

set -- foo bar baz qux
echo "$@"
shift 2
echo "$@"
```

```text:例の実行結果
foo bar baz qux
bar baz qux
foo bar baz qux
baz qux
```

## 4.2. 末尾

末尾の位置パラメータを削除する場合、部分列を取得し `set` コマンドで全ての位置パラメータを置き換えます。

```bash:例
set -- foo bar baz qux
echo "$@"
set -- "${@:1:$#-1}"
echo "$@"

set -- foo bar baz qux
echo "$@"
set -- "${@:1:$#-2}"
echo "$@"
```

```text:例の実行結果
foo bar baz qux
foo bar baz
foo bar baz qux
foo bar
```

## 4.3. ループ

ループを用いて位置パラメータをそれぞれ取得しながら削除することが出来ます。

参考「[3.2.5.1 Looping Constructs - Bash Reference Manual](https://www.gnu.org/software/bash/manual/bash.html#Looping-Constructs)」

```bash:例
set -- foo bar baz
while test $# -gt 0; do
	echo -n "$1 "
	shift
done
echo

set -- foo bar baz
while [ $# -gt 0 ]; do
	echo -n "$1 "
	shift
done
echo

set -- foo bar baz
while [[ $# -gt 0 ]]; do
	echo -n "$1 "
	shift
done
echo

set -- foo bar baz
while (( $# > 0 )); do
	echo -n "$1 "
	shift
done
echo
```

```text:例の実行結果
foo bar baz
foo bar baz
foo bar baz
foo bar baz
```

# 5. 変換

## 5.1. 部分列

部分列を取得し `set` コマンドで全ての位置パラメータを置き換えることで、部分列の位置パラメータを変換することが出来ます。

```bash:例
# 
function command_qux() {
	echo 'qux'
}

# 
set -- foo bar baz
echo "$@"
set -- "$(command_qux "$1")" "${@:2}"
echo "$@"

set -- foo bar baz
echo "$@"
set -- "${@:1:$#-1}" "$(command_qux "${@:$#}")"
echo "$@"

set -- foo bar baz
echo "$@"
set -- "${@:1:$#-1}" "$(command_qux "${@: -1}")"
echo "$@"
```

```text:例の実行結果
foo bar baz
qux bar baz
foo bar baz
foo bar qux
foo bar baz
foo bar qux
```

## 5.2. 全て

一部のパラメータ展開は特殊パラメータ `@` に対して使うと各位置パラメータに適用されます。

参考「[[シェル] 特殊パラメータ @ のパラメータ展開 - Qiita](https://qiita.com/kerupani129/items/1f365b210961a44d7dfd)」

```bash:例
set -- 'foo bar baz' 'foo bar baz' 'foo bar baz'
echo "${@#* }"
echo "${@##* }"
echo "${@% *}"
echo "${@%% *}"

set -- 'foo bar baz' 'foo bar baz' 'foo bar baz'
echo "${@/ /_}"
echo "${@// /_}"
echo "${@/#foo/***}"
echo "${@/%baz/***}"

set -- foo bar baz
echo "${@^}"
echo "${@^^}"
echo "${@^f}"
echo "${@^^a}"

set -- FOO BAR BAZ
echo "${@,}"
echo "${@,,}"
echo "${@,F}"
echo "${@,,A}"

set -- foo '\' \'
echo "${@@Q}"
```

```text:例の実行結果
bar baz bar baz bar baz
baz baz baz
foo bar foo bar foo bar
foo foo foo
foo_bar baz foo_bar baz foo_bar baz
foo_bar_baz foo_bar_baz foo_bar_baz
*** bar baz *** bar baz *** bar baz
foo bar *** foo bar *** foo bar ***
Foo Bar Baz
FOO BAR BAZ
Foo bar baz
foo bAr bAz
fOO bAR bAZ
foo bar baz
fOO BAR BAZ
FOO BaR BaZ
'foo' '\' \'
```