1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

[Bash] 互換モード

Last updated at Posted at 2024-08-31

シェル変数 BASH_COMPAT で互換モードのバージョンを指定可能です。

※互換モードのシェルオプションは廃止予定です。

バージョン シェルオプション
5.1 (なし)
5.0 (なし)
4.4 compat44
4.3 compat43
4.2 compat42
4.1 compat41
4.0 compat40
3.2 compat32
3.1 compat31

参考「6.12 Shell Compatibility Mode - Bash Reference Manual

まとめ

互換性レベル:

  • バージョン 5.1
    • unset 'array[@]'unset array と同じ意味にする
    • 算術式内の展開を複数回行う
      • 算術コマンド (( expression ))
      • 算術 forfor (( expr1 ; expr2 ; expr3 ))
      • 条件コマンド [[ expression ]] の算術演算子の引数
      • パラメータ展開の部分列 ${parameter:offset} および ${parameter:offset:length}
      • 算術式展開 $(( expression ))
      • インデックス配列の添字
    • 条件コマンド [[ expression ]] の条件式 -v array[subscript] のインデックス配列および連想配列 array の添字 subscript の展開を複数回行う
      • ※ Bash Reference Manual に書かれていないが、COMPAT ファイルに書かれている
    • 条件式 -v 'assoc_array[@]'-v 'indexed_array[@]' と同じ意味にする
    • パラメータ展開のデフォルト値代入 ${parameter=value} および ${parameter:=value} で代入時に変換前の値を返す
    • コマンド置換 $(command) は解析時に拡張パターンマッチングが有効の扱いにする
  • バージョン 5.0
    • シェル変数 RANDOM の乱数の生成方法を変更する
    • コマンドハッシュテーブルが空の場合、hash -l の出力を引数なしの hash の出力と同じにする
      • POSIX モードが無効の場合
      • hash -l の出力形式に関する POSIX モードの影響は (おそらく) 文書化されていないため注意
    • ヒアドキュメントおよびヒアストリングで一時ファイルの使用を強制する
      • ※ Bash Reference Manual に書かれていないが、COMPAT ファイルに書かれている
  • バージョン 4.4
    • シェル変数 BASH_ARGV および BASH_ARGC を初期化するタイミングを変更する
    • ループ内のサブシェルにおける break または continue コマンドはサブシェルを終了する
    • 関数内の export および readonly コマンドの前にある変数代入は、関数呼び出しの前に変数代入がある場合にシェル環境に影響する
      • ※ POSIX モードが無効の場合
  • バージョン 4.3
    • 配列の複合代入が引用符で囲まれている場合に警告を表示しない
      • declare name[subscript]='(value1 value2 ... )'
    • パラメータ展開における一部の "bad substitution" エラーは致命的でないエラーとみなす
      • ※ POSIX モードが有効の場合
    • 関数内の break および continue コマンドは、関数呼び出しがあるループに影響する
  • バージョン 4.2
    • 二重引用符で囲まれたパターン置換 ${parameter/pattern/string} における置換文字列 string は引用符の削除を行わない
    • 二重引用符で囲まれたパラメータ展開 ${...} 内の文字列において、一重引用符で囲まれた範囲を異なる文脈で展開する
      • ※ POSIX モードが有効の場合
  • バージョン 4.1
    • オプションが与えられている time はディスクコマンドでなく予約語とみなす
      • ※ POSIX モードが有効の場合
    • 二重引用符で囲まれたパラメータ展開 ${...} 内の文字列において、解析時に一重引用符の対応の確認を行う
      • ※ POSIX モードが有効の場合
  • バージョン 4.0
    • 条件コマンド [[ expression ]]< および > 演算子はロケールを考慮せず ASCII 順序で文字列比較する
  • バージョン 3.2
    • 対話モードでサブシェルでないコマンドリスト内のコマンドをシグナル SIGINT で終了したとき、コマンドリスト全体の実行を中断しない
      • a; b; c
  • バージョン 3.1
    • 条件コマンド [[ expression ]]=~ 演算子の右辺はクォートされても効果なしにする

参考「6.12 Shell Compatibility Mode - Bash Reference Manual
参考「COMPAT - bash.git - bash

バージョン 5.1

1. unset 'array[@]'

違い:

  • バージョン 5.2 以降
    • unset 'assoc_array[@]' は連想配列 assoc_array の要素 assoc_array[@]unset する
      • unset assoc_array しない
    • unset 'indexed_array[@]' はインデックス配列 indexed_array の要素を全て unset する
      • unset indexed_array しない
      • indexed_array=() と同じ意味
  • バージョン 5.1 以前
    • unset 'array[@]'unset array と同じ意味

※コマンドの引数に添字をともなう配列変数を指定する場合、パス名展開されないよう注意する必要があります。

1.1. 連想配列

# 
function f() {

	local -A assoc_array=([@]=foo [x]=bar)

	unset 'assoc_array[@]'
	declare -p assoc_array

}

BASH_COMPAT=5.2
f
BASH_COMPAT=5.1
f

# 
function g() {

	local -A assoc_array=([@]=foo [x]=bar)

	unset assoc_array
	declare -p assoc_array

}

g
実行結果
declare -A assoc_array=([x]="bar" )
declare -- assoc_array
declare -- assoc_array

1.2. インデックス配列

# 
function f() {

	local -a indexed_array=(foo bar baz)

	unset 'indexed_array[@]'
	declare -p indexed_array

}

BASH_COMPAT=5.2
f
BASH_COMPAT=5.1
f

# 
function g() {

	local -a indexed_array=(foo bar baz)

	indexed_array=()
	declare -p indexed_array

	unset indexed_array
	declare -p indexed_array

}

g
実行結果
declare -a indexed_array=()
declare -- indexed_array
declare -a indexed_array=()
declare -- indexed_array

