Como posso imprimir apenas determinados comandos de um script bash enquanto são executados?

19

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

Usando set -vno script foi um pouco útil para ver os comandos impresso na tela como eles foram executados no script, no entanto eu fico muito muitos comandos. É quase como uma cópia inteira do script.

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

Existe uma maneira de passar toda a saída possível gerada pela opção -v através de um regex antes de ser impressa? Ou alguma outra solução para que o bash obtenha apenas comandos de saída de um determinado "tipo" (por exemplo, que estejam usando executáveis ​​e não apenas bash declarações específicas, comentários etc.?)

[1] /programming/257616/sudo-changes-path-why foi bastante útil nisso e foi aí que recebi a sugestão para o set -vuso.

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 2 conjuntos possíveis de linhas de saída desse script. O primeiro:

python ascript.py

O segundo:

pkill -f nginx
nginx -c /some/current/directory/conf/artfndr_nginx.conf
Trindaz
fonte
11
É claro que você pode analisá-lo, mas não podemos ajudar, a menos que você nos mostre o script e explique quais partes da set -vsaída você deseja e quais não.
terdon

Respostas:

12

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 
}

Então, no meu script, eu executo comandos como este:

runthis "cp /foo/bar /baz/"

Se você não deseja que um comando seja impresso, execute-o normalmente.

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

terdon
fonte
Também pude executar isso dentro do meu script simplesmente acrescentando comandos "importantes" com uma versão abreviada da função, para que as linhas pareçam v python ascript.pysem ter que incluir aspas e perder o destaque do código vim
Trindaz
@Trindaz, as aspas existem para quando você precisar passar variáveis ​​em seus comandos; se as variáveis ​​contiverem espaços, você poderá ter problemas.
terdon
eval ..... || ok=1: irá definir ok para "1" somente quando "eval ..." falhar ?? Talvez você quis dizer "&&"? E se você quis dizer isso, adicione um "ok = 0" antes da linha de avaliação, para que seja "redefinido" a cada vez. Ou simplesmente renomeie "ok" em "erro"? parece que foi o que quis dizer aqui. Então, no final:eval "$@" 2>> "$LOG" && error=0 || error=1
Olivier Dulac
@OlivierDulac, na versão que eu uso, tenho uma okvariável que interromperá o script se algum comando falhar. Como isso não era relevante aqui, eu o removi, mas esqueci de excluir o arquivo || ok=1. Obrigado, corrigido agora.
terdon
Excelente solução! Eu tinha que remover o "rodeia a declaração eval, porque o comando já está cercado por "s
gromit190
11

Use um sub-shell, ou seja:

( set -x; cmd1; cmd2 )

Por exemplo:

( set -x; echo "hi there" )

impressões

+ echo 'hi there'
hi there
Nik
fonte
Eu prefiro este por set -x; cmd; set +xvárias razões. Primeiro, ele não é redefinido set -xcaso esteja ligado antes. Segundo, o término do script interno não faz com que os traps sejam executados com as configurações detalhadas ativadas.
Oliver Gondža 27/09
2

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

Você pode obter algo semelhante usando o set -xBash, como você mencionou, mas pode usá-lo para ativar a depuração apenas um subconjunto de comandos, agrupando blocos de código com eles dessa maneira.

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

Exemplos

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

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

Você pode agrupá-los dessa maneira para vários comandos em um script.

set -x
cmd1
cmd2
set +x

cmd3

Log4Bash

A maioria das pessoas não sabe, mas o Bash também possui um log4 *, o Log4Bash . Se você tiver necessidades mais modestas, pode valer a pena configurá-lo.

log4bash é uma tentativa de ter um melhor registro para scripts do Bash (ou seja, tornar o log no Bash menos trabalhoso).

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ê quiser o que eu classificaria como mais do poder total de uma estrutura log4 *, eu tentaria o Log4sh .

excerto

O log4sh foi desenvolvido originalmente para resolver um problema de registro que eu tive em alguns dos ambientes de produção em que trabalhei, onde eu tinha registro em excesso ou não o bastante. Os empregos de Cron, em particular, me causaram mais dores de cabeça com seus e-mails constantes e irritantes, dizendo que tudo funcionava ou que nada funcionava, mas não uma razão detalhada. Agora eu uso o log4sh em ambientes em que o log a partir de shell scripts é crítico, mas onde preciso mais do que apenas um simples "Olá, me conserte!" tipo de mensagem de log. Se você gosta do que vê, ou tem alguma sugestão de melhorias, sinta-se à vontade para me enviar um e-mail. Se houver interesse suficiente no projeto, vou desenvolvê-lo ainda mais.

