Sopprimere la traccia di esecuzione per il comando echo?


29

Sto eseguendo script di shell da Jenkins, che danno il via agli script di shell con le opzioni shebang #!/bin/sh -ex.

Secondo Bash Shebang per i manichini? , -x"fa sì che la shell stampi una traccia di esecuzione", il che è ottimo per la maggior parte degli scopi, ad eccezione di echos:

echo "Message"

produce l'output

+ echo "Message"
Message

che è un po 'ridondante e sembra un po' strano. C'è un modo per lasciare -xabilitato, ma solo l'output

Message

invece delle due righe precedenti, ad esempio prefissando il comando echo con un carattere di comando speciale o reindirizzando l'output?

Risposte:


20

Quando sei alleato di alligatori, è facile dimenticare che l'obiettivo era drenare la palude.                   - detto popolare

La domanda è circa echo, eppure la maggior parte delle risposte finora si è concentrata su come intrufolare un set +xcomando. C'è una soluzione molto più semplice e diretta:

{ echo "Message"; } 2> /dev/null

(Riconosco che potrei non aver pensato al { …; } 2> /dev/null se non l'avessi visto nelle risposte precedenti.)

Questo è un po 'ingombrante, ma, se hai un blocco di echocomandi consecutivi , non è necessario farlo singolarmente:

{
  echo "The quick brown fox"
  echo "jumps over the lazy dog."
} 2> /dev/null

Nota che non hai bisogno di punti e virgola quando hai nuove righe.

Puoi ridurre l'onere della digitazione usando l'idea di kenorb di aprirsi /dev/nullpermanentemente su un descrittore di file non standard (ad es. 3) e poi dire 2>&3invece che 2> /dev/nullsempre.


Le prime quattro risposte al momento della stesura di questo articolo richiedono di fare qualcosa di speciale (e, nella maggior parte dei casi, ingombrante) ogni volta che lo fai echo. Se vuoi davvero che tutti i echo comandi sopprimano la traccia dell'esecuzione (e perché non lo faresti?), Puoi farlo a livello globale, senza mungere molto codice. Innanzitutto, ho notato che gli alias non vengono tracciati:

$ myfunc()
> {
>     date
> }
$ alias myalias="date"
$ set -x
$ date
+ date
Mon, Oct 31, 2016  0:00:00 AM           # Happy Halloween!
$ myfunc
+ myfunc                                # Note that function call is traced.
+ date
Mon, Oct 31, 2016  0:00:01 AM
$ myalias
+ date                                  # Note that it doesn’t say  + myalias
Mon, Oct 31, 2016  0:00:02 AM

(Si noti che i seguenti frammenti di script funzionano se lo shebang lo è #!/bin/sh, anche se /bin/shè un collegamento a bash. Ma, se lo è lo shebang #!/bin/bash, è necessario aggiungere un shopt -s expand_aliasescomando per far funzionare gli alias in uno script.)

Quindi, per il mio primo trucco:

alias echo='{ set +x; } 2> /dev/null; builtin echo'

Ora, quando diciamo echo "Message", chiamiamo l'alias, che non viene tracciato. L'alias disattiva l'opzione di traccia, mentre sopprime il messaggio di traccia dal setcomando (utilizzando la tecnica presentata prima nella risposta dell'utente 5071535 ), quindi esegue il echocomando effettivo . Questo ci consente di ottenere un effetto simile a quello della risposta dell'utente 5071535 senza la necessità di modificare il codice ad ogni echo comando. Tuttavia, questo lascia la modalità di traccia disattivata. Non possiamo inserire a set -xnell'alias (o almeno non facilmente) perché un alias consente solo di sostituire una stringa con una parola; nessuna parte della stringa di alias può essere iniettata nel comando dopo gli argomenti (es., "Message"). Quindi, ad esempio, se lo script contiene

date
echo "The quick brown fox"
echo "jumps over the lazy dog."
date

l'output sarebbe

+ date
Mon, Oct 31, 2016  0:00:03 AM
The quick brown fox
jumps over the lazy dog.
Mon, Oct 31, 2016  0:00:04 AM           # Note that it doesn’t say  + date

quindi è ancora necessario riattivare l'opzione di traccia dopo aver visualizzato i messaggi, ma solo una volta dopo ogni blocco di echocomandi consecutivi :

date
echo "The quick brown fox"
echo "jumps over the lazy dog."
set -x
date


Sarebbe bello se potessimo fare l' set -xautomatico dopo un echo- e possiamo, con un po 'più di inganno. Ma prima di presentarlo, considera questo. L'OP sta iniziando con script che usano un #!/bin/sh -exshebang. Implicitamente l'utente potrebbe rimuovere il xda shebang e avere uno script che funzioni normalmente, senza traccia di esecuzione. Sarebbe bello se potessimo sviluppare una soluzione che mantenga quella proprietà. Le prime poche risposte qui non riescono a quella proprietà perché attivano la traccia "indietro" dopo le echoistruzioni, incondizionatamente, senza considerare se fosse già attiva.  Questa risposta evidentemente non riesce a riconoscere quel problema, in quanto sostituisce echooutput con output trace; pertanto, tutti i messaggi scompaiono se la traccia è disattivata. Presenterò ora una soluzione che riattiva la traccia dopo echoun'affermazione condizionata - solo se era già attiva. Il downgrade a una soluzione che riattiva incondizionatamente la traccia è banale e viene lasciato come un esercizio.

alias echo='{ save_flags="$-"; set +x;} 2> /dev/null; echo_and_restore'
echo_and_restore() {
        builtin echo "$*"
        case "$save_flags" in
         (*x*)  set -x
        esac
}

$-è l'elenco delle opzioni; una concatenazione delle lettere corrispondente a tutte le opzioni impostate. Ad esempio, se sono impostate le opzioni ee x, allora $-sarà un miscuglio di lettere che include ee x. Il mio nuovo alias (sopra) salva il valore di $-prima di disattivare la traccia. Quindi, con la traccia disattivata, passa il controllo in una funzione shell. Tale funzione esegue l'effettivo echo e quindi verifica se l' xopzione è stata attivata quando è stato invocato l'alias. Se l'opzione era attiva, la funzione la riattiva; se era spento, la funzione lo interrompe.

Puoi inserire le sette righe sopra (otto, se includi una shopt) all'inizio dello script e lasciare il resto da solo.

Questo ti permetterebbe

  1. usare una delle seguenti linee shebang:
    #! / bin / sh -ex
    #! / bin / sh -e
    #! / bin / sh –x
    o semplicemente
    #! / Bin / sh
    e dovrebbe funzionare come previsto.
  2. avere un codice simile
    (shebang) 
    comando 1
     comando 2
     comando 3
    set -x
    comando 4
     comando 5
     comando 6
    imposta + x
    comando 7
     comando 8
     comando 9
    e
    • I comandi 4, 5 e 6 verranno tracciati, a meno che uno di essi non sia un echo, nel qual caso verrà eseguito ma non tracciato. (Ma anche se il comando 5 è un echo, il comando 6 verrà comunque rintracciato.)
    • I comandi 7, 8 e 9 non verranno tracciati. Anche se il comando 8 è un echo, il comando 9 non verrà ancora tracciato.
    • I comandi 1, 2 e 3 verranno tracciati (come 4, 5 e 6) o meno (come 7, 8 e 9) a seconda che includa lo shebang x.

PS Ho scoperto che, sul mio sistema, posso tralasciare la builtinparola chiave nella mia risposta intermedia (quella che è solo un alias per echo). Questo non è sorprendente; bash (1) afferma che, durante l'espansione dell'alias, ...

... una parola identica a un alias che viene espanso non viene espansa una seconda volta. Questo significa che si può alias lsa ls -F, per esempio, e bash non tenta di espandere ricorsivamente il testo di sostituzione.

Non sorprendentemente, l'ultima risposta (quella con echo_and_restore) fallisce se la builtinparola chiave viene omessa 1 . Ma stranamente funziona se elimino builtine cambio l'ordine:

echo_and_restore() {
        echo "$*"
        case "$save_flags" in
         (*x*)  set -x
        esac
}
alias echo='{ save_flags="$-"; set +x;} 2> /dev/null; echo_and_restore'

__________
1  Sembra dare origine a comportamenti indefiniti. ho visto

  • un ciclo infinito (probabilmente a causa della ricorsione illimitata),
  • un /dev/null: Bad addressmessaggio di errore e
  • una discarica principale.

2
Ho visto alcuni incredibili trucchi magici fatti con gli alias, quindi so che la mia conoscenza di ciò è incompleta. Se qualcuno può presentare un modo per fare l'equivalente di echo +x; echo "$*"; echo -xun alias, mi piacerebbe vederlo.
G-Man dice "Ripristina Monica" il

11

Ho trovato una soluzione parziale su InformIT :

#!/bin/bash -ex
set +x; 
echo "shell tracing is disabled here"; set -x;
echo "but is enabled here"

uscite

set +x; 
shell tracing is disabled here 
+ echo "but is enabled here"
but is enabled here

Sfortunatamente, riecheggia ancora set +x, ma almeno dopo è tranquillo. quindi è almeno una soluzione parziale al problema.

Ma c'è forse un modo migliore per farlo? :)


2

In questo modo migliora la tua soluzione eliminando l' set +xoutput:

#!/bin/bash -ex
{ set +x; } 2>/dev/null
echo "shell tracing is disabled here"; set -x;
echo "but is enabled here"

2

Metti set +xtra le parentesi, quindi si applica solo per ambito locale.

Per esempio:

#!/bin/bash -x
exec 3<> /dev/null
(echo foo1 $(set +x)) 2>&3
($(set +x) echo foo2) 2>&3
( set +x; echo foo3 ) 2>&3
true

produrrebbe:

$ ./foo.sh 
+ exec
foo1
foo2
foo3
+ true

Correggimi se sbaglio, ma non penso che l' set +xinterno della subshell (o l'utilizzo di subshells) stia facendo qualcosa di utile. Puoi rimuoverlo e ottenere lo stesso risultato. È il reindirizzamento di stderr su / dev / null che sta facendo il lavoro di "disabilitazione" della traccia temporanea ... Sembra echo foo1 2>/dev/null, ecc., Sarebbe altrettanto efficace e più leggibile.
Tyler Rick,