2. 算術式内の展開

違い:

  • バージョン 5.2 以降
    • 算術式内の展開は 1 回のみ行う
  • バージョン 5.1 以前
    • 算術式内の展開は複数回行う

連想配列の添字はシェルオプション assoc_expand_once の影響を受ける場合があります。

シェルオプション assoc_expand_once については別記事にしました。

参考「[Bash] シェルオプション assoc_expand_once - Qiita

※本記事ではパラメータ展開の例を記載しますが、コマンド置換等の他の展開も同様の扱いになります。
※本記事では連想配列の添字の例を記載しますが、連想配列の添字以外の展開も同様の扱いになります。

2.1. 算術コマンド (( expression ))

# 
function f() {

	local -rAi assoc_array=([x]=1 ['$x']=0)
	local -r x='x'

	local -r subscript='$x'
	(( assoc_array[$subscript] )) && echo 'OK' || echo 'NG'

}

shopt -u assoc_expand_once
BASH_COMPAT=5.2
f
BASH_COMPAT=5.1
f
実行結果
NG
OK

2.2. 算術 forfor (( expr1 ; expr2 ; expr3 ))

# 
function f() {

	local -rAi assoc_array=([x]=23 ['$x']=42)
	local -r x='x'

	local -r subscript='$x'
	local -i i
	for (( i = assoc_array[$subscript]; i < 1 + assoc_array[$subscript]; i++ )); do
		echo "$i"
	done
	unset i

}

shopt -u assoc_expand_once
BASH_COMPAT=5.2
f
BASH_COMPAT=5.1
f
実行結果
42
23

2.3. 条件コマンド [[ expression ]] の算術演算子の引数

条件式の算術演算子 算術式の演算子 意味
-eq == 等値
-ne != 非等値
-lt < 小なり
-le <= 小なりイコール
-gt > 大なり
-ge >= 大なりイコール
# 
function f() {

	local -rAi assoc_array=([x]=1 ['$x']=0)
	local -r x='x'

	local -r subscript='$x'
	[[ assoc_array[$subscript] -ne 0 ]] && echo 'OK' || echo 'NG'

}

shopt -u assoc_expand_once
BASH_COMPAT=5.2
f
BASH_COMPAT=5.1
f
実行結果
NG
OK

2.4. パラメータ展開の部分列 ${parameter:offset} および ${parameter:offset:length}

# 
function f() {

	local -r parameter=foo

	local -rAi assoc_array=([x]=0 ['$x']=1)
	local -r x='x'

	local -r subscript='$x'
	echo "${parameter:assoc_array[$subscript]}"

}

shopt -u assoc_expand_once
BASH_COMPAT=5.2
f
BASH_COMPAT=5.1
f

# 
function g() {

	local -r parameter=foo

	local -rAi assoc_array=([x]=3 ['$x']=2)
	local -r x='x'

	local -r subscript='$x'
	echo "${parameter:0:assoc_array[$subscript]}"

}

shopt -u assoc_expand_once
BASH_COMPAT=5.2
g
BASH_COMPAT=5.1
g
実行結果
oo
foo
fo
foo

2.5. 算術式展開 $(( expression ))

# 
function f() {

	local -rAi assoc_array=([x]=23 ['$x']=42)
	local -r x='x'

	local -r subscript='$x'
	echo "$(( assoc_array[$subscript] ))"

}

shopt -u assoc_expand_once
BASH_COMPAT=5.2
f
BASH_COMPAT=5.1
f
実行結果
42
23

2.6. インデックス配列の添字

# 
function f() {

	local -a indexed_array=(foo bar)

	local -rAi assoc_array=([x]=0 ['$x']=1)
	local -r x='x'

	local -r subscript='$x'
	indexed_array[assoc_array[$subscript]]=baz

	echo "${indexed_array[@]}"

}

shopt -u assoc_expand_once
BASH_COMPAT=5.2
f
BASH_COMPAT=5.1
f

# 
function g() {

	local -a indexed_array=(foo bar)

	local -rAi assoc_array=([x]=0 ['$x']=1)
	local -r x='x'

	local -r subscript='$x'
	echo "${indexed_array[assoc_array[$subscript]]}"

}

shopt -u assoc_expand_once
BASH_COMPAT=5.2
g
BASH_COMPAT=5.1
g
実行結果
foo baz
baz bar
bar
foo

3. 条件コマンド [[ expression ]] の条件式 -v array[subscript]subscript の展開

違い:

  • バージョン 5.2 以降
    • 配列の添字の展開は 1 回のみ行う
  • バージョン 5.1 以前
    • 配列の添字の展開は複数回行う

連想配列の添字はシェルオプション assoc_expand_once の影響を受ける場合があります。

シェルオプション assoc_expand_once については別記事にしました。

参考「[Bash] シェルオプション assoc_expand_once - Qiita

※本記事ではパラメータ展開の例を記載しますが、コマンド置換等の他の展開も同様の扱いになります。
※条件コマンド [[ expression ]] ではパス名展開は行われません。

3.1. インデックス配列

# 
function f() {

	local -ra indexed_array=(foo)
	local -r x='0'

	local -r subscript='$x'
	[[ -v indexed_array[$subscript] ]] && echo 'OK' || echo 'NG'

}

BASH_COMPAT=5.2
(f 2> /dev/null) || echo 'Error'
BASH_COMPAT=5.1
(f 2> /dev/null) || echo 'Error'
実行結果
Error
OK

3.2. 連想配列

# 
function f() {

	local -rA assoc_array=([x]=foo)
	local -r x='x'

	local -r subscript='$x'
	[[ -v assoc_array[$subscript] ]] && echo 'OK' || echo 'NG'

}

shopt -u assoc_expand_once
BASH_COMPAT=5.2
f
BASH_COMPAT=5.1
f
実行結果
NG
OK

