Vor einigen Wochen habe ich eine seltsame Antwort auf die Frage " (Wie) starte Aufgabe (n) stillschweigend im Hintergrund? " Gesehen . Diese Lösung scheint falsch zu sein ( siehe meine Antwort ), obwohl die Shell die Aufgabe im Hintergrund still zu starten scheint.
I. Problem: Können wir den Shell-Standardfehler tatsächlich umleiten?
Es gibt keine Erklärung für die vorgeschlagene Lösung und eine Analyse liefert keine verlässliche Antwort.
Unten sehen Sie das Code-Snippet.
# Run the command given by "$@" in the background
silent_background() {
if [[ -n $BASH_VERSION ]]; then
{ 2>&3 "$@"& } 3>&2 2>/dev/null
fi
}
Der problematische Befehl ist { 2>&3 "$@"& } 3>&2 2>/dev/null
.
i) Analysieren
Die Befehlsgruppe ( { ... }
) gibt zwei Umleitungen ( 3>&2
und 2>/dev/null
) für einen Befehl ( # Run the command given by "$@" in the background
) an. Der Befehl ist eine asynchrone Liste ( cmd&
) mit einer Umleitung ( 2>&3
).
POSIX-Spezifikation
Jede Umleitung gilt für alle Befehle innerhalb des zusammengesetzten Befehls, die diese Umleitung nicht explizit überschreiben.
Die Standardfehlerumleitung 2>/dev/null
wird durch die der asynchronen Liste zugeordnete Umleitung überschrieben 2>&3
.
Konkrete Fälle
Im ersten Fall wird der Standardfehler umgeleitet, /dev/null
während im zweiten Fall der Standardfehler des Befehls weiterhin an das Terminal angehängt wird.
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
Unten sehen wir ähnliche Fälle. Im ersten Fall ist die Standardausgabe des Befehls weiterhin an das Terminal angehängt. Im zweiten Fall wird die Standardausgabeumleitung des Befehls geändert: >echo.txt
wird von überschrieben >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) Beobachtungen
Wie bereits erwähnt, scheint der Befehl im Hintergrund lautlos zu starten. Genauer gesagt wird die Benachrichtigung über einen Hintergrundjob, z. B. [1] 3842
, nicht angezeigt.
Die vorherige Analyse impliziert, dass die Weiterleitungen überflüssig sind, da sie möglicherweise abgebrochen werden.
{ redir_3 cmd& } redir_1 redir_2
ist äquivalent zu cmd&
.
iii) Interpretation
In meinen Augen verbirgt das erwähnte Konstrukt die Benachrichtigung aufgrund einer Nebenwirkung.
Können Sie erklären, wie das passiert?
II. Hypothese (s)
Ilkkachus Antwort und Kommentare ermöglichten einige Fortschritte. Beachten Sie, dass jeder Prozess seine eigenen Dateideskriptoren hat.
- Die Shell-Nachrichten können über den Shell-Standardfehler gesendet werden.
Ein weiterer Kontext: Eine asynchrone Liste, die in einer Subshell ausgeführt wird. Der Befehl g
existiert nicht.
prompt% ( g& )
g: command not found
Asynchrone Befehle, Befehle in Klammern, ..., werden in einer Subshell-Umgebung ausgeführt, die ein Duplikat der Shell-Umgebung ist ...
Eine Subshell erbt den Wert ihres Standardfehlerstroms von ihrer übergeordneten Shell.
Daher sollten sich im vorherigen Fall ihre Standardfehlerströme auf das Terminal beziehen. Die Jobbenachrichtigung wird jedoch nicht angezeigt, während die Shell-Fehlermeldung wahrscheinlich im Shell-Standardfehler angezeigt wird.
quelle
print [n] nnnnn
zum Terminal ist. Wobei $ n $ und $ nnnnn $ Zahlen sind.( ( g & ) )
Beispiel von dem oben angeforderten unterscheidet, da in Klammern eine Unterschale eingerichtet ist und keine Unterschale vorhanden ist{ 2>&3 "$@"& } 3>&2 2>/dev/null
.()
startet immer noch eine Unterschale, auch wenn es nur ein Paar gibt. Die Zahnspangen{}
nicht. Soweit ichfork()
weiß, ist Bash immer beim Starten einer Subshell, und das macht Hintergrundprozesse, die von Subshells gestartet werden, etwas anders, da sie keine untergeordneten Elemente des Haupt-Shell-Prozesses sind. Sie werden auch nicht zB in der Ausgabejobs
von der Haupt-Shell usw. angezeigt (der Punkt der Frage, auf die Sie verlinken). Anscheinend macht sich Bash auch in diesem Fall nicht die Mühe, die Job-ID-Zeile auszudrucken. Das ist eine ganz andere Situation als beim{ foo & }
Umleitungstanz.{ 2>&3 "$@" & } 3>&2 2>/dev/null
ist, die Job-ID-Ausgabezeile loszuwerden, die die Subshell( "$@" & )
überhaupt nicht druckt ...Antworten:
Sie haben den wichtigsten Punkt verpasst. Die Shell-Umleitung wird in der Reihenfolge von links nach rechts angewendet.
Im:
Der gesamte Gruppenbefehl wird ausgeführt mit:
Wenn also ein Befehl innerhalb der Gruppierung ausgeführt wird:
Wenn Sie also
"$@"&
etwas mit dem Standardfehler drucken, wird die Ausgabe auf das Terminal gedruckt.Für Ihre konkreten Fälle:
{ grep warning system.log& }
Läufe mit Standardfehler wird angezeigt/dev/null
.grep
überschreibt keine Umleitung, daher ist der Standardfehler derselbe mit{...}
und wird umgeleitet/dev/null
, wenn Sie keine Ausgabe an das Terminal erhalten haben.Im:
grep
Der Standardfehler wird an den Dateideskriptor 3 umgeleitet, der wie oben erläutert auf das Terminal zeigt, sodass Sie eine Ausgabe an das Terminal erhalten.quelle
[1] 3842
nicht angezeigt?{ 2>&3 grep warning system.log& } 3>&2 2>/dev/null
Linie laufen ? Ich nicht.( sleep 10 & )
druckt nichts und ist nicht in derjobs
Liste enthalten. Etwas schneller zu tippensleep 10& disown -r
. Möglicherweise benötigen Sie Umleitungen, um einen Befehlsblock zu vermeiden, wenn versucht wird, von stdin zu lesen.In der interaktiven Bash,
foo &
läuftfoo
im Hintergrund und druckt seinen Job - ID und Prozess - ID an den Standardfehler des Shell.foo 2>/dev/null &
wirdfoo
im Hintergrund ausgeführt, wobei der stderr an umgeleitet/dev/null
wird, druckt jedoch weiterhin die Job-ID und die Prozess-ID an den Standardfehler der Shell (nicht an den umgeleiteten stderr vonfoo
).{ foo & } 2>/dev/null
Leitet zuerst stderr an um/dev/null
, dann wird das Innere von{}
mit dieser Umleitung verarbeitet. Dannfoo
wird im Hintergrund gestartet, und die Job-ID und die Prozess-ID werden auf den jetzt umgeleiteten stderr der Shell (dh bis/dev/null
) gedruckt .Das Handbuch von Bash impliziert, dass die Weiterleitungen außerhalb der Gruppe für die gesamte Gruppe gelten :
Wir können überprüfen, ob die von der Gruppe angewendete Umleitung auch die Job-ID-Ausgabe umleitet:
Wenn der Befehl schließlich beendet wird, erscheint der Hinweis darauf jedoch auf dem Terminal: (Oder besser gesagt, auf dem aktuellen stderr der Shell, was auch immer das ist.)
In
{ ... } 3>&2 2>/dev/null
wird fd 3 dorthin umgeleitet, wo immer stderr auf jetzt zeigt, dann wird stderr dorthin umgeleitet/dev/null
. Unter der Annahme, dass stderr ursprünglich auf das Terminal zeigte, ist das Ergebnis3 -> terminal, 2 -> /dev/null
. Diese Weiterleitungen gelten für die Gruppe.Wenn wir
{ foo 2>&3 & }
innerhalb der Gruppe haben,foo
wird im Hintergrund gestartet, wobei sein stderr auf den fd 3 der Gruppe zeigt, und die Job-ID und die Prozess-ID werden auf den stderr der Gruppe gedruckt.Wenn Sie diese beiden zusammenfügen, zeigt
foo
der stderr auf die Stelle, auf die der fd 3 der Gruppe zeigt, dh auf den ursprünglichen stderr, und die Job-ID wird auf den fd 2 der Gruppe gedruckt, d/dev/null
. H.Leitet also in der Tat
{ 2>&3 foo & } 3>&2 2>/dev/null
die von der Shell gedruckte Job-ID und Prozess-ID an um/dev/null
und leitetfoo
den Stderr um diese Umleitung herum weiter:quelle
{ 2>&3 foo & } 3>&2 2>/dev/null
Tricks stützen. FWIW, Bash 3.2, Bash 4.4 und ksh93 sind in dieser Hinsicht ähnlich.g
betrifft, dass der Befehl nicht ausgeführt werden kann, wird er nach dem Punkt gedruckt, an dem erg
tatsächlich gestartet worden wäre , da der einzige Weg, um zu wissen, dass das Finaleexecve
fehlschlägt, darin besteht, dass er einen Fehler zurückgibt. Und zu diesem Zeitpunkt sind alle Umleitungen fürg
bereits eingerichtet: Sieg
müssen gestartet worden sein und alle zu diesem Zeitpunkt eingerichteten Dateideskriptoren erhalten haben, wenn kein Fehler aufgetreten wäre. Die Job-ID und die Prozess-ID eines Hintergrundprogramms sind der übergeordneten Shell jedoch bekannt, bevor sie sichg
selbst einrichtet.{ ... }
, eine Subshell( ... )
oderif
/for
/while
...), und die Shell muss die Dateideskriptoren entsprechend jonglieren. Außerdem scheinen alle Beweise darauf hinzudeuten, dass Bash tatsächlich die Job-ID-Nachricht in das zu diesem Zeitpunkt gültige stderr schreibt, das für den umgebenden zusammengesetzten Befehl festgelegt wurde. (Sie könntenif true; then somecmd & fi 2>/dev/null
auf die gleiche Weise wie verwenden{ somecmd & } 2>/dev/null
.)Sie vermissen, dass der
{
Befehl mit einem Nullwert ausgeführtstderr
wird. Er startet die enthaltenen Hintergrundbefehle mit dem wiederhergestellten fd, aber er startet die Befehle und gibt die vom Befehl gestarteten Rückmeldungsnachrichten aus. Er wird mit dem Nullwert ausgeführt.quelle
printf 2>/dev/null >&2 hello &
, wird die vom Job gestartete Nachricht an das stderr der ausstellenden Umgebung und nicht an das stderr des gestarteten Jobs gesendet.exec 2>/dev/null
einen Job ausgeben und dann in den Hintergrund stellen, werden die Monitormeldungen weiterhin angezeigt. Dies gilt auch dann, wenn ich mitzsh 2>/dev/null
Bash beginne .