zsh:ポインタを持つリダイレクトとパイプの順序を理解する

zsh:ポインタを持つリダイレクトとパイプの順序を理解する

zshでは

echo "hello" 1>&1 1>&1 1>&1 | cat

helloを8回印刷している間

echo "hello" 1>&1 1>&1 1>&1 1>&1 | cat

helloを16回印刷してください。したがって、helloは2^n回印刷されます。ここで、nは1>&1上記のパイプの数と同じです。

私は何を探していますか?複数のソースによると、パイプ+リダイレクトが発生する順序は次のとおりです。

「コマンドの標準入力、標準出力、またはその両方は、コマンドの一部としてリダイレクト演算子によって指定されたリダイレクトの前にパイプされたかのように処理されます。」

ソースコード:https://pubs.opengroup.org/onlinepubs/9699919799.2008edition/utilities/V3_chap02.html#tag_18_09_02https://unix.stackexchange.com/a/672961/456507

大まかに言えば、リダイレクトとパイプが処理される順序は、「パイプの後に左から右へ」です。

zsh LiberaチャットIRCの誰かがリダイレクトを処理する前に、「echoのfd1がcatのfd0を指す」と提案しました。誰かがリダイレクトを含むようにこれらの表現方法を拡張できますか1>&1?人々がそれを言葉で表現できるなら、人々は他のリダイレクトやパイプについても同様の表現をすることができ、何が起こっているのか理解できると思うので、これは重要です。ご希望の場合は、作成したC/C++ポインタ用語を自由にお試しください!

答え1

この動作は次のとおりです。複数のオペレーティングシステムzsh 固有の機能。したがって、POSIX仕様などの他の文書はあまり役に立ちません。ジッシュはい複数のオペレーティングシステムがオフになっている場合、POSIXはこの点で互換性があるため、POSIXの動作はストーリーの一部のみを提供しますが、一部のみを提供します。

複数のOSでも、リダイレクトを理解する最も自然な方法は、命令型の方法で考えることです。コマンドリダイレクトは左から右に処理され(パイプの最初を除く)、各リダイレクトはプロセスの状態を変更します。これがシェルの目的です。

  1. 最初に、コマンドには周辺コードと同じオープンファイル記述子があります。

  2. コマンドがパイプの右側にある場合は、パイプの読み取り端をファイル記述子0(標準入力)に接続します。コマンドがパイプの左側にある場合(含む|&)、パイプの書き込み側をファイル記述子1(標準出力)に接続します。

  3. リダイレクトは順次適用されます。ここでは、主な関連ケースのみをリストします。より手動リダイレクト演算子の完全なリストです。N0個以上の数字を表すファイル記述子変数。ファイル名を表すM1 つ以上の数値を表しますFILENAME(プロセス置換可能).

    • N<&Mファイル記述子MをNにコピーします。つまり、Mが接続されているすべての場所にNを接続します。以前にファイル記述子Nに接続されたすべては、もはや重要ではありません。
    • N<FILENAME入力のためにFILENAMEを開き、ファイル記述子Nに接続します。以前にファイル記述子Nに接続されたすべては、もはや重要ではありません。
    • N>&M、形式N>FILENAMEに似ていますが、ファイルを上書きするために開き、ファイルを追加するために開きます。ただし、以下の場合は除外されます。N>>FILENAME<N>FILENAMEN>>FILENAME
      有効 (デフォルト)multiosで、このコマンドのファイル記述子 N にリダイレクト (パイプを含む) がすでに存在する場合、zsh はファイル記述子 N を M または FILENAME に接続せず、代わりにパイプ P を生成し、組み込みパイプから読み込み、teeその出力を Mあるいは、FILENAMEとファイル記述子Mの内容にコピーし、Nをパイプの書き込みの終わりに接続するプロセスと同じです。つまり、ファイル記述子 N への 2 番目のリダイレクトは、exec N> >(tee FILENAME >&N)コマンドの前に実行されるものとほぼ同じです。 (これはN> >(tee FILENAME >&N)単純な場合はリダイレクトを適用するのと同じですが、同じファイル記述子に3つ以上のリダイレクトがある場合は該当しません。)
    • N<&-または、N>&-ファイル記述子 N を閉じます。
  4. コマンドが左側にある場合、|&リダイレクト2>&1が実行されます。 (これは単純な場合は配管標準誤差と同じですが、複雑な場合は必ずしもそうではありません。)

したがって、1>&1 1>&1 1>&1 …複数のオペレーティングシステムの場合、複数のリダイレクトを使用すると、最初のリダイレクト自体は何もしません。ファイル記述子を独自にリダイレクトすることは何もしません(ファイル記述子が閉じられたときにエラーが発生しない限り)。しかし、まだマルチOSリダイレクトと見なされるため、2番目は1>&1入力をコピーし、両方の出力が元の標準出力に送信されるTeeに似たプロセスのパイプを生成します。 3番目は1>&1入力をコピーする別のティープロセスを作成し、両方の出力は最初のティーの入力である標準出力に移動するため、データは2回コピーされます。 4番目は1>&1入力を複製し、2つの出力は2番目のティーに移動し、3回の反復、つまり入力の8倍になります。など。複数のリダイレクトとパイプを使用すると、パイプは1つのリダイレクトとしてカウントされるため、最初は1>&1ファイルディスクリプタ1への2番目のリダイレクトであり、コピーを開始するため8回echo "hello" 1>&1 1>&1 1>&1 | cat印刷されます。helloそれは等しい

( exec 1> >(tee /dev/fd/1 1>&1);
  exec 1> >(tee /dev/fd/1 1>&1);
  exec 1> >(tee /dev/fd/1 1>&1);
  echo "hello" ) | cat

または

{ { { { echo hello; } > >(tee /dev/fd/1 1>&1); } > >(tee /dev/fd/1 1>&1); } > >(tee /dev/fd/1 1>&1); } | tr a-z A-Z

演習 1: 次のコマンドの動作を説明します。

/bin/echo hello 1>&1 1>&1 1>&-

1>&1 1>&1出力を標準出力にコピーします。その後、1>&-標準出力を閉じて書き込むecho場所がないようにして文句を言います。

演習2:これら2つのコマンドの違いを説明してください。

/bin/echo hello 1>&- 1>&2
/bin/echo hello 1>&1 1>&1 1>&- 1>&2

最初のケースでは、ファイル記述子1に閉じられないリダイレクトのみがあるため、multio動作は開始されません。1>&-fd 1を閉じて1>&2通常のリダイレクトなので、コマンドは次helloのようにfd 2に印刷されます/bin/echo 1>&2。 2番目のケースでは、fd 1に複数のリダイレクトがあるため、それを1>&-閉じた後、zshはfd 1に対して複数のリダイレクトを実行します1>&2。これには、現在fd 1に接続されていますが、fd 1が閉じているため、ディスプレイで失敗したティープロセスを分岐することが含まれますmultio failed for fd 1: bad file descriptor

練習2b:説明

/bin/echo hello 1>&1 1>&- 1>&2

ファイル記述子を閉じると、is-the-the-first-redirection状態がリセットされるため、2番目のリダイレクトは/bin/echo hello 1>&2

関連情報