Comment imprimer uniquement certaines commandes d'un script bash lors de leur exécution?

19

J'ai un script bash avec diverses instructions if basées sur des arguments de ligne de commande que je transmets lors de l'appel. Avoir une sorte de sortie sur les commandes en cours d'exécution est utile pour confirmer le flux à travers toutes ces instructions if, mais ma solution actuelle me donne trop d' informations.

L'utilisation set -vdans le script était quelque peu utile pour voir les commandes imprimées à l'écran lorsqu'elles étaient exécutées dans le script, mais j'obtiens trop de commandes. C'est presque comme une copie entière du script.

Je veux une sortie qui montre quelles commandes sont exécutées, mais je ne veux pas voir les commentaires, les nouvelles lignes, les expressions dans les instructions if, etc.

Existe-t-il un moyen de passer toutes les sorties possibles générées par l'option -v via une expression régulière avant d'être imprimées? Ou une autre solution pour que bash ne produise que des commandes d'un certain "type" (par exemple, qui utilisent des exécutables et pas seulement des instructions, commentaires, etc.?)

[1] /programming/257616/sudo-changes-path-why m'a été très utile à ce sujet et c'est là que j'ai obtenu la suggestion d' set -vutilisation.

Modifier :

Un script similaire (mais pas identique) à celui que j'utilise:

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

Je veux seulement 2 ensembles possibles de lignes de sortie de ce script. La première:

python ascript.py

La deuxième:

pkill -f nginx
nginx -c /some/current/directory/conf/artfndr_nginx.conf
Trindaz
la source
1
Bien sûr, vous pouvez l'analyser, mais nous ne pouvons vous aider que si vous nous montrez le script et expliquez quelles parties de la set -vsortie vous souhaitez et lesquelles vous ne le souhaitez pas.
terdon

Réponses:

12

Lorsque j'écris des scripts bash plus complexes, j'utilise une petite fonction pour exécuter des commandes qui imprimeront également les commandes exécutées dans un fichier journal:

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

Ensuite, dans mon script, j'exécute des commandes comme celle-ci:

runthis "cp /foo/bar /baz/"

Si vous ne voulez pas qu'une commande soit imprimée, exécutez-la normalement.

Vous pouvez soit définir le $LOGsur un nom de fichier, soit le supprimer et imprimer sur stdout ou stderr.

terdon
la source
+1 J'ai également pu exécuter cela dans mon script en ajoutant simplement des commandes "importantes" avec une version abrégée de la fonction, de sorte que les lignes ressemblent à quelque chose v python ascript.pysans avoir à les mettre entre guillemets et à perdre mon code vim en surbrillance
Trindaz
@Trindaz les guillemets sont là pour quand vous devez passer des variables dans vos commandes, si les variables contiennent des espaces vous pourriez avoir des problèmes sinon.
terdon
eval ..... || ok=1: définira ok sur "1" uniquement lorsque "eval ..." échoue ?? Peut-être que vous vouliez dire "&&"? Et si vous vouliez dire cela, ajoutez un "ok = 0" avant la ligne d'évaluation, afin qu'il soit "réinitialisé" à chaque fois. Ou simplement renommer "ok" en "erreur"? il semble que c'est ce que l'on voulait dire ici. Donc au final:eval "$@" 2>> "$LOG" && error=0 || error=1
Olivier Dulac
@OlivierDulac, dans la version que j'utilise, j'ai une okvariable qui arrêtera le script si une commande échoue. Comme cela n'était pas pertinent ici, je l'ai supprimé mais j'ai oublié de le supprimer || ok=1. Merci, réparé maintenant.
terdon
Excellente solution! J'ai dû supprimer l' "entourage de l'instruction eval, car la commande est déjà entourée de "s
gromit190
11

Utilisez un sous-shell, c'est-à-dire:

( set -x; cmd1; cmd2 )

Par exemple:

( set -x; echo "hi there" )

impressions

+ echo 'hi there'
hi there
Nik
la source
Je préfère celui-ci set -x; cmd; set +xà plusieurs raisons. Tout d'abord, il ne se réinitialise pas set -xs'il était déjà allumé. Deuxièmement, la fin du script à l'intérieur ne provoque pas l'exécution d'interruptions avec des paramètres détaillés.
Oliver Gondža
2

