¿Cómo puedo imprimir solo ciertos comandos de un script bash mientras se ejecutan?

19

Tengo un script bash con varias declaraciones if basadas en argumentos de línea de comando que paso al llamarlo. Tener algún tipo de salida sobre qué comandos se están ejecutando es útil para confirmar el flujo a través de todas esas declaraciones if, pero mi solución actual me está dando demasiada información.

Usando set -ven el guión fue algo útil para ver los comandos se envían a la pantalla a medida que se ejecutan en el guión, sin embargo llego demasiado comandos. Es casi como una copia completa del guión.

Quiero una salida que muestre qué comandos se están ejecutando, pero no quiero ver comentarios, nuevas líneas, expresiones en declaraciones if, etc.

¿Hay alguna manera de que pueda pasar toda la salida posible generada por la opción -v a través de una expresión regular primero antes de imprimirla? ¿O alguna otra solución para hacer que bash solo muestre comandos de un cierto "tipo" (por ejemplo, que están usando ejecutables y no solo declaraciones específicas de bash, comentarios, etc.?)

[1] /programming/257616/sudo-changes-path-why fue bastante útil en esto y es donde obtuve la sugerencia para el set -vuso.

Editar :

Un script similar (pero no idéntico) al que estoy ejecutando:

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

Solo quiero 2 conjuntos posibles de líneas de salida de este script. El primero:

python ascript.py

El segundo:

pkill -f nginx
nginx -c /some/current/directory/conf/artfndr_nginx.conf
Trindaz
fuente
1
Por supuesto, puede analizarlo, pero no podemos ayudarlo a menos que nos muestre el script y explique qué partes de la set -vsalida desea y cuáles no.
terdon

Respuestas:

12

Cuando escribo scripts de bash más complejos, utilizo una pequeña función para ejecutar comandos que también imprimirán los comandos ejecutados en un archivo de 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 
}

Luego, en mi script, ejecuto comandos como este:

runthis "cp /foo/bar /baz/"

Si no desea que se imprima un comando, simplemente ejecútelo normalmente.

Puede establecerlo $LOGen un nombre de archivo o simplemente eliminarlo e imprimirlo en stdout o stderr.

terdon
fuente
+1 También pude ejecutar esto dentro de mi script simplemente anteponiendo comandos "importantes" con una versión con nombre corto de la función, para que las líneas se vean v python ascript.pysin tener que encerrarse entre comillas y perder el resaltado del código vim
Trindaz
@Trindaz las comillas están ahí para cuando necesite pasar variables en sus comandos, si las variables contienen espacios, de lo contrario, podría tener problemas.
terdon
eval ..... || ok=1: establecerá ok en "1" solo cuando "eval ..." falle? ¿Quizás quisiste decir "&&"? Y si quiso decir eso, agregue un "ok = 0" antes de la línea de evaluación, para que sea "restablecer" cada vez. ¿O simplemente cambia el nombre de "ok" a "error"? Parece que eso es lo que se quiso decir aquí. Así que al final:eval "$@" 2>> "$LOG" && error=0 || error=1
Olivier Dulac
@OlivierDulac, en la versión de esto que uso, tengo una okvariable que detendrá el script si falla algún comando. Como eso no era relevante aquí, lo eliminé pero olvidé eliminarlo || ok=1. Gracias, arreglado ahora.
terdon
Excelente solucion! Tuve que eliminar el "enunciado que rodea la evaluación, porque el comando ya está rodeado por "s
gromit190
11

Use un sub-shell, es decir:

( set -x; cmd1; cmd2 )

Por ejemplo:

( set -x; echo "hi there" )

huellas dactilares

+ echo 'hi there'
hi there
Nik
fuente
Prefiero este set -x; cmd; set +xpor varias razones. Primero, no se reinicia set -xen caso de que haya estado encendido antes. En segundo lugar, la terminación de la secuencia de comandos en el interior no hace que las trampas se ejecuten con configuraciones detalladas activadas.
Oliver Gondža
2

He visto métodos similares a los de @ terdon. Es el comienzo de lo que los lenguajes de programación de nivel superior llaman registradores y ofrecen como bibliotecas completas, como log4J (Java), log4Perl (Perl), etc.

Puede obtener algo similar usando set -xen Bash como ha mencionado, pero puede usarlo para activar la depuración solo un subconjunto de comandos envolviendo bloques de código con ellos de esta manera.

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

Ejemplos

Aquí hay un patrón de una línea que puede usar.

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

Puede envolverlos así para múltiples comandos en un script.

set -x
cmd1
cmd2
set +x

cmd3

Log4Bash

La mayoría de las personas son ajenas pero Bash también tiene un log4 * también, Log4Bash . Si tiene necesidades más modestas, podría valer la pena configurarlo.

log4bash es un intento de tener un mejor registro para los scripts de Bash (es decir, hacer que el inicio de sesión en Bash succione menos).

Ejemplos

Aquí hay algunos ejemplos del uso 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 quieres lo que clasificaría como más de la potencia total de un marco log4 *, entonces probaría Log4sh .

extracto

Log4sh se desarrolló originalmente para resolver un problema de registro que tuve en algunos de los entornos de producción en los que he trabajado, donde tuve demasiado registro o no lo suficiente. Los trabajos de Cron en particular me causaron la mayor cantidad de dolores de cabeza con sus correos electrónicos constantes y molestos que me decían que todo funcionaba o que nada funcionaba, pero no una razón detallada del por qué. Ahora uso log4sh en entornos en los que el registro desde scripts de shell es crítico, pero donde necesito algo más que un simple "¡Hola, corrígeme!" tipo de mensaje de registro. Si le gusta lo que ve o tiene alguna sugerencia sobre mejoras, no dude en enviarme un correo electrónico. Si hay suficiente interés en el proyecto, lo desarrollaré más.