4. 条件式 -v 'assoc_array[@]'

違い:

  • バージョン 5.2 以降
    • 条件式 -v 'assoc_array[@]' は連想配列の要素 assoc_array[@] に値が割り当てられているかを表す
  • バージョン 5.1 以前
    • 条件式 -v 'assoc_array[@]'-v 'indexed_array[@]' と同じ意味

※条件式 -v arrayarray が連想配列かインデックス配列かに関わらず条件式 -v 'array[0]' と同じ意味になります。
※コマンドの引数に添字をともなう配列変数を指定する場合、パス名展開されないよう注意する必要があります。
※条件コマンド [[ expression ]] ではパス名展開は行われません。

# 
function f() {

	local -rA assoc_array=([x]=foo)

	test -v 'assoc_array[@]' && echo 'OK' || echo 'NG'
	[ -v 'assoc_array[@]' ] && echo 'OK' || echo 'NG'
	[[ -v assoc_array[@] ]] && echo 'OK' || echo 'NG'

}

BASH_COMPAT=5.2
f
BASH_COMPAT=5.1
f

# 
function g() {

	local -ra indexed_array=(foo)

	test -v 'indexed_array[@]' && echo 'OK' || echo 'NG'
	[ -v 'indexed_array[@]' ] && echo 'OK' || echo 'NG'
	[[ -v indexed_array[@] ]] && echo 'OK' || echo 'NG'

}

g
実行結果
NG
NG
NG
OK
OK
OK
OK
OK
OK

