Wie übergebe ich 2> / dev / null als Variable?

13

Ich habe diesen Code, der funktioniert:

# Hide irrelevant errors so chrome doesn't email us in cron
if [[ $fCron == true ]] ; then
    google-chrome --headless --disable-gpu --dump-dom \
        "$RobWebAddress" > "$DownloadName" 2>/dev/null
else
    # Get silly error messages when running from terminal
    google-chrome --headless --disable-gpu --dump-dom \
        "$RobWebAddress" > "$DownloadName"
fi

Wenn ich versuche, es so zu verkürzen:

# Hide irrelevant errors so chrome doesn't email us in cron
local HideErrors
[[ $fCron == true ]] && HideErrors="2>/dev/null"

google-chrome --headless --disable-gpu --dump-dom \
    "$RobWebAddress" > "$DownloadName" "$HideErrors"

Ich erhalte Fehlermeldungen:

[0826/043058.634775:ERROR:headless_shell.cc(597)] Open multiple tabs is only supported when remote debugging is enabled.
[0826/043058.672587:ERROR:headless_shell.cc(597)] Open multiple tabs is only supported when remote debugging is enabled.
[0826/043058.711640:ERROR:headless_shell.cc(597)] Open multiple tabs is only supported when remote debugging is enabled.
(... SNIP ...)

Warum funktioniert ein hartcodiertes Argument, aber kein Argument als Variable?


Bearbeiten 2:

Momentan fand ich Erfolg mit dem alternativen Vorschlag der zweiten Antwort:

# Redirect errors when cron is used to /dev/null to reduce emails
ErrorPipe=/dev/stderr
[[ $fCron == true ]] && ErrorPipe=/dev/null

google-chrome --headless --disable-gpu --dump-dom \
                "$RobWebAddress" > "$DownloadName" 2>"$ErrorPipe"

Bearbeiten 1:

Anhand der ersten Antwort sollte ich darauf hinweisen, dass der Programm-Header bereits Folgendes enthält:

[[ $fCron != true ]] &&
    exec 2> >(grep -v 'GtkDialog mapped without a transient parent' >&2)
WinEunuuchs2Unix
quelle
Sie können es [[ $fCron == true ]] && exec 2>/dev/nullstattdessen versuchen
steeldriver
Das liegt grob gesagt daran, dass die Shell Umleitungen einrichtet, bevor Variablen erweitert werden, denke ich. Siehe zum Beispiel bash: Verwenden Sie eine Variable, um stderr | stdout Redirection zu speichern
steeldriver

Antworten:

19

Der Grund, warum Sie keine Umleitung durch Erweitern verursachen können, "$HideErrors"ist, dass Symbole wie >nicht speziell behandelt werden, nachdem sie durch Parametererweiterung erstellt wurden . Dies ist tatsächlich sehr gut, da solche Symbole in Text enthalten sind, den Sie möglicherweise erweitern und wörtlich verwenden möchten.

Dies gilt unabhängig davon, ob Sie zitieren oder nicht $HideErrors. Das Ergebnis der Parametererweiterung unterliegt der Wortteilung und dem Globbing, wenn die Erweiterung nicht in Anführungszeichen gesetzt ist, aber das war's.


Es gibt zahlreiche Möglichkeiten, eine bedingte Umleitung zu erreichen. Für einen sehr einfachen Befehl, kann es den ganzen Befehl zweimal angemessene Schreib sein, einmal in jedem Zweig eines caseoder if- elseKonstrukt. Dies wird jedoch bald lästig, und der Befehl, den Sie gezeigt haben, ist sicherlich ein Fall, in dem dies nicht ideal wäre.

Von den Ansätzen, mit denen Sie vermeiden können, sich zu wiederholen , sind zwei besonders zu empfehlen, da sie recht sauber und leicht zu finden sind. Sie möchten nur einen dieser Befehle verwenden, nicht beide gleichzeitig für denselben Befehl und dieselbe Umleitung.