O log4sh foi desenvolvido no Bourne Again Shell (/ bin / bash) no Linux, mas foi tomado muito cuidado para garantir que ele funcione no Bourne Shell do Solaris padrão (/ bin / sh), pois essa é a produção principal. plataforma usada por mim.

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

  • Casca de Bourne (sh)
  • BASH - GNU Bourne Novamente SHell (bash)
  • DASH (traço)
  • Korn Shell (ksh)
  • pdksh - o Shell Korn de domínio público (pdksh)

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

O uso de uma estrutura log4 * levará algum tempo para aprender, mas vale a pena se você tiver necessidades mais exigentes do seu log. O Log4sh utiliza um arquivo de configuração onde você pode definir anexos 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 o executo:

$ ./log4sh.bash 
INFO - Hello, world

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

Consulte a excelente documentação do Log4sh se precisar de mais detalhes.

slm
fonte
Obrigado pelas notas adicionadas, mas o principal problema que tenho com isso é todos os setcomandos que eu precisaria introduzir, alternando entre comentários, etc. todas as linhas "importantes" do script me pareciam mais claras por enquanto. (caractere único porque a função possui um nome de caractere único)
Trindaz 27/12/13
@Trindaz - desculpe, ainda não tinha terminado minha resposta. Dê uma olhada no log4bash se você tiver mais necessidades do que a função que o terdon deu.
slm
11
@Trindaz - Eu faço algo semelhante de tempos em tempos, a outra abordagem que usei é agrupar echoem minha própria função mechoe depois passar um switch para o programa chamado -vverbose quando eu quero desligar as coisas. Também posso controlá-lo com uma segunda opção de argumento que especifica o nome da função, portanto, tenho 2 eixos para controlar o log. Geralmente, esse é o gateway para querer o log4bash.
slm
11
O @Trindaz set -ximprime comandos à medida que são executados. Não imprime comentários. set -xé prático para depuração (ao contrário do set -vque não é muito útil). O Zsh possui uma saída melhor do set -xque o bash, por exemplo, mostra qual função está sendo executada no momento e o número da linha de origem.
Gilles 'SO- stop be evil'
@Gilles graças isso é verdade, mas me deu a se expansões de expressão, o que era um exagero neste caso
Trindaz
1

Você poderia trap DEBUGe depois testar a BASH_COMMANDvariável . Adicione isso ao topo do script:

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

trap 'log "$BASH_COMMAND"' DEBUG

O código é legível; apenas testa se o primeiro argumento começa com pythonou pkille o imprime, se for o caso. E a armadilha usa BASH_COMMAND(que contém o comando que será executado) como o 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, enquanto caseusa globs, você pode facilmente:

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

E use expressões regulares.

muru
fonte
0

Esta é uma versão revisada da função elegante de Steven Penny. Ele imprime seus argumentos em cores e os cita conforme necessário. Use-o para ecoar seletivamente os comandos que você deseja rastrear. Como as aspas são produzidas, você pode copiar as 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 = "\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
    }
  }
  ' "$@"
}

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 e colada para reproduzir a terceira linha.

Observações adicionais

O 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 sua postagem diretamente porque não tenho reputação suficiente. Então, eu fiz uma edição sugerida em sua postagem, mas os revisores rejeitaram minha edição. Por isso, comecei a postar meus comentários como esta resposta, embora eu preferisse poder editar a resposta de Steve Penny.

O que eu mudei Wrt resposta de Steven-Penny

Corrigido: imprimindo cadeias nulas - elas não foram impressas. Corrigido: imprimir seqüências de caracteres que incluem %- causaram erros de sintaxe do awk. Substituído for (j in ...)por for (j = 0, ...)porque o primeiro não garante a ordem de passagem da matriz (depende da implementação do awk). Adicionado 0 aos números octais para portabilidade.

Atualizar

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

stepse
fonte
0

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

pré-visualização

Função Awk subjacente:

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
fonte