Come posso stampare solo alcuni comandi da uno script bash mentre vengono eseguiti?


19

Ho uno script bash con varie istruzioni if ​​basate su argomenti della riga di comando che passo quando lo chiamo. Avere un qualche tipo di output su quali comandi vengono eseguiti è utile per confermare il flusso attraverso tutte quelle istruzioni if, ma la mia soluzione attuale mi sta dando troppe informazioni.

L'utilizzo set -vnello script è stato in qualche modo utile per visualizzare i comandi stampati sullo schermo mentre venivano eseguiti nello script, tuttavia ottengo troppi comandi. È quasi come un'intera copia della sceneggiatura.

Voglio un output che mostri quali comandi vengono eseguiti, ma non voglio vedere commenti, nuove righe, espressioni in if dichiarazioni, ecc.

C'è un modo in cui posso passare tutto il possibile output generato dall'opzione -v attraverso un regex prima di essere stampato? O qualche altra soluzione per ottenere bash per emettere solo comandi di un certo "tipo" (ad es. Che usano eseguibili e non bash solo su dichiarazioni, commenti, ecc. Specifici?)

[1] /programming/257616/sudo-changes-path-why mi è stato di grande aiuto in questo ed è qui che ho ricevuto il suggerimento per l' set -vutilizzo.

Modifica :

Uno script simile (ma non identico) a quello che sto eseguendo:

#!/bin/bash

#get verbose command output
set -v

env=$1

if [ "$env" == "dev" ]; then
    python ascript.py
fi

if [ "$env" == "prod" ]; then

    #launching in prod will most likely fail if not run as root. Warn user if not running as root.
    if [ $EUID -ne 0 ]; then
        echo "It doesn't look like you're running me as root. This probably won't work. Press any key to continue." > /dev/stderr
        read input
    fi

    #"stop" any existing nginx processes
    pkill -f nginx

    nginx -c `pwd`/conf/artfndr_nginx.conf

fi

Voglio solo 2 possibili set di righe di output da questo script. Il primo:

python ascript.py

Il secondo:

pkill -f nginx
nginx -c /some/current/directory/conf/artfndr_nginx.conf

1
Ovviamente puoi analizzarlo ma non possiamo aiutarti se non ci mostri lo script e spieghi quali parti set -vdell'output vuoi e quali no.
terdon

Risposte:


12

Quando scrivo script bash più complessi, utilizzo una piccola funzione per eseguire comandi che stamperanno anche i comandi eseguiti in un file di registro:

runthis(){
    ## print the command to the logfile
    echo "$@" >> $LOG
    ## run the command and redirect it's error output
    ## to the logfile
    eval "$@" 2>> $LOG 
}

Quindi, nel mio script, eseguo comandi in questo modo:

runthis "cp /foo/bar /baz/"

Se non desideri stampare un comando, eseguilo normalmente.

Puoi impostare $LOGun nome file o semplicemente rimuoverlo e stamparlo su stdout o stderr.


+1 Inoltre sono stato in grado di eseguire questo all'interno del mio script semplicemente anteponendo comandi "importanti" con una versione con nome breve della funzione, quindi le linee sembrano qualcosa di simile v python ascript.pysenza dover racchiudere tra virgolette e perdere l'evidenziazione del mio codice vim
Trindaz

@Trindaz ci sono le virgolette per quando devi passare variabili nei tuoi comandi, se le variabili contengono spazi potresti avere problemi altrimenti.
terdon

eval ..... || ok=1: imposterà ok su "1" solo quando "eval ..." fallisce ?? Forse intendevi "&&"? E se intendevi quello, aggiungi un "ok = 0" prima della riga di valutazione, quindi è "resettato" ogni volta. O semplicemente rinominare "ok" in "errore"? sembra che sia quello che intendevo qui. Quindi alla fine:eval "$@" 2>> "$LOG" && error=0 || error=1
Olivier Dulac il

@OlivierDulac, nella versione di questo che uso, ho una okvariabile che interromperà lo script se un comando fallisce. Dato che qui non era rilevante, l'ho rimosso ma ho dimenticato di eliminare il file || ok=1. Grazie, risolto ora.
terdon

Ottima soluzione! Ho dovuto rimuovere ciò che "circondava la dichiarazione eval, perché il comando è già circondato da "s
gromit190

11

Utilizzare una sotto-shell, ovvero:

( set -x; cmd1; cmd2 )

Per esempio:

( set -x; echo "hi there" )

stampe

+ echo 'hi there'
hi there

Preferisco questo set -x; cmd; set +xper diversi motivi. Innanzitutto, non si reimposta set -xnel caso in cui fosse già acceso. In secondo luogo, la fine dello script all'interno non causa l'esecuzione di trap con impostazioni dettagliate su.
Oliver Gondža,

2

Ho visto metodi usati simili a quelli di @ terdon. Sono gli inizi di ciò che i linguaggi di programmazione di livello superiore chiamano logger e offrono come librerie complete, come log4J (Java), log4Perl (Perl) ecc.

