数週間前、「 (方法)バックグラウンドでタスクをサイレントに開始しますか? 」という質問について 奇妙な答え を見ました。このソリューションは正しくないようです( c.f。私の答え )。ただし、シェルはバックグラウンドで静かにタスクを開始しているようです。
提案されたソリューションについての説明はなく、分析は信頼できる回答を提供しません。
以下に、コードスニペットを示します。
# 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
です。
コマンドのグループ({ ... }
)は、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...
前述のように、コマンドはバックグラウンドで静かに開始するようです。より正確には、バックグラウンドジョブに関する通知。 [1] 3842
、は表示されません。
以前の分析は、リダイレクトがキャンセルされる可能性があるため、リダイレクトが不必要であることを示唆しています。
{ redir_3 cmd& } redir_1 redir_2
はcmd&
と同等です。
私の考えでは、上記の構造は副作用のために通知を隠しています。
それがどのように起こるか説明できますか?
Ilkkachuの回答とコメント ある程度の進歩が認められました。各プロセスには独自のファイル記述子があることに注意してください。
別のコンテキスト:サブシェルで実行される非同期リスト。コマンドg
は存在しません。
Prompt% ( g& )
g: command not found
非同期コマンド、括弧でグループ化されたコマンド、...は、シェル環境の複製であるサブシェル環境で実行されます...
サブシェルは、親シェルから標準エラーストリームの値を継承します。
したがって、前のケースでは、それらの標準エラーストリームはターミナルを参照する必要があります。ただし、ジョブの通知は表示されませんが、シェルのエラーメッセージはおそらくシェルの標準エラーに表示されます。
最も重要なポイントを逃しました。シェルリダイレクトは左から右に順に適用されます。
に:
{ 2>&3 "$@"& } 3>&2 2>/dev/null
グループ全体のコマンドは、次のコマンドで実行されます。
したがって、グループ化内のコマンドを実行すると、次のようになります。
したがって、"$@"&
が標準エラーに何かを出力する場合、出力は端末に出力されます。
あなたの具体的なケースについて:
{ 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は、上記で説明したようにターミナルを指しているため、ターミナルに出力されます。
インタラクティブ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
{
コマンドは、ヌルされたstderr
で実行され、含まれているバックグラウンドコマンドを復元されたfdで開始しますが、コマンドを開始し、コマンドで開始されたフィードバックメッセージを発行し、ヌルなもので実行します。