Come passare 2> / dev / null come variabile?


13

Ho questo codice che funziona:

# 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

Se provo ad accorciarlo in questo modo:

# 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"

Ricevo messaggi di errore:

[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 ...)

Perché funziona un argomento hardcoded ma non un argomento come variabile?


Modifica 2:

Attualmente ho trovato successo con il suggerimento alternativo della seconda risposta:

# 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"

Modifica 1:

Sulla base della prima risposta, devo sottolineare che l'intestazione del programma contiene già:

[[ $fCron != true ]] &&
    exec 2> >(grep -v 'GtkDialog mapped without a transient parent' >&2)

Puoi [[ $fCron == true ]] && exec 2>/dev/nullinvece provare
steeldriver il

.. in parole povere, è perché la shell imposta reindirizzamenti prima di espandere le variabili, penso. Vedi ad esempio bash: usa una variabile per memorizzare il reindirizzamento stderr | stdout
steeldriver

Risposte:


19

Il motivo per cui non è possibile provocare il reindirizzamento espandendosi "$HideErrors"è che simboli come >non vengono trattati in modo speciale dopo essere stati prodotti dall'espansione dei parametri . Questo è in realtà molto buono, perché tali simboli appaiono nel testo che potresti voler espandere e usare letteralmente.

Questo vale se citate o meno $HideErrors. Il risultato dell'espansione dei parametri è soggetto alla suddivisione delle parole e al globbing quando l'espansione non è quotata, ma il gioco è fatto.


Per quanto riguarda cosa fare al riguardo, ci sono numerosi modi per ottenere il reindirizzamento condizionale. Per un comando molto semplice, può essere ragionevole scrittura tutta comando due volte, una volta in ogni ramo di un caseo if- elsecostrutto. Questo diventa presto oneroso, tuttavia, e il comando che hai mostrato è certamente un caso in cui non sarebbe l'ideale.

Degli approcci che ti consentono di evitare di ripeterti , ce ne sono due che raccomando in particolare, perché sono abbastanza puliti e facili da ottenere. Vorresti usare solo uno di questi, non entrambi contemporaneamente per lo stesso comando e reindirizzamento.

Memorizza il comando anziché il reindirizzamento. Invece di tentare di memorizzare il reindirizzamento in una variabile e applicare l'espansione dei parametri, memorizzare il comando in una funzione shell . Quindi scrivere un caseo if- else, in cui la funzione viene chiamata con il reindirizzamento su un ramo e senza di essa sull'altro.

Se concettualizzi il tuo comando come codice che vuoi scrivere una volta ma eseguito in più circostanze, una funzione è la soluzione naturale. Questo è quello che faccio di solito. Ha il vantaggio di non richiedere né una subshell né una memorizzazione manuale e il ripristino dello stato.

Con il tuo codice:

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

Puoi applicare la spaziatura che preferisci o if, elseinvece, se preferisci. Nota che launchusa automaticamente il chiamante RobWebAddresse le DownloadNamevariabili, anche se sono variabili locali, perché Bash ha un ambito dinamico , a differenza della maggior parte dei linguaggi di programmazione che hanno un ambito lessicale.

Esegui il comando in una subshell e applica condizionalmente il reindirizzamento a exec. Questo è ciò di cui Steeldriver ha commentato , ma all'interno ( )per mantenere l'effetto locale . Quando il execcomando incorporato viene eseguito senza argomenti, non sostituisce la shell corrente con un nuovo processo, ma applica invece i suoi reindirizzamenti alla shell corrente.

(È anche possibile tenere traccia dell'errore standard e ripristinarlo, senza utilizzare una subshell e quindi senza sacrificare la possibilità di modificare l'ambiente della shell corrente. Tuttavia, lascerò i dettagli di questo ad altre risposte.)

Con il tuo codice:

(
    # 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"
)

Dopo la chiusura ), l'errore standard viene in effetti ripristinato su qualsiasi cosa fosse prima, perché viene realmente reindirizzato solo nella subshell e non nella shell genitore. Anche questo funziona bene con le variabili shell esistenti, poiché i subshells ne ottengono una copia. Anche se preferisco usare una funzione di shell, ammetto che questo metodo potrebbe richiedere meno codice.

Entrambi i metodi funzionano indipendentemente dall'errore standard del file o del dispositivo, incluso nel caso di reindirizzamenti applicati alle funzioni della shell che chiamano il codice che contiene il comportamento condizionale, nonché il caso (menzionato nella modifica) in cui l'errore standard per l'intero script è già stato reindirizzato da un precedente o . Che il percorso sia stato prodotto dalla sostituzione del processo non è un problema.exec 2>&fdexec 2> path


FYI SteelDriver ha menzionato qualcosa di execnon so se sta pianificando una risposta su questo ...
WinEunuuchs2Unix

@ WinEunuuchs2Unix Spero che questa risposta sia ancora pubblicata. Anche se raccomando principalmente di utilizzare una funzione, ho anche incluso un metodo che prevede un reindirizzamento attivo exec. Ma, come ho detto tra parentesi, non ho coperto applicazioni più sofisticate in cui il vecchio descrittore di file è conservato e ripristinato senza una subshell. Inoltre non ho coperto applicazioni meno sofisticate, come mantenere il reindirizzamento se questa è la fine della sceneggiatura. Un'altra risposta, se pubblicata, potrebbe riguardare entrambi, e forse di più.
Eliah Kagan,

Ho aggiornato la mia domanda con una esistente execche non dovrebbe avere alcun impatto sulla tua risposta.
WinEunuuchs2Unix,

@ WinEunuuchs2Unix Sì, questo non dovrebbe essere un problema. Ho aggiunto un paragrafo a riguardo alla fine della risposta.
Eliah Kagan,