Avere traccia nel tuo script potrebbe influire sulle prestazioni. In secondo luogo, il reindirizzamento & 2 su NULL potrebbe non essere lo stesso, quando si prevedono altri errori.
Kenorb,

Correggimi se sbaglio, ma nel tuo esempio hai già abilitato la traccia nel tuo script (con bash -x) e stai già reindirizzando &2a null (poiché è &3stato reindirizzato a null), quindi non sono sicuro di come quel commento sia pertinente. Forse abbiamo solo bisogno di un esempio migliore che illustri il tuo punto, ma nell'esempio dato, sembra comunque che potrebbe essere semplificato senza perdere alcun beneficio.
Tyler Rick,

1

Adoro la risposta completa e ben spiegata di g-man , e la considero la migliore fornita finora. Si preoccupa del contesto dello script e non forza le configurazioni quando non sono necessarie. Quindi, se stai leggendo questa risposta prima vai avanti e controlla quella, tutto il merito è lì.

Tuttavia, in quella risposta manca un pezzo importante: il metodo proposto non funzionerà per un caso d'uso tipico, ovvero la segnalazione di errori:

COMMAND || echo "Command failed!"

A causa del modo in cui è costruito l'alias, questo si espanderà a

COMMAND || { save_flags="$-"; set +x; } 2>/dev/null; echo_and_restore "Command failed!"