Puoi ottenere qualcosa di simile usando set -xin Bash come hai già detto, ma puoi usarlo per attivare il debug solo un sottoinsieme di comandi avvolgendo blocchi di codice con loro in questo modo.

$ set -x; cmd1; cmd2; set +x

Esempi

Ecco un modello di una fodera che puoi usare.

$ set -x; echo  "hi" ;set +x
+ echo hi
hi
+ set +x

Puoi avvolgerli in questo modo per più comandi in uno script.

set -x
cmd1
cmd2
set +x

cmd3

Log4Bash

Molte persone sono ignare, ma Bash ha anche un log4 *, Log4Bash . Se hai esigenze più modeste questo potrebbe valere la pena di configurarlo.

log4bash è un tentativo di avere una migliore registrazione per gli script Bash (ovvero fare in modo che la registrazione in Bash risulti meno efficace).

Esempi

Ecco alcuni esempi di utilizzo di log4bash.

#!/usr/bin/env bash
source log4bash.sh

log "This is regular log message... log and log_info do the same thing";

log_warning "Luke ... you turned off your targeting computer";
log_info "I have you now!";
log_success "You're all clear kid, now let's blow this thing and go home.";
log_error "One thing's for sure, we're all gonna be a lot thinner.";

# If you have figlet installed -- you'll see some big letters on the screen!
log_captains "What was in the captain's toilet?";

# If you have the "say" command (e.g. on a Mac)
log_speak "Resistance is futile";

Log4sh

Se volete quello che classificherei come più della piena potenza di un framework log4 *, allora proverei Log4sh .

estratto

log4sh è stato originariamente sviluppato per risolvere un problema di registrazione che avevo in alcuni degli ambienti di produzione in cui ho lavorato in cui avevo troppa registrazione o non abbastanza. I lavori di Cron in particolare mi hanno causato il maggior mal di testa con le loro e-mail costanti e fastidiose che mi dicevano che tutto funzionava o che nulla funzionava, ma non un motivo dettagliato per cui. Ora uso log4sh in ambienti in cui è fondamentale accedere dagli script della shell, ma dove ho bisogno di qualcosa di più di un semplice "Ciao, correggimi!" tipo di messaggio di registrazione. Se ti piace quello che vedi o hai suggerimenti su miglioramenti, non esitare a inviarmi un'e-mail. Se l'interesse per il progetto è sufficiente, lo svilupperò ulteriormente.

log4sh è stato sviluppato sotto la Bourne Again Shell (/ bin / bash) su Linux, ma è stata prestata molta attenzione per assicurarsi che funzioni sotto la Bourne Shell predefinita di Solaris (/ bin / sh) in quanto questa è la produzione primaria piattaforma utilizzata da me stesso.

Log4sh supporta diverse shell, non solo Bash.

  • Bourne Shell (sh)
  • BASH - GNU Bourne Again SHell (bash)
  • DASH (trattino)
  • Korn Shell (ksh)
  • pdksh: la shell di dominio pubblico (pdksh)

È stato anche testato su diversi sistemi operativi, non solo Linux.

  • Cygwin (sotto Windows)
  • FreeBSD (supportato dall'utente)
  • Linux (Gentoo, RedHat, Ubuntu)
  • Mac OS X
  • Solaris 8, 9, 10

L'uso di un framework log4 * richiederà un po 'di tempo per l'apprendimento, ma ne vale la pena se hai esigenze più impegnative dalla tua registrazione. Log4sh utilizza un file di configurazione in cui è possibile definire appendici e controllare la formattazione dell'output che verrà visualizzato.

Esempio

#! /bin/sh
#
# log4sh example: Hello, world
#

# load log4sh (disabling properties file warning) and clear the default
# configuration
LOG4SH_CONFIGURATION='none' . ./log4sh
log4sh_resetConfiguration

# set the global logging level to INFO
logger_setLevel INFO

# add and configure a FileAppender that outputs to STDERR, and activate the
# configuration
logger_addAppender stderr
appender_setType stderr FileAppender
appender_file_setFile stderr STDERR
appender_activateOptions stderr

# say Hello to the world
logger_info 'Hello, world'

Ora quando lo eseguo:

$ ./log4sh.bash 
INFO - Hello, world

NOTA: quanto sopra configura l'appender come parte del codice. Se ti piace questo può essere estratto nel suo file, log4sh.propertiesecc.

Consultare l' eccellente documentazione per Log4sh se sono necessari ulteriori dettagli.


Grazie per le note aggiunte, ma il problema principale che ho con questo sono tutti i setcomandi che dovrei introdurre, alternando commenti ecc., Quindi avendo solo una funzione nella parte superiore del mio script, con una chiamata di funzione a carattere singolo preposta a tutte le linee "importanti" della sceneggiatura mi sembravano più ordinate per ora. (carattere singolo perché la funzione ha un nome di carattere singolo)
Trindaz,

@Trindaz - scusa se non ho ancora finito la mia risposta. Dai un'occhiata a log4bash se hai più necessità della funzione che Terdon ha dato.
slm

1
@Trindaz - Di tanto in tanto faccio qualcosa di simile, l'altro approccio che ho usato è quello di avvolgere la echomia funzione mecho, e quindi passare un passaggio al programma chiamato -vverbose quando voglio spegnere le cose. Posso anche controllarlo con un secondo argomento che specifica il nome della funzione, quindi ho 2 assi su cui controllare la registrazione. Questo è spesso il gateway per volere log4bash però.
slm

1
@Trindaz set -xstampa i comandi mentre vengono eseguiti. Non stampa commenti. set -xè pratico per il debug (a differenza del set -vquale non è molto utile). Zsh ha un output migliore set -xrispetto a bash, ad esempio mostra quale funzione è attualmente in esecuzione e il numero della linea di origine.
Gilles 'SO- smetti di essere malvagio' il

Grazie @Gilles è vero, ma mi ha dato l'espansione dell'espressione if, che in questo caso è stata eccessiva
Trindaz,

1

È possibile trap DEBUGe quindi testare la BASH_COMMANDvariabile . Aggiungi questo nella parte superiore dello script:

log() {
    case "$1" in
        python\ *)
            ;&
        pkill\ *)
            printf "%s\n" "$*"
            ;;
    esac
}