J'ai vu des méthodes similaires à celles de @ terdon. Ce sont les débuts de ce que les langages de programmation de niveau supérieur appellent les enregistreurs, et proposent des bibliothèques complètes, telles que log4J (Java), log4Perl (Perl), etc.

Vous pouvez obtenir quelque chose de similaire en utilisant set -xBash comme vous l'avez mentionné, mais vous pouvez l'utiliser pour augmenter le débogage juste un sous-ensemble de commandes en enveloppant des blocs de code avec eux comme ça.

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

Exemples

Voici un motif à doublure que vous pouvez utiliser.

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

Vous pouvez les envelopper comme ceci pour plusieurs commandes dans un script.

set -x
cmd1
cmd2
set +x

cmd3

Log4Bash

La plupart des gens sont inconscients, mais Bash a également un log4 *, Log4Bash . Si vous avez des besoins plus modestes, cela peut valoir la peine de le configurer.

log4bash est une tentative d'avoir une meilleure journalisation pour les scripts Bash (c'est-à-dire que la journalisation dans Bash suce moins).

Exemples

Voici quelques exemples d'utilisation de 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

Si vous voulez ce que je qualifierais de plus de la pleine puissance d'un framework log4 *, j'essaierais Log4sh .

extrait

log4sh a été initialement développé pour résoudre un problème de journalisation que j'avais dans certains des environnements de production dans lesquels j'ai travaillé où j'avais trop de journalisation, ou pas assez. Les emplois Cron en particulier m'ont causé le plus de maux de tête avec leurs courriels constants et ennuyeux me disant que tout fonctionnait, ou que rien ne fonctionnait mais pas une raison détaillée pour laquelle. J'utilise maintenant log4sh dans des environnements où la journalisation à partir de scripts shell est critique, mais où j'ai besoin de plus qu'un simple "Bonjour, corrigez-moi!" type de message de journalisation. Si vous aimez ce que vous voyez ou avez des suggestions d'amélioration, n'hésitez pas à m'envoyer un e-mail. S'il y a suffisamment d'intérêt pour le projet, je le développerai davantage.

log4sh a été développé sous Bourne Again Shell (/ bin / bash) sous Linux, mais un grand soin a été pris pour s'assurer qu'il fonctionne sous le Bourne Shell par défaut de Solaris (/ bin / sh) car il se trouve que c'est la production principale plate-forme utilisée par moi-même.

Log4sh prend en charge plusieurs shells, pas seulement Bash.

  • Bourne Shell (sh)
  • BASH - GNU Bourne Again SHell (bash)
  • DASH (tiret)
  • Korn Shell (ksh)
  • pdksh - le domaine public Korn Shell (pdksh)