log4sh se ha desarrollado bajo Bourne Again Shell (/ bin / bash) en Linux, pero se ha tenido mucho cuidado para asegurarse de que funciona bajo el Bourne Shell predeterminado de Solaris (/ bin / sh) ya que esta es la producción principal plataforma utilizada por mí mismo.

Log4sh admite varios shells, no solo Bash.

  • Bourne Shell (sh)
  • BASH - GNU Bourne Again SHell (bash)
  • DASH (guión)
  • Korn Shell (ksh)
  • pdksh: el Korn Shell de dominio público (pdksh)

También se ha probado en varios sistemas operativos, no solo en Linux.

  • Cygwin (bajo Windows)
  • FreeBSD (compatible con el usuario)
  • Linux (Gentoo, RedHat, Ubuntu)
  • Mac OS X
  • Solaris 8, 9, 10

El uso de un marco log4 * llevará algún tiempo para aprender, pero vale la pena si tiene necesidades más exigentes de su registro. Log4sh utiliza un archivo de configuración donde puede definir los apéndices y controlar el formato de la salida que aparecerá.

Ejemplo

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

Ahora cuando lo ejecuto:

$ ./log4sh.bash 
INFO - Hello, world

NOTA: Lo anterior configura el apéndice como parte del código. Si lo desea, puede extraerlo en su propio archivo, log4sh.propertiesetc.

Consulte la excelente documentación de Log4sh si necesita más detalles.

slm
fuente
Gracias por las notas agregadas, pero el principal problema que tengo con eso es todos los setcomandos que necesitaría presentar, alternando comentarios, etc., por lo que solo tener una función en la parte superior de mi script, con una llamada de función de un solo carácter antepuesta a Todas las líneas "importantes" en el guión me parecían más ordenadas por ahora. (carácter único porque la función tiene un nombre de carácter único)
Trindaz
@Trindaz: lo siento, aún no había terminado mi respuesta. Eche un vistazo a log4bash si tiene más necesidades que la función que le dio terdon.
slm
1
@Trindaz: hago algo similar de vez en cuando, el otro enfoque que he usado es ajustar echomi propia función mechoy luego pasar un cambio al programa llamado -vdetallado cuando quiero apagar las cosas. También puedo controlarlo con un interruptor de segundo argumento que especifica el nombre de la función, por lo que tengo 2 ejes para controlar el registro. Sin embargo, esta suele ser la puerta de entrada para querer log4bash.
slm
1
@Trindaz set -ximprime los comandos a medida que se ejecutan. No imprime comentarios. set -xes práctico para la depuración (a diferencia de lo set -vcual no es muy útil). Zsh tiene mejor salida set -xque bash, por ejemplo, muestra qué función se está ejecutando actualmente y el número de línea de origen.
Gilles 'SO- deja de ser malvado'
Gracias @Gilles, eso es cierto, pero me dio las expansiones de expresión if, lo que fue excesivo en este caso
Trindaz
1

Podrías trap DEBUGy luego probar la BASH_COMMANDvariable . Agregue esto a la parte superior del script:

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

trap 'log "$BASH_COMMAND"' DEBUG

El código es legible; solo prueba si el primer argumento comienza con pythono pkill, y lo imprime si ese es el caso. Y la trampa usa BASH_COMMAND(que contiene el comando que se ejecutará) como primer argumento.

$ 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

Tenga en cuenta que si bien caseusa globs, podría hacer lo mismo:

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

Y usa expresiones regulares.

muru
fuente
0

Esta es una versión revisada de la función ordenada de Steven Penny. Imprime sus argumentos en color y los cita según sea necesario. Úselo para repetir selectivamente los comandos que desea rastrear. Como se envían comillas, puede copiar líneas impresas y pegarlas en el terminal para volver a ejecutarlas inmediatamente mientras está depurando un script. Lea el primer comentario para saber qué he cambiado y por qué.

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

Ejemplo de uso con salida:

# 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 segunda línea de arriba se imprime en verde y se puede copiar y pegar para reproducir la tercera línea.

Más observaciones

El xc original de @ Steven-Penny es inteligente y se merece todos los créditos por ello. Sin embargo, noté algunos problemas, pero no pude comentar su publicación directamente porque no tengo suficiente reputación. Así que hice una edición sugerida a su publicación, pero los revisores rechazaron mi edición. Por lo tanto, recurrí a publicar mis comentarios como esta respuesta, aunque hubiera preferido poder editar la propia respuesta de Steve Penny.

Lo que cambié con la respuesta de Steven-Penny

Corregido: impresión de cadenas nulas, no se imprimieron. Se corrigió: las cadenas de impresión que incluyen %- causaron errores de sintaxis awk. Reemplazado for (j in ...)confor (j = 0, ...) ya que el primero no garantiza el orden de la matriz de recorrido (que es dependiente de la implementación de awk). Se agregó 0 a los números octales para la portabilidad.

Actualizar

Desde entonces, Steven Penny ha solucionado esos problemas en su respuesta, por lo que estas observaciones se quedan solo para el registro histórico de mi respuesta. Vea la sección de Comentarios para más detalles.

stepse
fuente
0

Puede utilizar la función de shell "sh_trace" de la biblioteca POSIX stdlib para imprimir el comando en color antes de ejecutarlo. Ejemplo:

avance

Función subyacente de Awk:

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
fuente