Speichern Sie den Befehl anstelle der Umleitung. Anstatt zu versuchen, die Umleitung in einer Variablen zu speichern und die Parametererweiterung anzuwenden, speichern Sie den Befehl in einer Shell-Funktion . Dann schreiben Sie ein caseoder if- else, in dem die Funktion mit der Umleitung auf einen Zweig und ohne auf den anderen Zweig aufgerufen wird.

Wenn Sie Ihren Befehl als Code konzipieren, den Sie einmal schreiben, aber unter mehreren Umständen ausführen möchten, ist eine Funktion die natürliche Lösung. Das ist was ich normalerweise tue. Es hat den Vorteil, dass weder eine Unterschale noch ein manuelles Speichern und Zurücksetzen des Zustands erforderlich sind .

Mit deinem Code:

launch() {
    google-chrome --headless --disable-gpu --dump-dom \
        "$RobWebAddress" > "$DownloadName"
}

case $fCron in
true)  launch 2>/dev/null;;
*)     launch;; # Get silly error messages when running from terminal
esac

Sie können einen beliebigen Abstand verwenden oder if- elsewenn Sie möchten - einen anderen. Beachten Sie, dass launchder Aufrufer RobWebAddressund die DownloadNameVariablen automatisch verwendet werden, auch wenn es sich um lokale Variablen handelt, da Bash im Gegensatz zu den meisten lexikalischen Programmiersprachen einen dynamischen Gültigkeitsbereich hat.

Führen Sie den Befehl in einer Subshell aus und wenden Sie die Umleitung unter bestimmten Bedingungen auf an exec. Dies ist, was Stahlfahrer kommentierte , aber innen ( ), um den Effekt lokal zu halten . Wenn das execBuiltin ohne Argumente ausgeführt wird, ersetzt es die aktuelle Shell nicht durch einen neuen Prozess, sondern wendet stattdessen eine der Umleitungen auf die aktuelle Shell an.

(Es ist auch möglich, den Stand des Standardfehlers zu verfolgen und ihn wiederherzustellen, ohne eine Subshell zu verwenden und damit die Fähigkeit zu verlieren, die aktuelle Shell-Umgebung zu ändern. Die Details dazu überlasse ich jedoch anderen Antworten.)

Mit deinem Code:

(
    # Suppress silly error messages unless running from terminal
    case $fCron in true) exec 2>/dev/null;; esac

    google-chrome --headless --disable-gpu --dump-dom \
        "$RobWebAddress" > "$DownloadName"
)

Nach dem Schließen )wird der Standardfehler auf den vorherigen Stand zurückgesetzt, da er nur in der Subshell und nicht in der übergeordneten Shell umgeleitet wird. Auch dies funktioniert problemlos mit den vorhandenen Shell-Variablen, da Subshells eine Kopie davon erhalten. Obwohl ich lieber eine Shell-Funktion verwende, muss ich zugeben, dass für diese Methode möglicherweise weniger Code erforderlich ist.

Beide Methoden funktionieren unabhängig davon, als welcher Datei- oder Gerätestandardfehler gestartet wird, einschließlich im Fall von Umleitungen, die auf Shell-Funktionen angewendet werden, die den Code aufrufen, der das bedingte Verhalten enthält, sowie im Fall (in Ihrer Bearbeitung erwähnt), in dem Standardfehler für Das gesamte Skript wurde bereits von einem vorherigen oder umgeleitet . Dass der Weg durch Prozesssubstitution entstanden ist, ist kein Problem.exec 2>&fdexec 2> path