Il a également été testé sur plusieurs systèmes d'exploitation, pas seulement Linux.

  • Cygwin (sous Windows)
  • FreeBSD (supporté par l'utilisateur)
  • Linux (Gentoo, RedHat, Ubuntu)
  • Mac OS X
  • Solaris 8, 9, 10

L'utilisation d'un framework log4 * prendra un certain temps à apprendre, mais cela en vaut la peine si vous avez des besoins plus exigeants de votre journalisation. Log4sh utilise un fichier de configuration dans lequel vous pouvez définir des annexes et contrôler la mise en forme de la sortie qui apparaîtra.

Exemple

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

Maintenant, quand je l'exécute:

$ ./log4sh.bash 
INFO - Hello, world

REMARQUE: ce qui précède configure l'appender dans le cadre du code. Si vous le souhaitez, cela peut être extrait dans son propre fichier, log4sh.propertiesetc.

Consultez l' excellente documentation de Log4sh si vous avez besoin de plus de détails.

slm
la source
Merci pour les notes ajoutées, mais le principal problème que j'ai avec c'est toutes les setcommandes que j'aurais besoin d'introduire, en alternant autour des commentaires, etc., donc avoir juste une fonction en haut de mon script, avec un seul appel de fonction de caractère ajouté à toutes les lignes "importantes" du script me semblaient plus propres pour l'instant. (caractère unique car la fonction a un nom de caractère unique)
Trindaz
@Trindaz - désolé, je n'avais pas encore fini ma réponse. Jetez un œil à log4bash si vous avez plus de besoins que la fonction donnée par terdon.
slm
1
@Trindaz - Je fais quelque chose de similaire de temps en temps, l'autre approche que j'ai utilisée consiste à encapsuler echodans ma propre fonction, mechopuis à passer un commutateur dans le programme appelé -vverbose lorsque je veux désactiver les choses. Je peux également le contrôler avec un commutateur de 2ème argument qui spécifie le nom de la fonction, j'ai donc 2 axes sur lesquels contrôler la journalisation. C'est souvent la passerelle pour vouloir log4bash.
slm
1
@Trindaz set -ximprime les commandes lorsqu'elles sont exécutées. Il n'imprime pas de commentaires. set -xest pratique pour le débogage (contrairement à set -vce qui n'est pas très utile). Zsh a une meilleure sortie set -xque bash, par exemple, il montre quelle fonction est en cours d'exécution et le numéro de ligne source.
Gilles 'SO- arrête d'être méchant'
Merci @Gilles, c'est vrai, mais cela m'a donné les extensions d'expression if, ce qui était exagéré dans ce cas
Trindaz
1

Vous pouvez trap DEBUGensuite tester la BASH_COMMANDvariable . Ajoutez ceci en haut du script:

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

trap 'log "$BASH_COMMAND"' DEBUG

Le code est lisible; il teste simplement si le premier argument commence par pythonou pkill, et l'imprime si c'est le cas. Et le piège utilise BASH_COMMAND(qui contient la commande qui sera exécutée) comme premier argument.

$ 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

Notez que si casevous utilisez des globes, vous pouvez tout aussi facilement faire:

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

Et utilisez des expressions régulières.

muru
la source
0

Ceci est une version révisée de la fonction soignée de Steven Penny. Il imprime ses arguments en couleur et les cite au besoin. Utilisez-le pour faire écho de manière sélective aux commandes que vous souhaitez tracer. Étant donné que les guillemets sont générés, vous pouvez copier des lignes imprimées et les coller sur le terminal pour une réexécution immédiate pendant que vous déboguez un script. Lisez le premier commentaire pour savoir ce que j'ai changé et pourquoi.

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
    }
  }
  ' "$@"
}

Exemple d'utilisation avec sortie:

# 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 deuxième ligne au-dessus s'imprime en vert et peut être copiée-collée pour reproduire la troisième ligne.

Remarques supplémentaires

@ Xc original de Steven-Penny est intelligent et il mérite tous les crédits pour cela. Cependant, j'ai remarqué quelques problèmes, mais je n'ai pas pu commenter son message directement car je n'ai pas assez de réputation. J'ai donc proposé une modification à son article, mais les critiques ont rejeté ma modification. Par conséquent, j'ai eu recours à la publication de mes commentaires en tant que réponse, bien que j'aurais préféré pouvoir modifier la propre réponse de Steve Penny.

Ce que j'ai changé par rapport à la réponse de Steven-Penny

Correction: impression de chaînes nulles - elles n'étaient pas imprimées. Résolu: l'impression de chaînes incluant %- elles provoquaient des erreurs de syntaxe awk. Remplacé for (j in ...)par for (j = 0, ...)car le premier ne garantit pas l'ordre de traversée du tableau (il dépend de l'implémentation de awk). Ajout de 0 aux nombres octaux pour la portabilité.

Mise à jour

Steven Penny a depuis corrigé ces problèmes dans sa réponse, donc ces remarques ne restent que pour l'historique de ma réponse. Voir la section Commentaires pour plus de détails.

stepse
la source
0

Vous pouvez utiliser la fonction shell "sh_trace" de la bibliothèque POSIX stdlib pour imprimer la commande en couleur avant de l'exécuter. Exemple:

Aperçu

Fonction Awk sous-jacente:

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)
}
Steven Penny
la source