Come archiviare l’errore standard in una variabile in uno script Bash

Diciamo che ho uno script come il seguente:

useless.sh

echo "This Is Error" 1>&2 echo "This Is Output" 

E ho un altro script di shell:

alsoUseless.sh

 ./useless.sh | sed 's/Output/Useless/' 

Voglio catturare “This Is Error”, o qualsiasi altro stderr da useless.sh, in una variabile. Chiamiamolo ERRORE.

Si noti che sto usando stdout per qualcosa. Voglio continuare a usare lo stdout, in questo caso non è utile redirect lo stderr nello stdout.

Quindi, fondamentalmente, voglio farlo

 ./useless.sh 2> $ERROR | ... 

ma ovviamente non funziona.

So anche che potrei fare

 ./useless.sh 2> /tmp/Error ERROR=`cat /tmp/Error` 

ma questo è brutto e inutile.

Sfortunatamente, se nessuna risposta arriva qui è quello che dovrò fare.

Spero che ci sia un altro modo.

Qualcuno ha qualche idea migliore?

Sarebbe più ordinato catturare il file di errore in questo modo:

 ERROR=$( 

La shell lo riconosce e non deve eseguire ' cat ' per ottenere i dati.

La domanda più grande è difficile. Non penso che ci sia un modo semplice per farlo. Dovresti build l'intera pipeline nella sub-shell, inviando infine il suo output standard finale a un file, in modo che tu possa redirect gli errori allo standard output.

 ERROR=$( { ./useless.sh | sed s/Output/Useless/ > outfile; } 2>&1 ) 

Si noti che è necessario il punto e virgola (nelle shell classiche - Bourne, Korn - di sicuro, probabilmente anche in Bash). ' {} ' Esegue il reindirizzamento I / O sui comandi racchiusi. Come scritto, catturerebbe anche gli errori di sed .

(Codice formalmente non verificato - utilizzare a proprio rischio.)

alsoUseless.sh

Ciò consentirà di redirect l’output del tuo script useless.sh attraverso un comando come sed e salvare lo stderr in una variabile denominata error . Il risultato della pipe viene inviato a stdout per la visualizzazione o per essere reindirizzato in un altro comando.

Imposta un paio di descrittori di file aggiuntivi per gestire i reindirizzamenti necessari per fare ciò.

 #!/bin/bash exec 3>&1 4>&2 #set up extra file descriptors error=$( { ./useless.sh | sed 's/Output/Useless/' 2>&4 1>&3; } 2>&1 ) echo "The message is \"${error}.\"" exec 3>&- 4>&- # release the extra file descriptors 

Reindirizza stderr a stdout, stdout a / dev / null e quindi usa i backtick o $() per catturare lo stderr reindirizzato:

 ERROR=$(./useless.sh 2>&1 >/dev/null) 
 # command receives its input from stdin. # command sends its output to stdout. exec 3>&1 stderr="$(command &1 1>&3)" exitcode="${?}" echo "STDERR: $stderr" exit ${exitcode} 

Ci sono molti duplicati per questa domanda, molti dei quali hanno uno scenario di utilizzo leggermente più semplice in cui non si desidera catturare tutto stderr e stdout e il codice di uscita tutti allo stesso tempo.

 if result=$(useless.sh 2>&1); then stdout=$result else rc=$? stderr=$result fi 

funziona per lo scenario comune in cui ci si aspetta un output corretto in caso di successo o un messaggio di diagnostica su stderr in caso di errore.

Si noti che le istruzioni di controllo della shell esaminano già $? sotto il cappuccio; quindi tutto ciò che sembra

 cmd if [ $? -eq 0 ], then ... 

è solo un modo goffo e unidiomatico di dire

 if cmd; then ... 

Ecco come l’ho fatto:

 # # $1 - name of the (global) variable where the contents of stderr will be stored # $2 - command to be executed # captureStderr() { local tmpFile=$(mktemp) $2 2> $tmpFile eval "$1=$(< $tmpFile)" rm $tmpFile } 

Esempio di utilizzo:

 captureStderr err "./useless.sh" echo -$err- 

Usa un file temporaneo. Ma almeno le cose brutte sono avvolte in una funzione.

Questo è un problema interessante al quale speravo che ci fosse una soluzione elegante. Tristemente, finisco con una soluzione simile a Mr. Leffler, ma aggiungo che puoi chiamare inutile dall’interno di una funzione di Bash per migliorare la leggibilità:

 #! / Bin / bash

 funzione inutile {
     /tmp/useless.sh |  sed 's / Output / Inutile /'
 }

 ERROR = $ (inutile)
 echo $ ERROR

Tutti gli altri tipi di reindirizzamento dell’output devono essere supportati da un file temporaneo.

 $ b=$( ( a=$( (echo stdout;echo stderr >&2) ) ) 2>&1 ) $ echo "a=>$ab=>$b" a=>stdout b=>stderr 

Questo post mi ha aiutato a trovare una soluzione simile per i miei scopi:

 MESSAGE=`{ echo $ERROR_MESSAGE | format_logs.py --level=ERROR; } 2>&1` 

Quindi finché il nostro MESSAGGIO non è una stringa vuota, la passiamo ad altre cose. Questo ci farà sapere se il nostro format_logs.py ha fallito con qualche tipo di eccezione python.

Cattura e stampa stderr

 ERROR=$( ./useless.sh 3>&1 1>&2 2>&3 | tee /dev/fd/2 ) 

Abbattersi

Puoi usare $() per catturare lo stdout, ma invece vuoi catturare lo stderr. Quindi scambiate lo stdout e lo stderr. Utilizzo di fd 3 come memoria temporanea nell’algoritmo di swap standard.

Se si desidera acquisire e stampare, utilizzare il tee per creare un duplicato. In questo caso l’output di tee verrà catturato da $() piuttosto che passare alla console, ma stderr (di tee ) andrà comunque alla console, quindi la useremo come seconda uscita per tee tramite il file speciale /dev/fd/2 poiché tee aspetta un percorso file piuttosto che un numero fd.

NOTA: si tratta di un sacco di reindirizzamenti in una singola riga e l’ordine è importante. $() sta prendendo lo stdout del tee alla fine della pipeline e la pipeline stessa instrada lo stdout di ./useless.sh allo stdin del tee DOPO abbiamo scambiato lo stdin e lo stdout per ./useless.sh .

Utilizzando lo stdout di ./useless.sh

L’OP ha detto che voleva ancora usare (non solo stampare) stdout, come ./useless.sh | sed 's/Output/Useless/' ./useless.sh | sed 's/Output/Useless/' .

Nessun problema, fallo PRIMA di scambiare lo stdout e lo stderr. Raccomando di spostarlo in una funzione o in un file (also-useless.sh) e chiamandolo al posto di ./useless.sh nella riga sopra.

Tuttavia, se si desidera catturare stdout e stderr, allora penso che si debba ricorrere ai file temporanei perché $() eseguirà uno solo alla volta e crea una subshell da cui non è ansible restituire le variabili.

Se si desidera evitare l’uso di un file temporaneo, è ansible utilizzare la sostituzione del processo. Non ho ancora finito per funzionare ancora. Questo era il mio primo tentativo:

 $ .useless.sh 2> >( ERROR=$(<) ) -bash: command substitution: line 42: syntax error near unexpected token `)' -bash: command substitution: line 42: `<)' 