e hai indovinato, echo_and_restoreviene eseguito sempre , incondizionatamente. Dato che la set +xparte non è stata eseguita, significa che verrà stampato anche il contenuto di quella funzione.

Cambiare l'ultimo ;in &&non funzionerebbe neanche, perché in Bash, ||e &&sono associativi di sinistra .

Ho trovato una modifica che funziona per questo caso d'uso:

echo_and_restore() {
    echo "$(cat -)"
    case "$save_flags" in
        (*x*) set -x
    esac
}
alias echo='({ save_flags="$-"; set +x; } 2>/dev/null; echo_and_restore) <<<'

Usa una subshell (la (...)parte) per raggruppare tutti i comandi, quindi passa la stringa di input attraverso stdin come Here String (la <<<cosa) che viene quindi stampata da cat -. Il -è opzionale, ma si sa, " esplicito è meglio che implicita ".

Puoi anche usare cat -direttamente senza l' eco , ma mi piace in questo modo perché mi consente di aggiungere altre stringhe all'output. Ad esempio, tendo ad usarlo in questo modo:

BASENAME="$(basename "$0")"  # Complete file name
...
echo "[${BASENAME}] $(cat -)"

E ora funziona magnificamente:

false || echo "Command failed"
> [test.sh] Command failed

0

