Como posso imprimir apenas certos comandos de um script bash à medida que eles são executados?

14

Eu tenho um script bash com várias instruções if baseadas em argumentos de linha de comando que eu passo ao chamá-lo. Ter algum tipo de saída para os comandos que estão sendo executados é útil para confirmar o fluxo através de todas essas instruções if, mas a minha solução atual está me dando informações demais .

Usar set -v no script foi um pouco útil para ver comandos impressos na tela enquanto eram executados no script, no entanto, eu obtenho também muitos comandos. É quase como uma cópia inteira do script.

Eu quero uma saída que mostre quais comandos estão sendo executados, mas não quero ver comentários, novas linhas, expressões em declarações if, etc.

Existe uma maneira de passar todas as saídas possíveis geradas pela opção -v por meio de uma regex antes de serem impressas? Ou alguma outra solução para obter bash para apenas os comandos de saída de um determinado "tipo" (por exemplo, que estão usando executáveis e não apenas basear declarações específicas, comentários etc?)

[1] link foi bastante útil para isso e é onde recebi a sugestão de o uso de set -v .

Editar :

Um script semelhante (mas não idêntico) ao que estou executando:

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

Eu quero apenas dois conjuntos possíveis de linhas de saída deste script. O primeiro:

python ascript.py

O segundo:

pkill -f nginx
nginx -c /some/current/directory/conf/artfndr_nginx.conf
    
por Trindaz 27.12.2013 / 14:57

6 respostas

10

Quando escrevo scripts bash mais complexos, uso uma pequena função para executar comandos que também imprimem os comandos executados em um arquivo de log:

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

Em seguida, no meu script, executo comandos como este:

runthis "cp /foo/bar /baz/"

Se você não quiser um comando impresso, basta executá-lo normalmente.

Você pode definir o $LOG para um nome de arquivo ou apenas removê-lo e imprimir em stdout ou stderr.

    
por 27.12.2013 / 15:03
8

Use um sub-shell, por exemplo:

( set -x; cmd1; cmd2 )

Por exemplo:

( set -x; echo "hi there" )

imprime

+ echo 'hi there'
hi there
    
por 07.01.2015 / 10:15
2

Eu vi métodos usados como os de @ terdon. É o começo do que linguagens de programação de alto nível chamam de registradores, e oferecem como bibliotecas completas, como log4J (Java), log4Perl (Perl) etc.

Você pode obter algo semelhante usando set -x no Bash, como você mencionou, mas você pode usá-lo para aumentar a depuração apenas de um subconjunto de comandos envolvendo blocos de código com eles da mesma forma.

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

Exemplos

Aqui está um padrão que você pode usar.

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

Você pode envolvê-los assim para vários comandos em um script.

set -x
cmd1
cmd2
set +x

cmd3

Log4Bash

A maioria das pessoas é alheia, mas o Bash também tem um log4 *, Log4Bash . Se você tem necessidades mais modestas, isso pode valer a pena para configurá-lo.

log4bash is an attempt to have better logging for Bash scripts (i.e. make logging in Bash suck less).

Exemplos

Aqui estão alguns exemplos de uso do 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 você quer o que eu classificaria como mais do poder total de um framework log4 *, eu daria Log4sh uma tentativa.

trecho

log4sh was originally developed to solve a logging problem I had in some of the production environments I have worked in where I either had too much logging, or not enough. Cron jobs in particular caused me the most headaches with their constant and annoying emails telling me that everything worked, or that nothing worked but not a detailed reason why. I now use log4sh in environments where logging from shell scripts is critical, but where I need more than just a simple "Hello, fix me!" type of logging message. If you like what you see, or have any suggestions on improvements, please feel free to drop me an email. If there is enough interest in the project, I will develop it further.

log4sh has been developed under the Bourne Again Shell (/bin/bash) on Linux, but great care has been taken to make sure it works under the default Bourne Shell of Solaris (/bin/sh) as this happens to be the primary production platform used by myself.

