シェルリダイレクトを使用して同じファイル記述子を読み書きする

シェルリダイレクトを使用して同じファイル記述子を読み書きする

シェルリダイレクトの文脈でファイル記述子を理解しようとしています。

catSTDOUTに書かれたFD3が読めないのはなぜですか?ls

{ err=$(exec 2>&1 >&3; ls -ld /x /bin); exec 0<&3; out=$(cat); } 3>&1;

これを試してみると、catまだキーボードから読みたいです。

これが不可能であれば、なぜダメですか?


違い:この質問は、質問を使用して同じファイル記述子を読み書きすることです。一時ファイルなしでSTDERRとSTDOUTを別の変数にリダイレクトする 例えば。

答え1

存在する

{ err=$(exec 2>&1 >&3; ls -ld /x /bin); exec 0<&3; out=$(cat); } 3>&1

fd 1を{ ... } 3>&1fd 3に複製します。これはfd 3が同じリソース(同じリソース)を指すことを意味します。ファイル説明を開く) fd 1で指摘したとおりです。端末で実行している場合は、おそらく端末装置に対して読み取り+書き込みモードで開かれたfdになります。

その後、exec 0<&3fds 0、1、3はすべて同じものを指します。ファイル説明を開く(端末エミュレータは、シェルを実行する前に生成された擬似端末ペアのスレーブ端を開くと生成されます(上記の端末ケースでコマンドを実行した場合))。

その後、変更を実行するプロセスでは、out=$(cat)fd 1はパイプの書き込み終了、0はまだttyデバイスです。したがって、端末デバイスから読み取られるため、キーボードに入力する内容(ターミナルデバイスでない場合、fdが書き込み専用モードで開かれる可能性があるため、エラーが発生する可能性があります)。cat$(...)cat

stdoutに書き込まれた内容をcat読み取るには、IPCメカニズム(パイプ、ソケットペア、または疑似ターミナルペア)の2つのエンドであるstdoutとstdinが必要です。たとえば、stdout はパイプの書き込み終了、stdin はパイプの読み込み終了です。lslscatlscat

ただし、これはプロセス間通信(IPC)メカニズムであるため、順番に実行するのではなく、同時にls実行する必要があります。cat

パイプが可能なので捕まえる一部のデータ(現在のLinuxバージョンではデフォルトでは64KiB)、2番目のパイプを作成すると短い出力が得られますが、より大きな出力の場合はデッドロックが発生し、パイプがls完全に起動すると停止し、何かがパイプをクリアしますが戻ってくるcatときのみ可能ですls

また、yash生のインターフェースは1つだけで、stdoutから読み取る2番目のパイプ(構成で生成されたstderrの別のパイプ)をpipe()作成する必要があります。ls$(...)

yashは次のことを行います。

{ out=$(ls -d / /x 2>&3); exec 3>&-; err=$(exec cat <&4); } 3>>|4

(yash固有の関数)は、3>>|4fd 3に書き込み終了があり、fd 4に読み取り終了がある2番目のパイプを生成します。

ただし、stderr出力がパイプサイズより大きい場合は中断されます。私たちはパイプではなく、メモリ内の一時ファイルとしてパイプを効果的に使用しています。

実際にパイプを使用するには、まずlsstdoutをあるパイプの書き込み端として使用し、stderrを別のパイプの書き込み端として使用する必要があります。その後、シェルは同時にこのパイプのもう一方の端(1つのパイプではない)を読み取る必要があります。データが来る。他の場合はデッドロックが発生します)2つの変数に保存します。

データが入ったときに両方のfdを読み取ることができるようにするには、select()/をpoll()サポートするシェルが必要です。zshそんな殻ですが、そんな殻がありませんyashパイプリダイレクト機能1、したがって名前付きパイプを使用し(したがって、作成、権限、およびクリーンアップ管理)、zselectと一緒に複雑なループを使用する必要がありますsysread

/proc/self/fd/x¹Linuxを使用している場合は、パイプの動作が名前付きパイプと似ているという事実を利用できるため、次のことができます。

#! /bin/zsh -
zmodload zsh/zselect
zmodload zsh/system

(){exec {wo}>$1 {ro}<$1} <(:) # like yash's wo>>|ro (but on Linux only)
(){exec {we}>$1 {re}<$1} <(:)

ls -d / /x >&$wo 2>&$we &
exec {wo}>&- {we}>&-
out= err=
o_done=0 e_done=0

while ((! (o_done && e_done))) && zselect -A ready $ro $re; do
  if ((${#ready[$ro]})); then
    sysread -i $ro && out+=$REPLY || o_done=1
  fi
  if ((${#ready[$re]})); then
    sysread -i $re && err+=$REPLY || e_done=1
  fi
done

答え2

これは変数割り当て構文の基本的な制限であり、シェル生成サブシェルの副作用であるようです。 stderrまたはstdoutをキャプチャできますが、両方をキャプチャすることはできません。他のストリームはファイル(おそらくFIFO)にリダイレクトする必要があります。

# a function for testing
your_command() { sh -c 'echo "this is stdout"; echo "this is stderr" >&2'; }

errfile=$(mktemp)
out=$( your_command 2>|"$errfile" )
err=$(< "$errfile")
rm "$errfile"

echo "out: $out"
echo "err: $err"

関連情報