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


16

bashのmanページによると:

リダイレクト演算子

   [n]<&digit-

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

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

回答:


14

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 のみがパイプに移動しますが、fd 1は閉じられます。場合cmd試みはして戻ってくる任意の出力、書き込みにEBADFエラーが発生し、それがファイルを開いた場合、それは最初のフリーFDを取得し、開いているファイルは、それを割り当てられますstdoutことに対してコマンドガードしない限り!私たちが望むものでもありません。

stdoutをcmdそのままにしておきたい場合、つまり、コマンド置換の外側を指しているのと同じリソースを指す場合は、何らかの方法でそのリソースをコマンド置換の内側にする必要があります。そのために、コマンド置換のstdout 外側のコピーを実行して、それを内部に取り込むことができます。

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

これは、より簡潔な記述方法です。

exec 3>&1
var=$(cmd)
exec 3>&-

(これは、fd 3を最後に閉じるのではなく復元するという利点もあります)。

次に{(またはexec 3>&1)からに至る}まで、fd 1と3は両方とも、最初に指していた同じリソースfd 1を指します。fd 3はコマンド置換内のそのリソースも指します(コマンド置換はfd 1、stdoutのみをリダイレクトします)。上記のように、cmdfds 1、2、3:

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

変更する場合:

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

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

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

これで、必要なものが得られました。stderrはパイプに移動し、stdoutはそのまま残ります。ただし、fd 3をに漏らしていますcmd

コマンドは(慣例により)fds 0から2がオープンで、標準入力、出力、エラーであると想定していますが、他のfdsを想定していません。ほとんどの場合、fd 3はそのままです。別のファイル記述子が必要な場合はopen()/dup()/socket()...、最初に利用可能なファイル記述子を返すだけです。(そうするシェルスクリプトのようにexec 3>&1)それらをfd具体的に使用する必要がある場合、最初にそれを何かに割り当てます(そしてそのプロセスで、fd 3が保持するリソースはそのプロセスによって解放されます)。

fd 3 cmdは使用しないので、この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-orの代わりに行うだけです>&3 3>&-

あなたが知っているように、それがめったに使用されないという証拠は、それがbash偽であることです。bash compound-command 3>&4-またはleaveでは、any-builtin 3>&4-fd 4 が戻った後、compound-commandまたはany-builtin戻ってきました。この問題を修正するパッチが入手可能になりました(2013-02-19)。


おかげで、今ではfdを動かすことを知っています。2番目のスニペット(および一般的なfds)に関する4つの基本的な質問があります。1)cmd1で、2(stderr)を3のコピーにし、コマンドがこの3 fdを内部で使用するとどうなりますか?2)3>&1および4>&1が機能するのはなぜですか?3と4の複製は、これらの2つのコマンドでのみ有効になりますが、現在のシェルも影響を受けますか?3)なぜcmd1の4とcmd2の3を閉じるのですか?これらのコマンドは、言及されたfdsを使用しませんか?4)最後のスニペットでは、fdが(cmd1とcmd2で)存在しないものに複製された場合、それぞれ3と4になりますか?
クエンティン

@Quentin、私はより簡単な例を使用し、それが答えよりも質問が少なくなることを望んでいることをもう少し説明しました。それでも直接構文を移動FDとは関係のない質問を持っていれば、私はあなたが別の質問を示唆
ステファンChazelas

{ var=$(cmd 2>&1 >&3) ; } 3>&1-1を閉じる際のタイプミスではありませんか?
クエンティン

@Quentin、それは私がそれを含めるつもりはないという点でタイプミスですが、中括弧内の何もその fd 1を使用ないので違いはありません(それはfd 3にそれを複製する全体のポイントでした:元の1そうしないと、内部でアクセスできなくなります$(...))。
ステファンシャゼル

1
@Quentinに入ると{...}、fd 3はfd 1が指していたものを指し、fd 1は閉じます。次に$(...)、fd 1は、を供給するパイプに設定され$var、次にcmd2に対しても、1から3を指すようになりますto、それは外側の1です。1がその後閉じられたままになるという事実はbashのバグです。それを報告します。その機能が由来するksh93には、そのバグはありません。
ステファンシャゼル

4

他のファイル記述子と同じ場所を指すようにすることを意味します。あなたは離れて標準エラー記述子の明白な別々の取り扱いから、非常に稀にこれを実行しないする必要があります(stderrfd 2/dev/stderr -> /proc/self/fd/2)。いくつかの複雑な場合に便利です。

高度なBashスクリプトガイドには、この長いログレベルの例と次のスニペットがあります。

# 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のソーサリーでは、たとえば、同じコードブロックから異なる出力を識別するために使用します。

  (
    # 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に出力し、特別に処理します。


0

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

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


しかし、次のようにファイル記述子1を複製しないのはなぜですか:5>&1?カーネルはすぐにそれを閉じようとしているので、移動FDの使用が何なのか理解できません
Quentin

5を1に複製するのではなく、1が行く先に5を送ります。そして、あなたが尋ねる前に; はい、さまざまな前駆シェルから選択されたいくつかの異なる表記があります。
フォンブランド

まだわからない。5>&11が行く先に5を送信する場合、5を1>&5-閉じる以外に正確に何をしますか?
クエンティン
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.