La traccia di esecuzione va a stderr, filtrala in questo modo:

./script.sh 2> >(grep -v "^+ echo " >&2)

Qualche spiegazione, passo dopo passo:

  • stderr viene reindirizzato ... - 2>
  • ... a un comando. ->(…)
  • grep è il comando ...
  • ... che richiede l'inizio della linea ... - ^
  • ... seguito da + echo...
  • ... quindi grepinverte la partita ... --v
  • ... e questo scarta tutte le linee che non vuoi.
  • Il risultato normalmente andrebbe a stdout; lo reindirizziamo a stderrdove appartiene. ->&2

Il problema è (suppongo) che questa soluzione possa desincronizzare i flussi. A causa del filtro stderrpotrebbe essere un po 'in ritardo rispetto a stdout(dove l' echooutput appartiene per impostazione predefinita). Per risolverlo puoi prima unirti ai flussi se non ti dispiace averli entrambi in stdout:

./script.sh > >(grep -v "^+ echo ") 2>&1

È possibile incorporare un tale filtro nello script stesso ma questo approccio è sicuramente incline alla desincronizzazione (vale a dire che si è verificato nei miei test: la traccia dell'esecuzione di un comando potrebbe apparire dopo l'output di immediatamente successivo echo).

Il codice è simile al seguente:

#!/bin/bash -x

{
 # original script here
 # …
} 2> >(grep -v "^+ echo " >&2)

Eseguilo senza trucchi:

./script.sh

Ancora una volta, utilizzare > >(grep -v "^+ echo ") 2>&1per mantenere la sincronizzazione a costo di unire i flussi.


Un altro approccio. Ottieni un output "un po 'ridondante" e dall'aspetto strano perché il tuo terminale si mescola stdoutestderr . Questi due flussi sono animali diversi per un motivo. Verifica se l'analisi soddisfa stderrsolo le tue esigenze; scartare stdout:

./script.sh > /dev/null

Se nel tuo script è presente un echomessaggio di debug / errore di stampa, stderrpuoi eliminare la ridondanza nel modo sopra descritto. Comando completo:

./script.sh > /dev/null 2> >(grep -v "^+ echo " >&2)

Questa volta stiamo lavorando stderrsolo con , quindi la desincronizzazione non è più un problema. Sfortunatamente in questo modo non vedrai traccia o output di echotali stampe stdout(se presenti). Potremmo provare a ricostruire il nostro filtro per rilevare il reindirizzamento ( >&2) ma se lo guardi echo foobar >&2, echo >&2 foobare echo "foobar >&2"probabilmente acconsentirai al fatto che le cose si complicano.