ちなみにバージョン 5.2 以降では、連想配列が要素を持つかどうかを条件式 -v 'assoc_array[@]' で確認出来ないため、パラメータ展開 ${#assoc_array[@]} を用いて要素数で確認します。

# 
function f() {

	local -rA assoc_array=([x]=foo)

	echo "${#assoc_array[@]}"
	test "${#assoc_array[@]}" -gt 0 && echo 'OK' || echo 'NG'
	[ "${#assoc_array[@]}" -gt 0 ] && echo 'OK' || echo 'NG'
	[[ ${#assoc_array[@]} -gt 0 ]] && echo 'OK' || echo 'NG'

}

f
実行結果
1
OK
OK
OK

5. パラメータ展開のデフォルト値代入 ${parameter=value} および ${parameter:=value}

違い:

  • バージョン 5.2 以降
    • デフォルト値代入時に変換後の値を返す
    • 変換後の値が代入される
  • バージョン 5.1 以前
    • デフォルト値代入時に変換前の値を返す
    • 変換後の値が代入される

大文字属性または小文字属性が設定された変数に対して変数代入を用いて代入する場合、大文字小文字が変換されます。

# 
function f() {

	local -l x='FOO'
	echo "$x"

	local -u y='bar'
	echo "$y"

}

f
実行結果
foo
BAR
# 
function f() {

	local -l x
	echo "${x=FOO}"
	echo "$x"

	local -u y
	echo "${y=bar}"
	echo "$y"

}

BASH_COMPAT=5.2
f
BASH_COMPAT=5.1
f
実行結果
foo
foo
BAR
BAR
FOO
foo
bar
BAR
# 
function f() {

	local -l x=''
	echo "${x:=FOO}"
	echo "$x"

	local -u y=''
	echo "${y:=bar}"
	echo "$y"

}

BASH_COMPAT=5.2
f
BASH_COMPAT=5.1
f
実行結果
foo
foo
BAR
BAR
FOO
foo
bar
BAR

6. コマンド置換 $(command) における拡張パターンマッチング

違い:

  • バージョン 5.2 以降
    • 拡張パターンマッチングが無効のときにコマンド置換 $(command) で拡張パターンを用いると、未実行であっても解析時にエラーが発生する
  • バージョン 5.1 以前
    • コマンド置換 $(command) は解析時に拡張パターンマッチングが有効の扱いにする
    • 拡張パターンマッチングが無効のときにコマンド置換 $(command) で拡張パターンを用いても、それが実行されるまでに拡張パターンマッチングが有効化されればエラーは発生しない

※コマンド置換 `command` には影響しません。
※パラメータ展開の拡張パターンには影響しません。

# 
BASH_COMPAT=5.2

shopt -s extglob
function f() {

	echo "$(echo /usr/@(lib))"

	echo "$(case 'foo' in @(foo)) echo 'OK' ;; *) echo 'NG' ;; esac)"

}

shopt -s extglob
f

# 
BASH_COMPAT=5.1

shopt -u extglob
function g() {

	echo "$(echo /usr/@(lib))"

	echo "$(case 'foo' in @(foo)) echo 'OK' ;; *) echo 'NG' ;; esac)"

}

shopt -s extglob
g
実行結果
/usr/lib
OK
/usr/lib
OK

バージョン 5.0

1. シェル変数 RANDOM

バージョン 5.1 以降、よりランダム性の高い生成方法に変更されました。

# 
function f() {

	RANDOM=0

	local -i i
	for (( i = 0 ; i < 3 ; i++ )); do
		echo "$RANDOM"
	done
	unset i

}

BASH_COMPAT=5.1
f
BASH_COMPAT=5.0
f
実行結果
20814
24386
149
20034
24315
12703

2. コマンドハッシュテーブルが空の場合の hash -l

違い:

  • バージョン 5.1 以降
    • 何も表示しない
  • バージョン 5.0 以前
    • 引数なしの hash と同じ内容を表示する

※ POSIX モードが有効の場合、バージョンに関わらず hash -l も引数なしの hash も何も表示しません。
hash -l の出力形式に関する POSIX モードの影響は (おそらく) 文書化されていないため注意が必要です。

set +o posix

# 
function f() {

	hash -r

	echo "$(hash -l)"

}

BASH_COMPAT=5.1
f
BASH_COMPAT=5.0
f

# 
function g() {

	hash -r

	echo "$(hash)"

}

g
実行結果

hash: hash table empty
hash: hash table empty

参考「hash.def\builtins - bash.git - bash」(バージョン 5.1)

3. ヒアドキュメントおよびヒアストリング

違い:

  • バージョン 5.1 以降
    • パイプバッファのサイズ以下ならパイプを使用する
    • パイプバッファのサイズ超なら一時ファイルを使用する
  • バージョン 5.0 以前
    • 一時ファイルを使用する
# 
function f() {

	echo "BASH_COMPAT=$BASH_COMPAT"

	strace -f bash -c $'cat <<EOF\nfoo\nEOF\ncat <<< bar' |& grep '/tmp/' || :

}

export BASH_COMPAT=5.1
f
export BASH_COMPAT=5.0
f
実行結果の例
BASH_COMPAT=5.1
BASH_COMPAT=5.0
[pid  7208] openat(AT_FDCWD, "/tmp/sh-thd.MD3H1J", O_RDWR|O_CREAT|O_EXCL, 0600) = 3
[pid  7208] openat(AT_FDCWD, "/tmp/sh-thd.MD3H1J", O_RDONLY) = 4
[pid  7208] unlink("/tmp/sh-thd.MD3H1J") = 0
[pid  7209] openat(AT_FDCWD, "/tmp/sh-thd.7O7HmK", O_RDWR|O_CREAT|O_EXCL, 0600) = 3
[pid  7209] openat(AT_FDCWD, "/tmp/sh-thd.7O7HmK", O_RDONLY) = 4
[pid  7209] unlink("/tmp/sh-thd.7O7HmK") = 0

パイプバッファのサイズは ulimit コマンドで確認出来ます。

echo "$(( $(ulimit -p) * 512 )) bytes"
実行結果の例
4096 bytes

参考「ulimit - Bash Reference Manual

バージョン 4.4

1. シェル変数 BASH_ARGV および BASH_ARGC

サブルーチンは以下のものがあります:

  • シェル関数
  • . コマンドまたは source コマンドによって実行されるシェルスクリプト

サブルーチンにおける挙動が変化します:

  • バージョン 5.0 以降
    • 拡張デバッグモードを有効化時に初期化される: 位置パラメータ
  • バージョン 4.4 以前
    • 実行開始時に初期化される
      • 拡張デバッグモードが無効の場合: 空
      • 拡張デバッグモードが有効の場合: 位置パラメータ

※初めから拡張デバッグモードを有効にする場合、内部的な処理方法は変わりますが見かけの挙動は変化しません。

# 
function f() {
	shopt -s extdebug
	echo "${BASH_ARGV[@]}"
	echo "${BASH_ARGC[@]}"
}

BASH_COMPAT=5.0
(shopt -u extdebug; f foo)
BASH_COMPAT=4.4
(shopt -u extdebug; f foo)

BASH_COMPAT=5.0
(shopt -s extdebug; f foo)
BASH_COMPAT=4.4
(shopt -s extdebug; f foo)
実行結果
foo
1

0
foo
1 0
foo
1 0

バージョン 5.0 以降、メインルーチンにおける内部的な処理方法が変更されましたが、互換モードを使用しても内部的な処理方法は変化しません:

  • バージョン 5.0 以降
    • 拡張デバッグモードを有効化時に初期化される: 位置パラメータ
    • 下位互換のため、拡張デバッグモードが無効の場合、参照時に動的に初期化される: 位置パラメータ
  • バージョン 4.4 以前
    • 実行開始時に初期化される: 位置パラメータ
bash -c 'shopt -u extdebug; echo "${BASH_ARGV[@]}"; echo "${BASH_ARGC[@]}"' bash foo
bash -c 'shopt -s extdebug; echo "${BASH_ARGV[@]}"; echo "${BASH_ARGC[@]}"' bash foo
実行結果
foo
1
foo
1

参考「variables.c - bash.git - bash」(バージョン 5.0)
参考「NEWS - bash.git - bash」(バージョン 5.0)
参考「changelog\CWRU - bash.git - bash」(バージョン 5.0)

以下の場合に取得した値が正しくないことがあります:

  • 途中で拡張デバッグモードを有効化する場合
  • 拡張デバッグモードが無効のままの場合
# 
function f() {
	shopt -s extdebug
	echo "${BASH_ARGV[@]}"
	echo "${BASH_ARGC[@]}"
}

BASH_COMPAT=5.0
(shopt -u extdebug; f foo)
BASH_COMPAT=4.4
(shopt -u extdebug; f foo)
実行結果
foo
1

0
export BASH_COMPAT=5.0
bash -c 'shopt -u extdebug; f() { :; }; f; echo "${BASH_ARGV[@]}"; echo "${BASH_ARGC[@]}"' bash foo
export BASH_COMPAT=4.4
bash -c 'shopt -u extdebug; f() { :; }; f; echo "${BASH_ARGV[@]}"; echo "${BASH_ARGC[@]}"' bash foo
実行結果
foo
1

0

参考「BASH_ARGC - Bash Reference Manual
参考「BASH_ARGV - Bash Reference Manual

2. ループ内のサブシェルにおける break または continue コマンド

ループは以下のものがあります:

  • for コマンド
  • while コマンド
  • until コマンド
  • select コマンド

違い:

  • バージョン 5.0 以降
    • 何もしない
    • ループ外でコマンドを実行するのと同じ扱い
  • バージョン 4.4 以前
    • サブシェルを終了する
    • break コマンドはループを終了しない
    • continue コマンドは次のループを開始しない

※バージョン 4.4 以前でも本来の正しいコマンドの挙動にならないため、そもそもサブシェル内で break または continue コマンドを使用しないべきです。

# 
function f() {

	for x in foo bar baz; do
		(break; echo -n "$x ")
		echo -n '_ '
	done
	echo

}

BASH_COMPAT=5.0
f
BASH_COMPAT=4.4
f

# 
function g() {

	for x in foo bar baz; do
		(continue; echo -n "$x ")
		echo -n '_ '
	done
	echo

}

BASH_COMPAT=5.0
g
BASH_COMPAT=4.4
g

# 
function h() {

	for x in foo bar baz; do
		(exit; echo -n "$x ")
		echo -n '_ '
	done
	echo

}

h
実行結果
foo _ bar _ baz _
_ _ _
foo _ bar _ baz _
_ _ _
_ _ _

3. 関数内の export および readonly コマンドの前にある変数代入

3.1. 概要

関数呼び出しの前に変数代入がある場合の違い:

  • バージョン 5.0 以降
    • POSIX モードが無効の場合、シェル環境に影響しない
    • POSIX モードが有効の場合、シェル環境に影響する
  • バージョン 4.4 以前
    • シェル環境に影響する

※関数呼び出しの前に変数代入がない場合、(POSIX モードかどうかやバージョンに関わらず) シェル環境に影響します。

set +o posix

# 
function f() {

	x=baz export x

}

BASH_COMPAT=5.0
(x=foo; x=bar f; echo "$x")
BASH_COMPAT=4.4
(x=foo; x=bar f; echo "$x")

# 
function g() {

	x=baz readonly x

}

BASH_COMPAT=5.0
(x=foo; x=bar g; echo "$x")
BASH_COMPAT=4.4
(x=foo; x=bar g; echo "$x")
実行結果
foo
baz
foo
baz

3.2. 詳細

コマンドの前にある変数代入はコマンドが参照する環境に影響し、基本的にシェル環境に影響しません。

(x=foo; x=bar bash -c 'echo "$x"'; echo "$x")
実行結果
bar
foo

参考「3.7.1 Simple Command Expansion - Bash Reference Manual
参考「3.7.4 Environment - Bash Reference Manual

POSIX モードが無効の場合、export および readonly コマンドの前にある変数代入も基本的にシェル環境に影響しません。

ただし、それらのコマンドの引数で変数を指定する場合、コマンド自体がシェル環境に影響するためコマンドの前にある変数代入はシェル環境に影響します。

set +o posix

(x=foo; x=bar export > /dev/null; echo "$x")
(x=foo; x=bar export x; echo "$x")

(x=foo; x=bar readonly > /dev/null; echo "$x")
(x=foo; x=bar readonly x; echo "$x")
実行結果
foo
bar
foo
bar

シェル関数の環境は以下のような挙動をします:

  • 基本的に呼び出し元の環境と同じ
  • 基本的に呼び出し元と変数およびその値が共有される
function f() {

	x=bar

}

(x=foo; f; echo "$x")
実行結果
bar

参考「3.3 Shell Functions - Bash Reference Manual

関数呼び出しの前に変数代入がある場合は基本的にシェル環境に影響しません。

function f() {

	x=bar

}

(x=foo; x=bar f; echo "$x")
実行結果
foo

参考「changelog\CWRU - bash.git - bash」(バージョン 5.0)
参考「changelog\CWRU - bash.git - bash」(バージョン 5.1)
参考「variables.c - bash.git - bash」(バージョン 5.1)
参考「0000654: unclear behavior of in-line variable assignments preceding functions, special built-ins - Austin Group Defect Tracker」(POSIX 解釈 654)

関数内で export および readonly コマンドを実行すると、関数呼び出しの前に変数代入がある場合でも関数呼び出しのシェル環境に影響します。

# 
function f() {

	export x

}

(x=foo; x=bar f; echo "$x")

# 
function g() {

	readonly x

}

(x=foo; x=bar g; echo "$x")
実行結果
bar
bar

POSIX モードが無効のとき、バージョン 5.0 以降、関数内の export および readonly コマンドの前にある変数代入は、関数呼び出しの前に変数代入がある場合、一時環境に影響しますがシェル環境に影響しません。

POSIX モードが無効のとき、バージョン 4.4 以前、関数内の export および readonly コマンドの前にある変数代入は、関数呼び出しの前に変数代入がある場合、シェル環境に影響します。

set +o posix

# 
function f() {

	x=baz export x

}

BASH_COMPAT=5.0
(x=foo; x=bar f; echo "$x")
BASH_COMPAT=4.4
(x=foo; x=bar f; echo "$x")

# 
function g() {

	x=baz readonly x

}

BASH_COMPAT=5.0
(x=foo; x=bar g; echo "$x")
BASH_COMPAT=4.4
(x=foo; x=bar g; echo "$x")
実行結果
foo
baz
foo
baz

3.3. おまけ: POSIX モードが有効の場合

POSIX モードが有効の場合、特殊組み込みコマンドの前にある変数代入はシェル環境に影響します。

特殊組み込みコマンド:

  • :
  • .
  • break
  • continue
  • eval
  • exec
  • exit
  • export
  • readonly
  • return
  • set
  • shift
  • trap
  • unset
set -o posix
(x=foo; x=bar :; echo "$x")
set +o posix
(x=foo; x=bar :; echo "$x")
実行結果
bar
foo

参考「4.4 Special Builtins - Bash Reference Manual

POSIX モードが有効の場合、export および readonly コマンドも特殊組み込みコマンドのため、それらのコマンドの前にある変数代入はシェル環境に影響します。

set -o posix

(x=foo; x=bar export > /dev/null; echo "$x")
(x=foo; x=bar export x; echo "$x")

(x=foo; x=bar readonly > /dev/null; echo "$x")
(x=foo; x=bar readonly x; echo "$x")
実行結果
bar
bar
bar
bar

POSIX モードが有効のとき、関数内の export および readonly コマンドの前にある変数代入は、関数呼び出しの前に変数代入がある場合、シェル環境に影響します。

set -o posix

# 
function f() {

	x=baz export x

}

(x=foo; x=bar f; echo "$x")

# 
function g() {

	x=baz readonly x

}

(x=foo; x=bar g; echo "$x")
実行結果
baz
baz

バージョン 4.3

1. 引用符で囲まれている配列の複合代入

違い:

  • バージョン 4.4 以降
    • 警告を表示する
      • declare name[subscript]='(value1 value2 ... )'
  • バージョン 4.3 以前
    • 警告を表示しない

※本記事ではインデックス配列の例を記載しますが、連想配列も同様の扱いになります。

# 
function f() {

	echo "BASH_COMPAT=$BASH_COMPAT"

	bash -c $'declare array[0]=\'(foo bar baz)\''
	bash -c $'declare array=\'\'; declare array[0]=\'(foo bar baz)\''

}

export BASH_COMPAT=4.4
f
export BASH_COMPAT=4.3
f
実行環境
BASH_COMPAT=4.4
bash: line 1: warning: array[1]=(foo bar baz): quoted compound array assignment deprecated
bash: line 1: warning: array[1]=(foo bar baz): quoted compound array assignment deprecated
BASH_COMPAT=4.3

配列の複合代入自体の挙動も変化するため注意が必要です。

BASH_COMPAT=4.4
(declare array[0]='(foo bar baz)' 2> /dev/null; declare -p array)
(declare array=''; declare array[0]='(foo bar baz)' 2> /dev/null; declare -p array)

BASH_COMPAT=4.3
(declare array[0]='(foo bar baz)' 2> /dev/null; declare -p array)
(declare array=''; declare array[0]='(foo bar baz)' 2> /dev/null; declare -p array)
実行結果
declare -a array=([0]="(foo bar baz)")
declare -a array=([0]="(foo bar baz)")
declare -a array=([0]="foo" [1]="bar" [2]="baz")
declare -a array=([0]="foo" [1]="bar" [2]="baz")

バージョン 4.4 以降でも以下の場合には警告は表示されません:

  • 既に配列変数が存在する
  • 複合代入時に配列属性 -a または -A を設定する
  • 関数内でローカル変数を作成する
# 
function f() {

	echo "BASH_COMPAT=$BASH_COMPAT"

	bash -c $'declare -a array=(); declare array[0]=\'(foo bar baz)\''
	bash -c $'declare -a array[0]=\'(foo bar baz)\''
	bash -c $'f() { declare -a array[0]=\'(foo bar baz)\'; }; f'

}

export BASH_COMPAT=4.4
f
export BASH_COMPAT=4.3
f
実行環境
BASH_COMPAT=4.4
BASH_COMPAT=4.3

参考「changelog\CWRU - bash.git - bash」(バージョン 4.4)

バージョン 4.4 以降、関数内でローカル変数を作成する場合に警告が表示されない理由は以下の通りです:

  • declare name[subscript] とすると (配列属性 -a または -A を設定しなくても) 配列変数が作成される
  • declare コマンドでは以下の順で処理する
    1. ローカル配列変数を作成する
    2. 配列変数の存在有無を確認する
    3. グローバル配列変数を作成する
    4. 配列変数が存在しない等の条件を満たす場合に警告を表示する

参考「declare.def\builtins - bash.git - bash」(バージョン 4.4)

2. パラメータ展開における一部の "bad substitution" エラー

違い:

  • バージョン 4.4 以降
    • POSIX モードが無効の場合、致命的でないエラー
    • POSIX モードが有効の場合、致命的なエラー
  • バージョン 4.3 以前
    • 致命的でないエラー

参考「changelog\CWRU - bash.git - bash」(バージョン 4.4)
参考「Bash does not exit on non-interactive "Bad substitution" errors

set +e -o posix
export SHELLOPTS

export BASH_COMPAT=4.4
bash -c $': "${}"\necho foo' || :
export BASH_COMPAT=4.3
bash -c $': "${}"\necho foo' || :
実行結果
bash: line 1: ${}: bad substitution
bash: line 1: ${}: bad substitution
foo

全ての "bad substitution" エラーには影響しません。

参考「subst.c - bash.git - bash」(バージョン 4.4)

3. 関数内の break および continue コマンド

ループは以下のものがあります:

  • for コマンド
  • while コマンド
  • until コマンド
  • select コマンド

ループ内に関数呼び出しがある場合の違い:

  • バージョン 4.4 以降
    • ループに影響しない
  • バージョン 4.3 以前
    • ループに影響する
# 
function f() {
	break
}

function g() {

	for x in foo bar baz; do
		f
		echo -n "$x "
	done
	echo

}

BASH_COMPAT=4.4
g
BASH_COMPAT=4.3
g

# 
function h() {
	continue
}

function i() {

	for x in foo bar baz; do
		h
		echo -n "$x "
	done
	echo

}

BASH_COMPAT=4.4
i
BASH_COMPAT=4.3
i
実行結果
foo bar baz

foo bar baz

バージョン 4.2

1. 二重引用符で囲まれたパターン置換 ${parameter/pattern/string} における置換文字列 string

違い:

  • バージョン 4.3 以降
    • 引用符の削除を行う
  • バージョン 4.2 以前
    • 引用符の削除を行わない
# 
function f() {

	local -r x=foo
	echo "${x/foo/'bar'}"

}

BASH_COMPAT=4.3
f
BASH_COMPAT=4.2
f
実行結果
bar
'bar'

二重引用符内だけでなく、ヒアドキュメント内も影響します。

# 
function f() {

	local -r x=foo
	cat <<-EOF
		${x/foo/'bar'}
	EOF

}

BASH_COMPAT=4.3
f
BASH_COMPAT=4.2
f
実行結果
bar
'bar'

参考「subst.c - bash.git - bash」(バージョン 4.3)

2. 二重引用符で囲まれたパラメータ展開 ${...} 内の文字列

影響する場所は以下の表の通りです。

説明 パラメータ展開 文字列
デフォルト値 ${parameter:-word} word
デフォルト値代入 ${parameter:=word} word
エラー表示 ${parameter:?word} word
代替値 ${parameter:+word} word

違い:

  • バージョン 4.3 以降
    • POSIX モードが無効の場合
      • 一重引用符で囲まれた範囲を異なる文脈で展開する
      • 解析時に一重引用符の対応の確認を行わない
    • POSIX モードが有効の場合、一重引用符は特別でない
  • バージョン 4.2
    • 一重引用符で囲まれた範囲を異なる文脈で展開する
    • 解析時に一重引用符の対応の確認を行わない
  • バージョン 4.1 以前
    • 一重引用符で囲まれた範囲を異なる文脈で展開する
    • 二重引用符で囲まれている場合、解析時に一重引用符の対応の確認を行う
    • ヒアドキュメント内の場合、解析時に一重引用符の対応の確認を行わない

パターン置換 ${parameter/pattern/string} の置換文字列 string では、(POSIX モードかどうかやバージョンに関わらず) 以下の挙動になります:

  • 一重引用符で囲まれた範囲を異なる文脈で展開する
  • 二重引用符で囲まれている場合、解析時に一重引用符の対応の確認を行う
  • ヒアドキュメント内の場合、解析時に一重引用符の対応の確認を行わない

※パターンには影響しません。

set -o posix
export SHELLOPTS

# 
function f() {

	local -xr x=''
	echo "${x:-foo'}'bar}"

	local y=''
	echo "${y:=foo'}'bar}"

	bash -c $'echo "${x:?foo\'}\'bar}"' || :

	local -r z=baz
	echo "${z:+foo'}'bar}"

}

export BASH_COMPAT=4.3
f
export BASH_COMPAT=4.2
f
実行結果
foo''bar}
foo''bar}
bash: line 1: x: foo
foo''bar}
foo'}'bar
foo'}'bar
bash: line 1: x: foo}bar
foo'}'bar