Rivelazione interessante che legge la tua risposta, RobWebAddressè sicuramente un contesto globale. DownloadNameè stato definito locale ma dovrebbe essere un contesto globale. Per qualche ragione le funzioni figlio ereditano le definizioni locali dei genitori (per intenderci DownloadNameera visibile la DownloadAsHTML ()funzione chiamata dalla UpdateOne ()funzione che lo definiva locale. È stata una giornata difficile :(
WinEunuuchs2Unix

4

Perché funziona un argomento hardcoded ma non un argomento come variabile?

Perché gli elementi di sintassi non sono interpretati da valori di variabili espansi. Cioè, l'espansione della variabile non è la stessa della sostituzione del riferimento della variabile con il testo della variabile nella riga di comando. (Roba come ;, |, &&e le citazioni, ecc sono, inoltre, non speciale nei valori delle variabili.)

Quello che potresti fare è usare gli alias o usare la variabile per contenere solo il target del reindirizzamento.

Gli alias sono solo una sostituzione del testo, quindi possono contenere elementi sintattici, come operatori e parole chiave. In uno script, è necessario shopt expand_aliases, poiché per impostazione predefinita sono disabilitati nelle shell non interattive. Quindi, questo stampa 2(solo):

#!/bin/bash
shopt -s expand_aliases

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

(E potresti anche alias jos=if niin=then soj=fie poi scrivere tutte le tue dichiarazioni if ​​in finlandese. Sono sicuro che chiunque legga la sceneggiatura ti adorerebbe.)

In alternativa, scrivi sempre il reindirizzamento, ma controlla solo il target con una variabile. Avrai bisogno di un obiettivo no-op per il caso in cui non desideri cambiare la destinazione dell'output, ma /dev/stderrin questo caso dovrebbe funzionare. In realtà, l'aggiunta 2> /dev/stderrnon è una no-op a causa del modo in cui Linux tratta le fd aperte da /proc/<pid>/fdcome indipendenti dall'originale. Ciò influisce sul posizionamento della posizione di scrittura e incasinerà l'output se si passa a un file normale.

Dovrebbe funzionare in modalità append, tuttavia (o se stderr va in una pipe o in un terminale):

#!/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"

Quindi, per ripetere: 2> /dev/stderrpuò rompere.


Hahaha, da ora in poi userò solo finlandesi al lavoro. :>
dessert

Mi piacciono i suggerimenti alternativi. L' idea expand_aliasesè spaventosa perché ~/.bashrccredo che il tuo programma possa essere tenuto in ostaggio .
WinEunuuchs2Unix,

1
@ WinEunuuchs2Unix, sì, expand_aliasesè un po 'spaventoso. Ma ~/.bashrcnon dovrebbe essere un problema poiché viene letto solo da shell interattive .profilee gli amici che potrebbero chiamarlo vengono letti solo da shell di login. Le shell non interattive non di login come gli script non devono eseguire nessuna di queste. (Ma poi c'è $BASH_ENV, e apparentemente .bashrcviene letto se lo stdin è collegato a un socket di rete. Quanto può essere contorto ...)
ilkkachu

Bene, capisco perfettamente il tuo suggerimento alternativo e lo proverò stasera :)
WinEunuuchs2Unix

Onestamente, non sono sicuro in che modo implementerei questo se dovessi. Probabilmente memorizzerei il comando in una funzione o in un array e quindi ramo per decidere se inserire il reindirizzamento lì (utilizzando una funzione è stata mostrata nell'altra risposta). O quel 2>> "$dst"trucco, ma ho appena capito che non funziona nel caso generale, quindi è meglio stare attenti.
ilkkachu,

1

Titolo della domanda: "Come passare 2> / dev / null come variabile?" Questo può effettivamente essere fatto usandoeval

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$ 

Quindi possiamo riscrivere come

# 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"

Dove l'accesso indiretto alla variabile impedisce l'espansione troppo presto sul resto della riga di comando.

Le virgolette doppie nelle variabili funzionano bene.

joshua@nova:/tmp$ X='"'
joshua@nova:/tmp$ Y='$X'
joshua@nova:/tmp$ eval echo $Y
"
joshua@nova:/tmp$ 

@EliahKagan: Titolo della domanda: "Come passare 2> / dev / null come variabile?"
Giosuè,

Ok non funzionava. L'ho risolto.
Giosuè,

Ora il file viene sempre denominato DownloadName- e il testo letterale RobWebAddressviene sempre utilizzato per l'URL. Stai usando il $" "preventivo . Penso che questo possa essere involontario e potresti volere le $s dentro " ", ma l'hai fatto in entrambi i posti, quindi non ne sono sicuro. Penso che > "$DownloadName"dovrei risolverlo. Ma capisco che potrebbe non piacerti, dal momento che mescolare accidentalmente argomenti e non argomenti evalè uno dei motivi per cui è così pericoloso e ampiamente scoraggiato usare evalil comportamento concatenante.
Eliah Kagan,

@EliahKagan: Oh. La mia shell preferita per gli script non ha quotazioni $ "".
Giosuè,

1
Se lo risolvi, dovrebbe funzionare. E ho sempre sbagliato a pensare che incollasse le citazioni su un testo arbitrario! Costruisce argomenti letterali che si evalconcatenano prima della valutazione. Ma penso che un altro modo per dirlo è che è un modo offuscato di scrivere eval 'google-chrome --headless --disable-gpu --dump-dom "$RobWebAddress" > "$DownloadName" '"$HideErrors"che ricorda l' aspetto del codice OP. E in generale, utilizzare evalper attività che non ne hanno bisogno è un male . (Nessuna di queste scuse - e nemmeno spiega - l'erroneità e l'ostilità della mia vecchia risposta.)
Eliah Kagan,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.