web-dev-qa-db-ja.com

IOリダイレクト-stdoutとstderrの交換

与えられたシェルスクリプト:

#!/bin/sh

echo "I'm stdout";
echo "I'm stderr" >&2;

コマンドの最後の部分が2>/dev/nullの場合、stderrのみが出力されるように、そのスクリプトを呼び出す方法はありますか。

$ > sh myscript.sh SOME_OPTIONS_HERE 2>/dev/null
I'm stderr

または、代わりに:

$ > sh myscript.sh SOME_OPTIONS_HERE >/dev/null
I'm stdout

一連の講義スライドの最後にある質問ですが、これで1日近く働いた後、それはある種のタイプミスであるとほぼ確信しています。ピボットは機能しません。 2>&-は機能しません。私はアイデアがありません!

33
Richard
% (sh myscript.sh 3>&2 2>&1 1>&3) 2>/dev/null
I'm stderr
% (sh myscript.sh 3>&2 2>&1 1>&3) >/dev/null 
I'm stdout

3>&2 2>&1 1>&3の説明:

  • 3>&2は、fd 3(ファイル記述子3)という名前のファイル記述子2(fd 2)(stderr)のコピーを作成を意味します。 ファイル記述子をコピーしますteeのようにストリームを複製しません。
  • 2>&1は、sh myscript.shのfd2がそのfd1(stdout)のコピーになることを意味します。これで、myscriptがstderr(fd 2)に書き込むと、stdout(fd 1)で受信します。
  • 1>&3は、sh myscript.shのfd1がfd3(stderr)のコピーになることを意味します。これで、myscriptがstdout(fd 1)に書き込むと、stderr(fd 2)で受信します。
34
unbeli

完全を期すために、上記の@ 200_successによるコメントに基づいて、1>&3-を使用してファイル記述子3を move する方がおそらく良いでしょう。

$ (sh myscript.sh 3>&2 2>&1 1>&3-) 2>/dev/null
I'm stderr
$ (sh myscript.sh 3>&2 2>&1 1>&3-) >/dev/null 
I'm stdout

プロセスごとにファイル記述子を交換する代わりに、execを使用して、現在のシェルによって起動された次のすべてのコマンドのstdinとstderrを交換できます。

$ (exec 3>&2 2>&1 1>&3- ; sh myscript.sh ; sh myscript.sh ) 2>/dev/null
I'm stderr
I'm stderr
$ (exec 3>&2 2>&1 1>&3- ; sh myscript.sh ; sh myscript.sh ) >/dev/null 
I'm stdout
I'm stdout
12
Sylvain Leroux

ファイル記述子の移動(1>&3-)は移植性がなく、すべてのPOSIXシェル実装がそれをサポートしているわけではありません。それはksh93-ismとbash-ismです。 (詳細はこちら https://unix.stackexchange.com/questions/65000/practical-use-for-moving-file-descriptors

リダイレクトを実行した後、代わりにFD3を閉じることもできます。

ls 3>&2 2>&1 1>&3 3>&-

現在の作業ディレクトリの内容をstderrから出力します。

構文3>&-または3<&-ファイル記述子3を閉じます。

5
Gregory Nisbet

bash hackers wiki は、この種のことで非常に役立ちます。これらの答えの中で言及されていないそれを行う方法があるので、私は私の2セントを入れます。

数値Nの>&Nのセマンティクスは、ファイル記述子Nのtargetにリダイレクトするを意味します。記述子は後でターゲットを変更できるため、Wordtargetは重要ですが、そのターゲットをコピーした後は気にしません。これが、リダイレクトを宣言する順序が適切である理由です。

したがって、次のように実行できます。

./myscript.sh 2>&1 >/dev/null

つまり、次のことを意味します。

  1. stderrをstdoutのターゲット、つまりstdout出力ストリームにリダイレクトします。 stderrがstdoutのターゲットをコピーしました

  2. stdoutを/ dev/nullに変更します。変更する前にターゲットを「コピー」したため、これはstderrには影響しません。

3番目のファイル記述子は必要ありません。

>&-の代わりに>/dev/nullを単純に実行できないのは興味深いことです。これは実際にはstdoutを閉じるので、エラーが発生します(stderrのターゲットでは、もちろん実際のstdoutです:D)

line 3: echo: write error: Bad file descriptor

リダイレクトを交換しようとすると、順序が関連していることがわかります。

./myscript.sh >/dev/null 2>&1

これは機能しません。理由は次のとおりです。

  1. Stdoutのターゲットを/dev/nullに設定しました
  2. Stderrのターゲットをstdoutのターゲット、つまり/dev/nullに設定しました。
5
Dacav