Molto dipende dagli echi che hai nei tuoi script. Pensaci due volte prima di implementare un filtro complesso, potrebbe fallire. È meglio avere un po 'di ridondanza piuttosto che perdere accidentalmente alcune informazioni cruciali.


Invece di scartare la traccia dell'esecuzione di un echo, possiamo scartarne l'output e qualsiasi output tranne le tracce. Per analizzare solo le tracce di esecuzione, prova:

./script.sh > /dev/null 2> >(grep "^+ " >&2)

Infallibile? No. Pensa cosa succederà se c'è echo "+ rm -rf --no-preserve-root /" >&2nella sceneggiatura. Qualcuno potrebbe avere un infarto.


E infine…

Fortunatamente esiste BASH_XTRACEFDuna variabile ambientale. Da man bash:

BASH_XTRACEFD
Se impostato su un numero intero corrispondente a un descrittore di file valido, bash scriverà l'output di traccia generato quando set -xè abilitato su quel descrittore di file.

Possiamo usarlo in questo modo:

(exec 3>trace.txt; BASH_XTRACEFD=3 ./script.sh)
less trace.txt

Si noti che la prima riga genera una subshell. In questo modo il descrittore di file non rimarrà valido né la variabile assegnata nella shell corrente in seguito.

Grazie a BASH_XTRACEFDte puoi analizzare tracce libere da echi e altri output, qualunque essi siano. Non è esattamente quello che volevi, ma la mia analisi mi fa pensare che sia (in generale) la strada giusta.

Ovviamente puoi usare un altro metodo, specialmente quando devi analizzare stdoute / o stderrinsieme alle tue tracce. Devi solo ricordare che ci sono alcuni limiti e insidie. Ho provato a mostrarli (alcuni).


0

In un Makefile puoi usare il @simbolo.

Esempio di utilizzo: @echo 'message'

Da questo documento GNU:

Quando una linea inizia con '@', l'eco di quella linea viene soppressa. '@' Viene scartato prima che la linea venga passata alla shell. In genere lo si utilizza per un comando il cui unico effetto è stampare qualcosa, come un comando echo per indicare l'avanzamento del makefile.


Ciao, il documento a cui ti riferisci è per gnu make, non per shell. Sei sicuro che funzioni? Ottengo l'errore ./test.sh: line 1: @echo: command not found, ma sto usando bash.

Wow, scusa se ho letto male la domanda. Sì, @funziona solo quando si fa eco nei makefile.
Derek,

@derek Ho modificato la tua risposta originale, quindi ora afferma chiaramente che la soluzione è limitata solo ai Makefile. In realtà stavo cercando questo, quindi volevo che il tuo commento non avesse una reputazione negativa. Speriamo che anche le persone lo trovino utile.
Shakaron,

-1

Editoed 29 ott 2016 per moderatore suggerisce che l'originale non conteneva abbastanza informazioni per capire cosa sta succedendo.

Questo "trucco" produce solo una riga di output del messaggio sul terminale quando xtrace è attivo:

La domanda originale è : c'è un modo per lasciare -x abilitato, ma emette solo Message invece delle due righe . Questa è una citazione esatta dalla domanda.

Capisco la domanda da porsi, come "lasciare set -x abilitato E produrre una sola riga per un messaggio"?

  • In senso globale, questa domanda riguarda fondamentalmente l'estetica: l'interrogante vuole produrre una sola linea invece delle due linee praticamente duplicate prodotte mentre xtrace è attivo.

Quindi, in sintesi, l'OP richiede:

  1. Per avere -x insieme a tutti gli effetti
  2. Produrre un messaggio, leggibile dall'uomo
  3. Produce solo una riga di output del messaggio.