二重引用符内だけでなく、ヒアドキュメント内も影響します。

set -o posix
export SHELLOPTS

# 
function f() {

	local -xr x=''
	cat <<-EOF
		${x:-foo'}'bar}
	EOF

	local y=''
	cat <<-EOF
		${y:=foo'}'bar}
	EOF

	bash -c $'cat <<EOF\n${x:?foo\'}\'bar}\nEOF' || :

	local -r z=baz
	cat <<-EOF
		${z:+foo'}'bar}
	EOF

}

export BASH_COMPAT=4.3
f
export BASH_COMPAT=4.2
f
実行結果
foo''bar}
foo''bar}
bash: line 1: x: foo
foo''bar}
foo'}'bar
foo'}'bar
bash: line 1: x: foo}bar
foo'}'bar

参考「subst.c - bash.git - bash」(バージョン 4.3)
参考「0000221: poor wording about even quotes in double quoted parameter expansion - Austin Group Defect Tracker」(POSIX 解釈 221)
参考「2.2.3 Double-Quotes - Shell Command Language」(2016 年版)

バージョン 4.1

1. オプションが与えられている time

違い:

  • バージョン 4.2 以降
    • POSIX モードが無効の場合、予約語
    • POSIX モードが有効の場合、ディスクコマンド
  • バージョン 4.1 以前
    • 予約語