Eliah Kagan
quelle
Zu Ihrer Information, SteelDriver erwähnte etwas über execweiß nicht, ob er eine Antwort darauf plant ...
WinEunuuchs2Unix
@ WinEunuuchs2Unix Ich hoffe eine solche Antwort wird noch gepostet. Obwohl ich hauptsächlich die Verwendung einer Funktion empfehle, habe ich auch eine Methode eingefügt, die eine Umleitung auf beinhaltet exec. Aber, wie ich in Klammern erwähnte, habe ich mich nicht mit anspruchsvolleren Anwendungen befasst, bei denen der alte Dateideskriptor ohne Subshell beibehalten und wiederhergestellt wird. Ich habe mich auch nicht mit weniger anspruchsvollen Anwendungen befasst, wie zum Beispiel die Weiterleitung beizubehalten, wenn dies das Ende des Skripts ist. Eine andere Antwort, wenn sie veröffentlicht wird, könnte beides und vielleicht auch mehr abdecken.
Eliah Kagan
Ich habe meine Frage mit einer vorhandenen Frage aktualisiert, die sich meiner execMeinung nach überhaupt nicht auf Ihre Antwort auswirken sollte.
WinEunuuchs2Unix
@ WinEunuuchs2Unix Ja, das sollte kein Problem sein. Ich habe am Ende der Antwort einen Absatz hinzugefügt.
Eliah Kagan
Interessante Offenbarung, die Ihre Antwort liest, RobWebAddressist definitiv globaler Kontext. DownloadNamewurde lokal definiert, sollte aber globaler Kontext sein. Aus irgendeinem Grund erben DownloadNameDownloadAsHTML ()UpdateOne ()
untergeordnete
4

Warum funktioniert ein hartcodiertes Argument, aber kein Argument als Variable?

Da Syntaxelemente nicht aus erweiterten Variablenwerten interpretiert werden. Das heißt, die Variablenerweiterung ist nicht dasselbe wie das Ersetzen der Variablenreferenz durch den Text der Variablen in der Befehlszeile. (Sachen wie ;, |, &&und Zitate usw. sind auch keine speziellen in den Werten der Variablen.)

Sie können Aliase verwenden oder die Variable verwenden, um nur das Ziel der Umleitung zu speichern.

Aliase sind nur ein Textersatz und können syntaktische Elemente wie Operatoren und Schlüsselwörter enthalten. In einem Skript müssen shopt expand_aliasesSie dies tun, da diese standardmäßig in nicht interaktiven Shells deaktiviert sind. Das druckt also 2(nur):

#!/bin/bash
shopt -s expand_aliases

alias redir='> /dev/null'
redir echo 1
alias redir=''
redir echo 2

(Und Sie könnten auch alias jos=if niin=then soj=fiund dann alle Ihre if-Anweisungen auf Finnisch schreiben. Ich bin sicher, dass jeder, der das Skript liest, Sie lieben würde.)

Alternativ können Sie die Umleitung immer schreiben, aber nur das Ziel mit einer Variablen steuern. Sie benötigen ein No-Op-Ziel für den Fall, dass Sie nicht ändern möchten, wohin die Ausgabe geht, aber /dev/stderrin diesem Fall funktionieren sollte. Tatsächlich ist das Hinzufügen 2> /dev/stderrkein No-Op, da Linux geöffnete FDS /proc/<pid>/fdals unabhängig vom Original behandelt. Dies wirkt sich auf die Positionierung der Schreibposition aus und bringt die Ausgabe durcheinander, wenn sie in eine reguläre Datei verschoben wird.

Es sollte jedoch im Append-Modus funktionieren (oder wenn stderr zu einer Pipe oder zu einem Terminal geht):

#!/bin/sh
exec 2>/tmp/error.log
dst=/dev/null
ls -l /nosuchfile-1 2>> "$dst"     # this doesn't print
dst=/dev/stderr
ls -l /nosuchfile-2 2>> "$dst"
ls -l /nosuchfile-3 2>> "$dst"

Also zu wiederholen: 2> /dev/stderrkann brechen.