L'OP non richiede l' utilizzo del comando echo . Lo hanno citato come esempio di produzione di messaggi, usando l'abbreviazione ad es. Che significa latino esempio, o "per esempio".

Pensando "fuori dagli schemi" e abbandonando l'uso dell'eco per produrre messaggi, noto che un'istruzione di assegnazione può soddisfare tutti i requisiti.

Eseguire un'assegnazione di una stringa di testo (contenente il messaggio) a una variabile inattiva.

L'aggiunta di questa riga a uno script non altera alcuna logica, ma produce una sola riga quando la traccia è attiva: (Se si utilizza la variabile $ echo , basta cambiare il nome in un'altra variabile non utilizzata)

echo="====================== Divider line ================="

Quello che vedrai sul terminale è solo 1 riga:

++ echo='====================== Divider line ================='

non due, come non piace all'OP:

+ echo '====================== Divider line ================='
====================== Divider line =================

Ecco uno script di esempio da dimostrare. Nota che la sostituzione delle variabili in un messaggio (il nome della directory $ HOME 4 righe dalla fine) funziona, quindi puoi tracciare le variabili usando questo metodo.

#!/bin/bash -exu
#
#  Example Script showing how, with trace active, messages can be produced
#  without producing two virtually duplicate line on the terminal as echo does.
#
dummy="====================== Entering Test Script ================="
if [[ $PWD == $HOME ]];  then
  dummy="*** "
  dummy="*** Working in home directory!"
  dummy="*** "
  ls -la *.c || :
else
  dummy="---- C Files in current directory"
  ls -la *.c || :
  dummy="----. C Files in Home directory "$HOME
  ls -la  $HOME/*.c || :
fi

Ed ecco l'output di eseguirlo nella radice, quindi nella home directory.

$ cd /&&DemoScript
+ dummy='====================== Entering Test Script ================='
+ [[ / == /g/GNU-GCC/home/user ]]
+ dummy='---- C Files in current directory'
+ ls -la '*.c'
ls: *.c: No such file or directory
+ :
+ dummy='----. C Files in Home directory /g/GNU-GCC/home/user'
+ ls -la /g/GNU-GCC/home/user/HelloWorld.c /g/GNU-GCC/home/user/hw.c
-rw-r--r-- 1 user Administrators 73 Oct 10 22:21 /g/GNU-GCC/home/user/HelloWorld.c
-rw-r--r-- 1 user Administrators 73 Oct 10 22:21 /g/GNU-GCC/home/user/hw.c
+ dummy=---------------------------------

$ cd ~&&DemoScript
+ dummy='====================== Entering Test Script ================='
+ [[ /g/GNU-GCC/home/user == /g/GNU-GCC/home/user ]]
+ dummy='*** '
+ dummy='*** Working in home directory!'
+ dummy='*** '
+ ls -la HelloWorld.c hw.c
-rw-r--r-- 1 user Administrators 73 Oct 10 22:21 HelloWorld.c
-rw-r--r-- 1 user Administrators 73 Oct 10 22:21 hw.c
+ dummy=---------------------------------

1
Nota, anche la versione modificata della tua risposta cancellata (di cui questa è una copia), non risponde ancora alla domanda, poiché la risposta non genera solo il messaggio senza il comando echo associato, che era ciò che l'OP ha richiesto.
DavidPostill


@ David Ho ampliato la spiegazione, per commenti nel meta forum.
HiTechHiTouch,

@David Ancora ti incoraggio a leggere attentamente il PO, prestando attenzione al latino ".eg". Il poster NON richiede l'uso del comando echo. L'OP fa riferimento solo all'eco come esempio (insieme al reindirizzamento) di potenziali metodi per risolvere il problema. Si scopre che non è nemmeno necessario,
HiTechHiTouch

E se l'OP volesse fare qualcosa del genere echo "Here are the files in this directory:" *?
G-Man dice "Ripristina Monica" il
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.