web-dev-qa-db-ja.com

このコマンドを分析する方法 `{2>&3" $ @ "&} 3>&2 2> / dev / null`?

数週間前、「 (方法)バックグラウンドでタスクをサイレントに開始しますか? 」という質問について 奇妙な答え を見ました。このソリューションは正しくないようです( c.f。私の答え )。ただし、シェルはバックグラウンドで静かにタスクを開始しているようです。

I.問題:シェルの標準エラーを実際にリダイレクトできますか?

提案されたソリューションについての説明はなく、分析は信頼できる回答を提供しません。

以下に、コードスニペットを示します。

# Run the command given by "$@" in the background
silent_background() {
   if [[ -n $BASH_VERSION ]]; then
      { 2>&3 "$@"& } 3>&2 2>/dev/null
   fi
}

問題のあるコマンドは{ 2>&3 "$@"& } 3>&2 2>/dev/nullです。

i)分析

コマンドのグループ({ ... })は、1つのコマンド(3>&2)に対して2つのリダイレクト(2>/dev/nullおよび# Run the command given by "$@" in the background)を指定します。コマンドは、1つのリダイレクト(cmd&)を持つ非同期リスト(2>&3)です。

POSIX仕様

各リダイレクトは、そのリダイレクトを明示的に上書きしない複合コマンド内のすべてのコマンドに適用されます。

標準エラーリダイレクト2>/dev/nullは、非同期リスト2>&3に関連付けられたリダイレクトによってオーバーライドされます。

具体的なケース

最初のケースでは、標準エラーは/dev/nullにリダイレクトされますが、2番目のケースでは、commandの標準エラーが引き続きターミナルにアタッチされます。

Prompt% { grep warning system.log& } 2>/dev/null
Prompt% { 2>&3 grep warning system.log& } 3>&2 2>/dev/null
grep: system.log: No such file or directory

以下に、同様のケースを示します。最初のケースでは、コマンドの標準出力がまだ端末に接続されています。 2番目のケースでは、コマンドの標準出力リダイレクトが変更されます。>echo.txt>print.txtによってオーバーライドされます。

Prompt% { >&3 echo some data...& } 3>&1 >echo.txt
[1] 3842
some data...
Prompt% file echo.txt
echo.txt: empty
Prompt% { >&3 echo some data...& } 3>print.txt >echo.txt
[1] 2765
Prompt% file echo.txt
echo.txt: empty
Prompt% cat print.txt
some data...

ii)観察

前述のように、コマンドはバックグラウンドで静かに開始するようです。より正確には、バックグラウンドジョブに関する通知。 [1] 3842、は表示されません。

以前の分析は、リダイレクトがキャンセルされる可能性があるため、リダイレクトが不必要であることを示唆しています。

{ redir_3 cmd& } redir_1 redir_2cmd&と同等です。

iii)解釈

私の考えでは、上記の構造は副作用のために通知を隠しています。

それがどのように起こるか説明できますか?

II。仮説

Ilkkachuの回答とコメント ある程度の進歩が認められました。各プロセスには独自のファイル記述子があることに注意してください。

  • シェルメッセージは、シェルの標準エラーで送信される場合があります。

別のコンテキスト:サブシェルで実行される非同期リスト。コマンドgは存在しません。

Prompt% ( g& )
g: command not found

非同期コマンド、括弧でグループ化されたコマンド、...は、シェル環境の複製であるサブシェル環境で実行されます...

サブシェルは、親シェルから標準エラーストリームの値を継承します。

したがって、前のケースでは、それらの標準エラーストリームはターミナルを参照する必要があります。ただし、ジョブの通知は表示されませんが、シェルのエラーメッセージはおそらくシェルの標準エラーに表示されます。

7
Fólkvangr

最も重要なポイントを逃しました。シェルリダイレクトは左から右に順に適用されます。

に:

{ 2>&3 "$@"& } 3>&2 2>/dev/null

グループ全体のコマンドは、次のコマンドで実行されます。

  • ファイル記述子3 =>標準エラー。この時点ではterminalです。
  • ファイル記述子2(標準エラー)=>/dev/null

したがって、グループ化内のコマンドを実行すると、次のようになります。

  • 標準エラー=>端末を指すファイル記述子3。

したがって、"$@"&が標準エラーに何かを出力する場合、出力は端末に出力されます。


あなたの具体的なケースについて:

{ grep warning system.log& } 2>/dev/null

{ grep warning system.log& }は標準エラーで実行され、/dev/nullがポイントされます。 grepはリダイレクトをオーバーライドしないため、その標準エラーは{...}と同じであり、/dev/nullにリダイレクトされます。ターミナルへの出力はありません。

に:

{ 2>&3 grep warning system.log& } 3>&2 2>/dev/null

grepの標準エラーは、ファイル記述子3にリダイレクトされます。ファイル記述子3は、上記で説明したようにターミナルを指しているため、ターミナルに出力されます。

10
cuonglm

インタラクティブBashでは、foo &fooをバックグラウンドで実行し、そのジョブIDとプロセスIDをシェルの標準エラーに出力します。

foo 2>/dev/null &はバックグラウンドでfooを実行し、stderrを/dev/nullにリダイレクトしますが、ジョブIDとプロセスIDをShell's標準エラー(fooのリダイレクトされたstderrではありません)。

{ foo & } 2>/dev/nullはまずstderrを/dev/nullにリダイレクトし、次に{}の内部がそのリダイレクトを適用して処理されます。次に、fooがバックグラウンドで開始され、ジョブIDとプロセスIDがシェルのリダイレクトされたstderr(つまり、 /dev/null)。

Bashのマニュアルでは、グループ外のリダイレクトがグループ全体に適用されることを示唆しています

コマンドをグループ化すると、リダイレクトがコマンドリスト全体に適用される場合があります。

グループに適用されたリダイレクトがジョブID出力もリダイレクトすることを確認できます。

 $ {睡眠9&} 2>一時
[ここに出力はありません]
 $猫の温度
 [1] 8490 

コマンドが最終的に終了すると、ターミナルからの通知が表示されます(または、シェルの現在のstderrへの通知です)。

$ kill %1
[1]+  Terminated              sleep 99

{ ... } 3>&2 2>/dev/nullでは、fd 3はstderrが今指す場所にリダイレクトされ、次にstderrが/dev/nullにリダイレクトされます。 stderrが最初に端末を指していたとすると、結果は3 -> terminal, 2 -> /dev/nullになります。これらのリダイレクトはグループに適用されます。

グループ内に{ foo 2>&3 & }がある場合、fooはバックグラウンドで開始され、stderrはグループのfd 3を指し、ジョブIDとプロセスIDはグループのstderrに出力されます。

これら2つをまとめると、fooのstderrはグループのfd 3が指す場所、つまり元のstderrを指し、ジョブIDはグループのfd 2、つまり/dev/nullに出力されます。

したがって、実際には、{ 2>&3 foo & } 3>&2 2>/dev/nullはシェルによって出力されたジョブIDとプロセスIDを/dev/nullにリダイレクトし、fooのstderrをこのリダイレクトにルーティングします。

foo's stdout      -> group's fd 1 -> original stdout (never redirected)
foo's stderr      -> group's fd 3 -> original stderr
job id from group -> group's fd 2 -> /dev/null
5
ilkkachu

{コマンドは、ヌルされたstderrで実行され、含まれているバックグラウンドコマンドを復元されたfdで開始しますが、コマンドを開始し、コマンドで開始されたフィードバックメッセージを発行し、ヌルなもので実行します。

1
jthill