ilkkachu
quelle
Hahaha, ich werde von nun an nur noch finnische Ifs verwenden. :>
Dessert
Ich mag alternative Vorschläge. Der Gedanke an expand_aliasesist beängstigend, weil Ihr Programm ~/.bashrcmeiner Meinung nach als Geisel gehalten werden kann .
WinEunuuchs2Unix
1
@ WinEunuuchs2Unix, ja, expand_aliasesist ein bisschen gruselig. Sollte aber ~/.bashrckein Problem sein, da es nur von interaktiven Shells gelesen wird und .profileund Freunde, die es möglicherweise aufrufen, nur von Login-Shells gelesen werden. Nicht interaktive, nicht angemeldete Shells wie Skripte sollten keine dieser Shells ausführen. (Aber dann gibt es $BASH_ENV, und anscheinend .bashrcwird gelesen, ob stdin an einen Netzwerk-Socket angeschlossen ist. Wie
kompliziert
Nun, ich verstehe Ihren alternativen Vorschlag perfekt und werde es heute Abend versuchen :)
WinEunuuchs2Unix
Ehrlich gesagt bin ich mir nicht sicher, auf welche Weise ich das implementieren würde, wenn ich müsste. Ich würde den Befehl wahrscheinlich in einer Funktion oder einem Array speichern und dann verzweigen, um zu entscheiden, ob die Umleitung dort abgelegt werden soll (die Verwendung einer Funktion wurde in der anderen Antwort gezeigt). Oder dieser 2>> "$dst"Trick, aber mir ist nur klar geworden, dass er im allgemeinen Fall nicht funktioniert, also sei besser vorsichtig damit.
Ilkkachu
1

Fragentitel: "Wie übergebe ich 2> / dev / null als Variable?" Dies kann tatsächlich mit erfolgeneval

joshua@nova:/tmp$ X=">/dev/null"
joshua@nova:/tmp$ echo $X
>/dev/null
joshua@nova:/tmp$ eval echo $X
joshua@nova:/tmp$ eval echo hi
hi
joshua@nova:/tmp$ eval echo hi $X
joshua@nova:/tmp$ echo hi $X
hi >/dev/null
joshua@nova:/tmp$ 

So können wir als umschreiben

# Hide irrelevant errors so chrome doesn't email us in cron
local HideErrors
local RobWebAddress2
local DownloadName2
[[ $fCron == true ]] && HideErrors="2>/dev/null"
RobWebAddress2='"$RobWebAddress"'
DownloadName2='>"$DownloadName"'

eval google-chrome --headless --disable-gpu --dump-dom \
    $RobWebAddress2 $DownloadName2 "$HideErrors"

Wobei der indirekte Variablenzugriff verhindert, dass die Erweiterung im Rest der Befehlszeile zu früh erfolgt.

Doppelte Anführungszeichen in Variablen funktionieren einwandfrei.

joshua@nova:/tmp$ X='"'
joshua@nova:/tmp$ Y='$X'
joshua@nova:/tmp$ eval echo $Y
"
joshua@nova:/tmp$ 
Joshua
quelle
@EliahKagan: Fragentitel: "Wie übergebe ich 2> / dev / null als Variable?"
Joshua
Ok, es hat nicht funktioniert. Ich habe es repariert.
Joshua
Jetzt heißt die Datei immer - und der DownloadNameLiteraltext RobWebAddresswird immer für die URL verwendet. Sie verwenden $" "Zitate . Ich denke, das ist vielleicht unbeabsichtigt und du willst das $s in der " ", aber du hast es an beiden Orten so gemacht, also bin ich mir nicht sicher. Ich denke, > "$DownloadName"sollte es einfach beheben. Aber ich verstehe, dass Sie das vielleicht nicht mögen, da das versehentliche Vermischen von Argumenten und Nichtargumenten evalein Grund dafür ist, dass es so gefährlich und weitgehend entmutigt ist, evaldas Verkettungsverhalten von Argumenten zu verwenden .
Eliah Kagan
@EliahKagan: Oh. Meine bevorzugte Shell für Skripte "" enthält keine Anführungszeichen.
Joshua
1
Wenn Sie das beheben, sollte es funktionieren. Und ich habe mich immer geirrt, wenn ich dachte, es würde Anführungszeichen für beliebigen Text einfügen! Es werden literale Argumente erstellt, die evalvor der Auswertung verkettet werden. Aber ich denke, eine andere Art, es auszudrücken, ist, dass es eine verschleierte Art des Schreibens ist eval 'google-chrome --headless --disable-gpu --dump-dom "$RobWebAddress" > "$DownloadName" '"$HideErrors", die dem Erscheinungsbild des OP-Codes ähnelt . Und im Allgemeinen ist die Verwendung evalfür Aufgaben, die nicht benötigt werden, schlecht . (Keine dieser Ausreden - noch erklärt - die Unrichtigkeit und Feindseligkeit meiner alten Antwort.)
Eliah Kagan