※バージョン 4.2 以降、POSIX モードが有効の場合はオプション -p が与えられた場合でも予約語でなくディスクコマンドとみなされるため注意が必要です。

set -o posix

BASH_COMPAT=4.2
(time -V) &> /dev/null && echo 'disk command' || echo 'reserved word'
(time -p ! bash -c 'exit 1') 2> /dev/null && echo 'reserved word' || echo 'disk command'
BASH_COMPAT=4.1
(time -V) &> /dev/null && echo 'disk command' || echo 'reserved word'
(time -p ! bash -c 'exit 1') 2> /dev/null && echo 'reserved word' || echo 'disk command'
実行結果
disk command
disk command
reserved word
reserved word

ディスクコマンも予約語も、オプション -p が与えられた場合の出力形式は同じです。

env time -p bash -c ''

set +o posix
time -p ! bash -c 'exit 1'
実行結果の例
real 0.00
user 0.00
sys 0.00
real 0.00
user 0.00
sys 0.00

Bash の time は組み込みコマンドでなく予約語です。

type -a echo
type -a time
実行結果の例
echo is a shell builtin
echo is /usr/bin/echo
echo is /bin/echo
time is a shell keyword
time is /usr/bin/time
time is /bin/time

参考「3.2.3 Pipelines - Bash Reference Manual