trap 'log "$BASH_COMMAND"' DEBUG

Il codice è leggibile; verifica solo se il primo argomento inizia con pythono pkille lo stampa in tal caso. E la trap usa BASH_COMMAND(che contiene il comando che verrà eseguito) come primo argomento.

$ bash foo.sh dev
python ascript.py
python: can't open file 'ascript.py': [Errno 2] No such file or directory
$ bash foo.sh prod
It doesn't look like you're running me as root. This probably won't work. Press any key to continue.

pkill -f nginx
foo.sh: line 32: nginx: command not found

Nota che mentre caseusi globs, potresti altrettanto facilmente:

if [[ $1 =~ python|nginx ]]
then
    printf "%s" "$*"
fi

E usa espressioni regolari.


0

Questa è una versione rivista della funzione ordinata di Steven Penny. Stampa i suoi argomenti a colori e li cita come necessario. Usalo per fare l'eco selettiva dei comandi che vuoi tracciare. Poiché le virgolette vengono emesse, è possibile copiare le linee stampate e incollarle sul terminale per la riesecuzione immediata durante il debug di uno script. Leggi il primo commento per sapere cosa ho cambiato e perché.

xc() # $@-args
{
  cecho "$@"
  "$@"
}
cecho() # $@-args
{
  awk '
  BEGIN {
    x = "\047"
    printf "\033[36m"
    while (++i < ARGC) {
      if (! (y = split(ARGV[i], z, x))) {
        printf (x x)
      } else {
        for (j = 1; j <= y; j++) {
          printf "%s", z[j] ~ /[^[:alnum:]%+,./:=@_-]/ ? (x z[j] x) : z[j]
          if (j < y) printf "\\" x
        }
      }
      printf i == ARGC - 1 ? "\033[m\n" : FS
    }
  }
  ' "$@"
}

Esempio di utilizzo con output:

# xc echo "a b" "c'd" "'" '"' "fg" '' " " "" \# this line prints in green

echo 'a b' c\'d \' '"' fg '' ' ' '' '#' this line prints in green

a b c'd ' " fg # this line prints in green

La seconda riga sopra viene stampata in verde e può essere incollata per riprodurre la terza riga.

Ulteriori osservazioni

@C originale di Steven-Penny è intelligente e si merita tutti i crediti per questo. Tuttavia, ho notato alcuni problemi, ma non ho potuto commentare direttamente il suo post perché non ho abbastanza reputazione. Quindi ho apportato una modifica suggerita al suo post ma i revisori hanno rifiutato la mia modifica. Quindi ho fatto ricorso a pubblicare i miei commenti come questa risposta, anche se avrei preferito poter modificare la risposta di Steve Penny.

Quello che ho cambiato è la risposta di Steven-Penny

Risolto: stampa stringhe null - non erano stampate. Risolto: le stringhe di stampa che includevano %causavano errori di sintassi awk. Sostituito for (j in ...)con for (j = 0, ...)perché il primo non garantisce l'ordine di attraversamento dell'array (dipende dall'implementazione awk). Aggiunto 0 ai numeri ottali per la portabilità.

Aggiornare

Da allora Steven Penny ha risolto questi problemi nella sua risposta, quindi queste osservazioni rimangono solo per il record storico della mia risposta. Vedi la sezione Commenti per ulteriori dettagli.


0

È possibile utilizzare la funzione shell "sh_trace" dalla libreria stdlib POSIX per stampare il comando a colori prima di eseguirlo. Esempio:

anteprima

Funzione Awk sottostante:

function sh_trace(ary,   b, d, k, q, w, z) {
  b = "\47"
  for (d in ary) {
    k = split(ary[d], q, b)
    q[1]
    if (d - 1)
      z = z " "
    for (w in q) {
      z = z (!k || q[w] ~ "[^[:alnum:]%+,./:=@_-]" ? b q[w] b : q[w]) \
      (w < k ? "\\" b : "")
    }
  }
  printf "\33[36m%s\33[m\n", z
  system(z)
}
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.