O Log4sh suporta vários shells, não apenas o Bash.

  • Bourne Shell (sh)
  • BASH - GNU Bourne Novamente SHell (bash)
  • DASH (traço)
  • Korn Shell (ksh)
  • pdksh - o Public Domain Korn Shell (pdksh)

Ele também foi testado em vários sistemas operacionais, não apenas no Linux.

  • Cygwin (no Windows)
  • FreeBSD (suportado pelo usuário)
  • Linux (Gentoo, RedHat, Ubuntu)
  • Mac OS X
  • Solaris 8, 9, 10

Usar um framework log4 * levará algum tempo para aprender, mas vale a pena se você tiver necessidades mais exigentes do seu registro. O Log4sh faz uso de um arquivo de configuração no qual você pode definir os anexadores e controlar a formatação da saída que será exibida.

Exemplo

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

Agora, quando eu executo:

$ ./log4sh.bash 
INFO - Hello, world

NOTA: O elemento acima configura o anexador como parte do código. Se você gosta disso pode ser extraído em seu próprio arquivo, log4sh.properties etc.

Consulte a documentação excelente para o Log4sh se você precisa de mais detalhes.

    
por 27.12.2013 / 16:00
1

Você pode usar trap DEBUG e testar a variável BASH_COMMAND . Adicione isto ao topo do script:

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

trap 'log "$BASH_COMMAND"' DEBUG

O código é legível; ele apenas testa se o primeiro argumento começa com python ou pkill e imprime se for esse o caso. E a armadilha usa BASH_COMMAND (que contém o comando que será executado) como primeiro 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

Observe que, embora case use globs, você pode facilmente fazer isso:

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

E use expressões regulares.

    
por 25.12.2016 / 06:15
0

Esta é uma versão revisada da função pura de Steven Penny. Ele imprime seus argumentos em cores e os cita conforme necessário. Use-o para selecionar seletivamente os comandos que você deseja rastrear. Como as cotações são produzidas, você pode copiar linhas impressas e colá-las no terminal para reexecução imediata enquanto estiver depurando um script. Leia o primeiro comentário para saber o que eu mudei e por quê.

xc() # $@-args
{
  cecho "$@"
  "$@"
}
cecho() # $@-args
{
  awk '
  BEGIN {
    x = "7"
    printf "3[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 ? "3[m\n" : FS
    }
  }
  ' "$@"
}

Exemplo de uso com saída:

# 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

A segunda linha acima é impressa em verde e pode ser copiada para reproduzir a terceira linha.

Outras observações

@ xc original de Steven-Penny é inteligente e ele merece todos os créditos por isso. No entanto, notei alguns problemas, mas não pude comentar o post dele diretamente porque não tenho reputação suficiente. Então eu fiz uma edição sugerida para o post dele, mas os revisores rejeitaram minha edição. Por isso, recorri a postar meus comentários como essa resposta, embora eu preferisse poder editar a resposta do próprio Steve Penny.

O que eu mudei com a resposta de Steven-Penny

Corrigido: imprimindo cadeias nulas - elas não foram impressas. Corrigido: strings de impressão que incluem % - causaram erros de sintaxe do awk. Substituiu for (j in ...) com for (j = 0, ...) porque o primeiro não garante a ordem de passagem da matriz (é dependente da implementação do awk). Adicionado 0 a números octal para portabilidade.

Atualizar

Desde então, Steven Penny's corrigiu esses problemas em sua resposta, então essas observações permanecem apenas para o registro histórico da minha resposta. Veja a seção de comentários para mais detalhes.

    
por 05.05.2016 / 14:17
0

Você pode usar a função de shell "sh_trace" da biblioteca stdlib do POSIX para imprima o comando em cores antes de executá-lo. Exemplo:

preview

Função Awk subjacente:

function sh_trace(ary,   b, d, k, q, w, z) {
  b = ""
  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 "[36m%s[m\n", z
  system(z)
}
    
por 06.03.2016 / 18:38