Poi ho provato

 $ ./useless.sh 2> >( ERROR=$( cat <() ) ) This Is Output $ echo $ERROR # $ERROR is empty 

però

 $ ./useless.sh 2> >( cat <() > asdf.txt ) This Is Output $ cat asdf.txt This Is Error 

Quindi la sostituzione del processo sta facendo generalmente la cosa giusta ... sfortunatamente, ogni volta che avvolgo STDIN dentro >( ) con qualcosa in $() nel tentativo di catturarlo in una variabile, perdo il contenuto di $() . Penso che questo sia dovuto al fatto che $() avvia un sottoprocesso che non ha più accesso al descrittore di file in / dev / fd che è di proprietà del processo genitore.

La sostituzione del processo mi ha acquistato la possibilità di lavorare con un stream di dati che non è più in STDERR, sfortunatamente non sembra che sia in grado di manipolarlo nel modo che voglio.

In zsh:

 { . ./useless.sh > /dev/tty } 2>&1 | read ERROR $ echo $ERROR ( your message ) 

Per la verifica degli errori dei comandi:

 execute [INVOKING-FUNCTION] [COMMAND] 

 execute () { error=$($2 2>&1 >/dev/null) if [ $? -ne 0 ]; then echo "$1: $error" exit 1 fi } 

Ispirato alla produzione snella:

  • Rendere gli errori impossibili dal design
  • Fai passi più piccoli
  • Finisci gli articoli uno per uno
  • Rendilo ovvio a chiunque