2. 二重引用符で囲まれたパラメータ展開 ${...} 内の文字列

影響する場所は以下の表の通りです。

説明 パラメータ展開 文字列
デフォルト値 ${parameter:-word} word
デフォルト値代入 ${parameter:=word} word
エラー表示 ${parameter:?word} word
代替値 ${parameter:+word} word

違い:

  • バージョン 4.3 以降
    • POSIX モードが無効の場合
      • 一重引用符で囲まれた範囲を異なる文脈で展開する
      • 解析時に一重引用符の対応の確認を行わない
    • POSIX モードが有効の場合、一重引用符は特別でない
  • バージョン 4.2
    • 一重引用符で囲まれた範囲を異なる文脈で展開する
    • 解析時に一重引用符の対応の確認を行わない
  • バージョン 4.1 以前
    • 一重引用符で囲まれた範囲を異なる文脈で展開する
    • 二重引用符で囲まれている場合、解析時に一重引用符の対応の確認を行う
    • ヒアドキュメント内の場合、解析時に一重引用符の対応の確認を行わない

パターン置換 ${parameter/pattern/string} の置換文字列 string では、(POSIX モードかどうかやバージョンに関わらず) 以下の挙動になります:

  • 一重引用符で囲まれた範囲を異なる文脈で展開する
  • 二重引用符で囲まれている場合、解析時に一重引用符の対応の確認を行う
  • ヒアドキュメント内の場合、解析時に一重引用符の対応の確認を行わない

