バッシュマンページによると:
リダイレクト演算子
[n]<&digit-
ファイル記述子
digit
をファイル記述子n
に移動するか、n
が指定されていない場合は標準入力(ファイル記述子0)に移動します。digit
は、n
に複製された後に閉じられます。
ファイル記述子を別の記述子に「移動」するとはどういう意味ですか?そのような実践の典型的な状況は何ですか?
_3>&4-
_はbashでもサポートされているksh93拡張機能であり、_3>&4 4>&-
_の略です。つまり、3は4が使用されていた場所を指し、4は閉じられているため、4が指しているものは現在3に移動しました。
一般的な使用法は、次のように、stdin
またはstdout
を複製してそのコピーを保存し、復元したい場合です。
Stdoutを変数に残したまま、コマンドのstderr(およびstderrのみ)をキャプチャするとします。
コマンド置換var=$(cmd)
は、パイプを作成します。パイプの書き込み端はcmd
のstdout(ファイル記述子1)になり、もう一方の端はシェルによって読み取られて変数が埋められます。
ここで、stderr
を変数に移動する場合は、var=$(cmd 2>&1)
を実行できます。これで、fd 1(stdout)と2(stderr)の両方がパイプ(最終的には変数)に移動します。これは、必要な半分にすぎません。
var=$(cmd 2>&1-)
(_var=$(cmd 2>&1 >&-
_の略)を実行すると、cmd
のstderrのみがパイプに送られますが、fd1は閉じられます。 cmd
が出力を書き込もうとすると、EBADF
エラーが返されます。ファイルを開くと、最初の空きfdが取得され、コマンドがそれを防止しない限り、開いているファイルはstdout
に割り当てられます。私たちが望むものでもありません。
cmd
のstdoutをそのままにする場合、つまり、コマンド置換の外側を指すのと同じリソースを指す場合は、そのリソースをコマンド置換の内側に持っていく必要があります。そのために、stdout
outsideのコピーを実行して、コマンド置換を実行して内部に取り込むことができます。
_{
var=$(cmd)
} 3>&1
_
どちらを書く方がきれいですか。
_exec 3>&1
var=$(cmd)
exec 3>&-
_
(これには、最後にfd 3を閉じる代わりにfd 3を復元するという利点もあります)。
次に、_{
_(または_exec 3>&1
_)から_}
_まで、fd1と3の両方が最初にポイントした同じリソースfd1をポイントします。 fd 3は、コマンド置換内のそのリソースも指します(コマンド置換は、fd 1、stdoutのみをリダイレクトします)。上記のように、cmd
には、fds 1、2、3があります。
これを次のように変更すると、
_{
var=$(cmd 2>&1 >&3)
} 3>&1-
_
次に、次のようになります。
これで、必要なものが得られました。stderrがパイプに移動し、stdoutはそのままになります。ただし、そのfd3をcmd
にリークしています。
コマンドは(慣例により)fds 0から2が開いていて標準入力、出力、エラーであると想定していますが、他のfdsを想定していません。ほとんどの場合、彼らはそのfd3をそのままにしておくでしょう。別のファイル記述子が必要な場合は、open()/dup()/socket()...
を実行するだけで、最初に使用可能なファイル記述子が返されます。 (_exec 3>&1
_を実行するシェルスクリプトのように)そのfd
を具体的に使用する必要がある場合、最初にそれを何かに割り当てます(そのプロセスで、fd 3が保持するリソースはそのプロセスによって解放されます)。
cmd
はfd3を使用しないため、そのfd 3を閉じることをお勧めしますが、cmd
を呼び出す前に割り当てたままにしておけば大したことではありません。問題は次のとおりです。cmd
(およびそれが生成する可能性のある他のプロセス)で使用できるfdが1つ少なくなります。より深刻な問題となる可能性があるのは、そのfdが指すリソースが、そのcmd
によってバックグラウンドで生成されたプロセスによって保持されてしまう可能性がある場合です。そのリソースがパイプまたは他のプロセス間通信チャネルであるかどうか(スクリプトがscript_output=$(your-script)
として実行されている場合など)は、もう一方の端から読み取ったプロセスが決して認識しないことを意味するため、懸念される可能性があります。そのバックグラウンドプロセスが終了するまでのファイルの終わり。
したがって、ここでは、次のように書く方がよいでしょう。
_{
var=$(cmd 2>&1 >&3 3>&-)
} 3>&1
_
これは、bash
を使用して次のように短縮できます。
_{
var=$(cmd 2>&1 >&3-)
} 3>&1
_
それがめったに使用されない理由を要約すると:
>&3
_または_>&3-
_の代わりに_>&3 3>&-
_を実行するだけです。 。あなたが知っているように、それがめったに使用されないという証拠は、それが bashの偽物 であるということです。 bashでは、_compound-command 3>&4-
_または_any-builtin 3>&4-
_は、_compound-command
_または_any-builtin
_が戻った後でも、fd4を閉じたままにします。問題を修正するための パッチ が利用可能になりました(2013-02-19)。
これは、他のファイル記述子と同じ場所を指すようにすることを意味します。標準エラー記述子(stderr
、fd 2
、/dev/stderr -> /proc/self/fd/2
)。複雑な場合に便利です。
Advanced Bash Scriptingガイドには、次の 長いログレベルの例 とこのスニペットがあります。
# Redirecting only stderr to a pipe.
exec 3>&1 # Save current "value" of stdout.
ls -l 2>&1 >&3 3>&- | grep bad 3>&- # Close fd 3 for 'grep' (but not 'ls').
# ^^^^ ^^^^
exec 3>&- # Now close it for the remainder of the script.
Source Mage's Sorceryでは、たとえば、同じコードブロックからのさまざまな出力を識別するために使用します。
(
# everything is set, so run the actual build infrastructure
run_build
) 3> >(tee -a $C_LOG >> /dev/stdout) \
2> >(tee -a $C_LOG 1>&2 > $VOYEUR_STDERR) \
> >(tee -a $C_LOG > $VOYEUR_STDOUT)
ロギングの理由で追加のプロセス置換が追加されています(VOYEURは、データを画面に表示するか、単にログに表示するかを決定します)が、一部のメッセージは常にする必要があります提示した。これを実現するために、それらをファイル記述子3に出力してから、特別に処理します。
Unixでは、ファイルはファイル記述子によって処理されます(たとえば、標準入力が0、標準出力が1、標準エラーが2などの小さい整数。他のファイルを開くと、通常、最小の未使用記述子が割り当てられます)。したがって、プログラムの内部を知っていて、ファイル記述子5に送られる出力を標準出力に送信したい場合は、記述子5を1に移動します。ここから2> errors
が作成され、構造が作成されます。 2>&1
のように、エラーを出力ストリームに複製します。
そのため、ほとんど使用されていません(25年以上のほぼ独占的なUnixの使用で、怒りで1、2回使用したことを漠然と覚えています)が、必要な場合は絶対に不可欠です。