こんにちは。
サブシェルによって実行される場合を簡単にまとめてみました:
コマンドグループ(サブシェルではなく元シェルで実行)
これに対して、下記の一塊の実行単位は、そのままでは元シェルで実行されます3。
- コマンドグループ(すなわち brace「{ }」で囲う)
- 関数、
-
while do 〜 done
ループ 4
サブシェルによる実行例
パイプラインとコマンドグループとの組み合わせは勘違いしやすかったです。下記例を読み解くと、
command1; command2; { command3; command4; } | { commmand5; command6; }; command7; command8
- 元シェルで実行されるのは:
command1, command2
command7, command8
- サブシェルで実行されるのは:
command3, command4
command5, command6
サブシェル内の変数更新
次にサブシェル内の変数更新を考えると、これは元シェルへは伝播しません。下記例で末尾の echo $n
の出力は空となります。
$ unset n; { n=0; printf 'a\nb\n'; } | cat; echo $n
a
b
$ unset n; printf 'a\nb\n' | (n=0; while read -r line; do n=$((n+1)); done; echo $n); echo $n
2
$ unset n; printf 'a\nb\n' | { n=0; while read -r line; do n=$((n+1)); done; echo $n; }; echo $n
2
$
「サブシェル内の変数更新」を回避
元シェルでの変数更新であれば、「サブシェル内の変数更新」を回避できます。下記例は混み入っていますが、n=0
は元シェルで実行され末尾の echo $n
の出力は 0
となります。
$ unset n; { n=0; printf 'a\nb\n' | cat; } ; echo $n
a
b
0
$
「ヒア・ドキュメント」の利用
同様に元シェルでの変数更新とするために、「ヒア・ドキュメント」を利用する方法もあります(パイプラインを回避できます)。下記例で末尾の echo $n
の出力は 2
となります。
$ unset n; { n=0; while read -r line; do n=$((n+1)); done << EOT
$(printf 'a\nb\n')
EOT
echo $n; }; echo $n
2
2
$
もしくは、POSIX shell ではなく、Bash などでは Process Substitution を利用できます:
$ unset n; { n=0; while read -r line; do n=$((n+1)); done < <(printf 'a\nb\n'); echo $n; }; echo $n
2
2
$
-
括弧「( )」で囲ったサブシェル実行の標準出力を得る
$(...)
の形式も同様です。しかし$((...))
の形式は元シェルで実行されます(「サブシェル内における変数」)。 ↩ -
シェルスクリプトを実行させたり、
sh -c 'n=0; echo $n'
のような実行例を指します。これに対してsource
コマンドを用いて動かすと、元シェルで実行されます。 ↩ -
これは、括弧「( )」で囲った場合と似ていますが、サブシェル扱いとなるわけではありません。 ↩
-
例えば、
printf 'a\nb\n' | while read -r line; do echo $line; done
。 ↩