web-dev-qa-db-ja.com

ファイル記述子を移動するための実用的な使用

バッシュマンページによると:

リダイレクト演算子

   [n]<&digit-

ファイル記述子digitをファイル記述子nに移動するか、nが指定されていない場合は標準入力(ファイル記述子0)に移動します。 digitは、nに複製された後に閉じられます。

ファイル記述子を別の記述子に「移動」するとはどういう意味ですか?そのような実践の典型的な状況は何ですか?

15
Quentin

_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をそのままにする場合、つまり、コマンド置換の外側を指すのと同じリソースを指す場合は、そのリソースをコマンド置換の内側に持っていく必要があります。そのために、stdoutoutsideのコピーを実行して、コマンド置換を実行して内部に取り込むことができます。

_{
  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があります。

  1. varへのパイプ
  2. 手つかず
  3. コマンド置換の外側を1が指すものと同じ

これを次のように変更すると、

_{
  var=$(cmd 2>&1 >&3)
} 3>&1-
_

次に、次のようになります。

  1. コマンド置換の外側を1が指すものと同じ
  2. varへのパイプ
  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
_

それがめったに使用されない理由を要約すると:

  1. それは非標準であり、単なる構文上の砂糖です。いくつかのキーストロークを節約することと、スクリプトの移植性を低下させ、その珍しい機能に慣れていない人にはわかりにくくすることとのバランスを取る必要があります。
  2. 複製後に元のfdを閉じる必要性は見過ごされがちです。ほとんどの場合、結果に悩まされることはないため、_>&3_または_>&3-_の代わりに_>&3 3>&-_を実行するだけです。 。

あなたが知っているように、それがめったに使用されないという証拠は、それが bashの偽物 であるということです。 bashでは、_compound-command 3>&4-_または_any-builtin 3>&4-_は、_compound-command_または_any-builtin_が戻った後でも、fd4を閉じたままにします。問題を修正するための パッチ が利用可能になりました(2013-02-19)。

14

これは、他のファイル記述子と同じ場所を指すようにすることを意味します。標準エラー記述子(stderrfd 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に出力してから、特別に処理します。

4
lynxlynxlynx

Unixでは、ファイルはファイル記述子によって処理されます(たとえば、標準入力が0、標準出力が1、標準エラーが2などの小さい整数。他のファイルを開くと、通常、最小の未使用記述子が割り当てられます)。したがって、プログラムの内部を知っていて、ファイル記述子5に送られる出力を標準出力に送信したい場合は、記述子5を1に移動します。ここから2> errorsが作成され、構造が作成されます。 2>&1のように、エラーを出力ストリームに複製します。

そのため、ほとんど使用されていません(25年以上のほぼ独占的なUnixの使用で、怒りで1、2回使用したことを漠然と覚えています)が、必要な場合は絶対に不可欠です。

0
vonbrand