※パターンには影響しません。

set -o posix
export SHELLOPTS

# 
function f() {

	local -xr x=''
	bash -c $'echo "${x:-\'foo}"' || :

	local -x y=''
	bash -c $'echo "${y:=\'foo}"' || :

	bash -c $'echo "${x:?\'foo}"' || :

	local -xr z=bar
	bash -c $'echo "${z:+\'foo}"' || :

}

export BASH_COMPAT=4.2
f
export BASH_COMPAT=4.1
f
実行結果
bash: line 1: bad substitution: no closing `}' in "${x:-'foo}"
bash: line 1: bad substitution: no closing `}' in "${y:='foo}"
bash: line 1: bad substitution: no closing `}' in "${x:?'foo}"
bash: line 1: bad substitution: no closing `}' in "${z:+'foo}"
bash: -c: line 1: unexpected EOF while looking for matching `''
bash: -c: line 1: unexpected EOF while looking for matching `''
bash: -c: line 1: unexpected EOF while looking for matching `''
bash: -c: line 1: unexpected EOF while looking for matching `''

解析時の挙動変更のため、未実行であってもエラーが発生します。

set -o posix
export SHELLOPTS

# 
function f() {

	echo "BASH_COMPAT=$BASH_COMPAT"

	local -xr x=''
	bash -c $'f() { : "${x:-\'foo}"; }' || :

	local -x y=''
	bash -c $'f() { : "${y:=\'foo}"; }' || :

	bash -c $'f() { : "${x:?\'foo}"; }' || :

	local -xr z=bar
	bash -c $'f() { : "${z:+\'foo}"; }' || :

}

export BASH_COMPAT=4.2
f
export BASH_COMPAT=4.1
f
実行結果
BASH_COMPAT=4.2
BASH_COMPAT=4.1
bash: -c: line 1: unexpected EOF while looking for matching `''
bash: -c: line 1: unexpected EOF while looking for matching `''
bash: -c: line 1: unexpected EOF while looking for matching `''
bash: -c: line 1: unexpected EOF while looking for matching `''

参考「parse.y - bash.git - bash」(バージョン 4.3)

バージョン 4.0

1. 条件コマンド [[ expression ]]< および > 演算子

違い:

  • バージョン 4.1 以降
    • ロケールを考慮して文字列比較する
  • バージョン 4.0 以前
    • ロケールを考慮せず ASCII 順序で文字列比較する
export LC_COLLATE=en_US.UTF-8
echo -e 'A\na\n' | sort | xargs
BASH_COMPAT=4.1
[[ 'a' < 'A' ]] && echo 'a A' || echo 'A a'
BASH_COMPAT=4.0
[[ 'a' < 'A' ]] && echo 'a A' || echo 'A a'

export LC_COLLATE=C
echo -e 'A\na\n' | sort | xargs
[[ 'a' < 'A' ]] && echo 'a A' || echo 'A a'
実行結果
a A
a A
A a
A a
A a

バージョン 3.2

1. 対話モードでサブシェルでないコマンドリスト

コマンドリスト内のコマンドをシグナル SIGINT で終了したときの違い:

  • バージョン 4.0 以降
    • コマンドリスト全体の実行を中断する
  • バージョン 3.2 以前
    • コマンドリスト全体の実行を中断しない
export BASH_COMPAT=4.0
bash -i -c 'trap - SIGINT; echo foo; sleep 5 & (sleep 1; kill -s INT $!) & fg %-; echo bar' || :
export BASH_COMPAT=3.2
bash -i -c 'trap - SIGINT; echo foo; sleep 5 & (sleep 1; kill -s INT $!) & fg %-; echo bar' || :
実行結果の例
foo
[1] 19092
[2] 19093
sleep 5

foo
[1] 19104
[2] 19105
sleep 5

bar
export BASH_COMPAT=4.0
bash -i -c 'trap - SIGINT; echo foo; cat || :; echo bar' || :
export BASH_COMPAT=3.2
bash -i -c 'trap - SIGINT; echo foo; cat || :; echo bar' || :
実行結果の例
foo
^C
foo
^C
bar

参考「jobs.c - bash.git - bash」(バージョン 4.0)

バージョン 3.1

1. 条件コマンド [[ expression ]]=~ 演算子の右辺

違い:

  • バージョン 3.2 以降
    • クォートされた範囲は正規表現でなく文字列とみなす
  • バージョン 3.1 以前
    • クォートされても効果なし
BASH_COMPAT=3.2
[[ 'foo' =~ '^foo$' ]] && echo 'OK' || echo 'NG'
BASH_COMPAT=3.1
[[ 'foo' =~ '^foo$' ]] && echo 'OK' || echo 'NG'

[[ 'foo' =~ ^foo$ ]] && echo 'OK' || echo 'NG'
実行結果
NG
OK
OK
BASH_COMPAT=3.2
[[ '^foo$' =~ '^foo$' ]] && echo 'OK' || echo 'NG'
BASH_COMPAT=3.1
[[ '^foo$' =~ '^foo$' ]] && echo 'OK' || echo 'NG'

[[ '^foo$' =~ ^foo$ ]] && echo 'OK' || echo 'NG'
実行結果
OK
NG
NG
1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?