Suprimir rastreamento de execução para o comando echo?


29

Estou executando scripts de shell da Jenkins, que inicia scripts de shell com as opções shebang #!/bin/sh -ex.

De acordo com Bash Shebang para manequins? , -x"Faz com que o shell para imprimir um rastreamento de execução", o que é ótimo para a maioria dos fins - exceto para Echos:

echo "Message"

produz a saída

+ echo "Message"
Message

que é um pouco redundante e parece um pouco estranho. Existe uma maneira de deixar -xativado, mas apenas a saída

Message

em vez das duas linhas acima, por exemplo, prefixando o comando echo com um caractere de comando especial ou redirecionando a saída?

Respostas:


20

Quando você chega ao pescoço em jacarés, é fácil esquecer que o objetivo era drenar o pântano.                   - provérbio popular

A pergunta é sobre echo, e, no entanto, a maioria das respostas até agora se concentrou em como ocultar um set +xcomando. Há uma solução muito mais simples e direta:

{ echo "Message"; } 2> /dev/null

(Reconheço que talvez eu não tivesse pensado nisso { …; } 2> /dev/null se não tivesse visto nas respostas anteriores.)

Isso é um pouco complicado, mas, se você tiver um bloco de echocomandos consecutivos , não precisará fazer isso em cada um individualmente:

{
  echo "The quick brown fox"
  echo "jumps over the lazy dog."
} 2> /dev/null

Observe que você não precisa de ponto e vírgula quando possui novas linhas.

Você pode reduzir a carga de digitação usando a idéia do kenorb de abrir /dev/nullpermanentemente em um descritor de arquivo não padrão (por exemplo, 3) e depois dizendo em 2>&3vez de 2> /dev/nullo tempo todo.


As quatro primeiras respostas no momento da redação deste documento exigem fazer algo especial (e, na maioria dos casos, complicado) toda vez que você faz um echo. Se você realmente deseja que todos os echo comandos suprimam o rastreamento de execução (e por que não?), Você pode fazê-lo globalmente, sem alterar muito código. Primeiro, notei que os aliases não são rastreados:

$ myfunc()
> {
>     date
> }
$ alias myalias="date"
$ set -x
$ date
+ date
Mon, Oct 31, 2016  0:00:00 AM           # Happy Halloween!
$ myfunc
+ myfunc                                # Note that function call is traced.
+ date
Mon, Oct 31, 2016  0:00:01 AM
$ myalias
+ date                                  # Note that it doesn’t say  + myalias
Mon, Oct 31, 2016  0:00:02 AM

(Observe que os seguintes trechos de script funcionam se o shebang for #!/bin/sh, mesmo se /bin/shfor um link para o bash. Mas, se o shebang for #!/bin/bash, você precisará adicionar um shopt -s expand_aliasescomando para que os aliases funcionem em um script.)

Então, para o meu primeiro truque:

alias echo='{ set +x; } 2> /dev/null; builtin echo'

Agora, quando dizemos echo "Message", estamos chamando o alias, que não é rastreado. O alias desativa a opção de rastreamento, enquanto suprime a mensagem de rastreamento do setcomando (usando a técnica apresentada primeiro na resposta do usuário5071535 ) e, em seguida, executa o echocomando real . Isso nos permite obter um efeito semelhante ao da resposta do usuário5071535 sem precisar editar o código a cada echo comando. No entanto, isso deixa o modo de rastreamento desativado. Não podemos colocar um set -xno alias (ou pelo menos não facilmente) porque um alias permite apenas que uma string seja substituída por uma palavra; nenhuma parte da cadeia de alias pode ser injetada no comando após os argumentos (por exemplo, "Message"). Então, por exemplo, se o script contiver

date
echo "The quick brown fox"
echo "jumps over the lazy dog."
date

a saída seria

+ date
Mon, Oct 31, 2016  0:00:03 AM
The quick brown fox
jumps over the lazy dog.
Mon, Oct 31, 2016  0:00:04 AM           # Note that it doesn’t say  + date

portanto, você ainda precisa ativar a opção de rastreamento novamente após exibir as mensagens - mas apenas uma vez após cada bloco de echocomandos consecutivos :

date
echo "The quick brown fox"
echo "jumps over the lazy dog."
set -x
date


Seria bom se pudéssemos fazer o set -xautomático depois de um echo- e podemos, com um pouco mais de truque. Mas antes de apresentar isso, considere isso. O OP está começando com scripts que usam um #!/bin/sh -exshebang. Implicitamente, o usuário pode remover o xdo shebang e ter um script que funcione normalmente, sem rastreamento de execução. Seria bom se pudéssemos desenvolver uma solução que retenha essa propriedade. As primeiras respostas aqui falham nessa propriedade porque ativam o rastreio após as echoinstruções após , incondicionalmente, sem considerar se ela já estava ativada.  Esta resposta conspicuamente falha em reconhecer esse problema, pois substitui echosaída com saída de rastreamento; portanto, todas as mensagens desaparecem se o rastreamento estiver desativado. Agora, apresentarei uma solução que ativa o rastreamento após uma echodeclaração condicionalmente - apenas se ela já estiver ativada. Fazer o downgrade para uma solução que ative o retorno incondicionalmente é trivial e é deixado como um exercício.

alias echo='{ save_flags="$-"; set +x;} 2> /dev/null; echo_and_restore'
echo_and_restore() {
        builtin echo "$*"
        case "$save_flags" in
         (*x*)  set -x
        esac
}

$-é a lista de opções; uma concatenação das letras correspondentes a todas as opções definidas. Por exemplo, se as opções ee xestiverem definidas, $-haverá um amontoado de letras que inclui ee x. Meu novo alias (acima) salva o valor de $-antes de desativar o rastreio. Em seguida, com o rastreamento desativado, ele lança o controle sobre uma função shell. Essa função faz o real echo e depois verifica se a xopção foi ativada quando o alias foi chamado. Se a opção estava ativada, a função a ativa novamente; se estiver desativado, a função o deixará desativado.

Você pode inserir as sete linhas acima (oito, se você incluir um shopt) no início do script e deixar o resto em paz.

Isso permitiria que você

  1. para usar qualquer uma das seguintes linhas shebang:
    #! / bin / sh -ex
    #! / bin / sh -e
    #! / bin / sh –x
    ou simplesmente
    #! / bin / sh
    e deve funcionar como esperado.
  2. ter código como
    
    comando 
    (shebang) 1 comando 2
     comando 3
    set -x
    comando 4
     comando 5
     comando 6
    set + x
    comando 7
     comando 8
     comando 9
    e
    • Os comandos 4, 5 e 6 serão rastreados - a menos que um deles seja um echo; nesse caso, ele será executado, mas não rastreado. (Mas, mesmo que o comando 5 seja um echo, o comando 6 ainda será rastreado.)
    • Os comandos 7, 8 e 9 não serão rastreados. Mesmo se o comando 8 for um echo, o comando 9 ainda não será rastreado.
    • Os comandos 1, 2 e 3 serão rastreados (como 4, 5 e 6) ou não (como 7, 8 e 9), dependendo da inclusão do shebang x.

PS Descobri que, no meu sistema, posso deixar de fora a builtinpalavra - chave na minha resposta do meio (aquela que é apenas um apelido echo). Isto não é surpreendente; o bash (1) diz que, durante a expansão do alias,…

... uma palavra idêntica a um alias sendo expandido não é expandida uma segunda vez. Isso significa que é possível usar um alias lspara ls -F, por exemplo, e o bash não tenta expandir recursivamente o texto de substituição.

Não é de surpreender que a última resposta (aquela com echo_and_restore) falhe se a builtinpalavra-chave for omitida 1 . Mas, estranhamente, funciona se eu excluir o builtine alternar a ordem:

echo_and_restore() {
        echo "$*"
        case "$save_flags" in
         (*x*)  set -x
        esac
}
alias echo='{ save_flags="$-"; set +x;} 2> /dev/null; echo_and_restore'

__________
1  Parece dar origem a um comportamento indefinido. eu tenho visto

  • um loop infinito (provavelmente por causa de recursão ilimitada),
  • uma /dev/null: Bad addressmensagem de erro e
  • um core dump.

2
Eu já vi alguns truques de mágica incríveis feitos com pseudônimos, então eu sei que meu conhecimento está incompleto. Se alguém puder apresentar uma maneira de fazer o equivalente echo +x; echo "$*"; echo -xem um apelido, eu gostaria de vê-lo.
G-Man diz 'Reinstate Monica'

11

Encontrei uma solução parcial no InformIT :

#!/bin/bash -ex
set +x; 
echo "shell tracing is disabled here"; set -x;
echo "but is enabled here"

saídas

set +x; 
shell tracing is disabled here 
+ echo "but is enabled here"
but is enabled here

Infelizmente, isso ainda ecoa set +x, mas pelo menos fica quieto depois disso. então é pelo menos uma solução parcial para o problema.

Mas existe talvez uma maneira melhor de fazer isso? :)


2

Dessa maneira, você melhora sua própria solução, eliminando a set +xsaída:

#!/bin/bash -ex
{ set +x; } 2>/dev/null
echo "shell tracing is disabled here"; set -x;
echo "but is enabled here"

2

Coloque set +xdentro dos colchetes, para que se aplicasse apenas ao escopo local.

Por exemplo:

#!/bin/bash -x
exec 3<> /dev/null
(echo foo1 $(set +x)) 2>&3
($(set +x) echo foo2) 2>&3
( set +x; echo foo3 ) 2>&3
true

produziria:

$ ./foo.sh 
+ exec
foo1
foo2
foo3
+ true

Corrija-me se estiver errado, mas não acho que o set +xinterior do subshell (ou o uso de subshells) esteja fazendo algo útil. Você pode removê-lo e obter o mesmo resultado. É o stderr de redirecionamento para / dev / null que está fazendo o trabalho de "desabilitar" o rastreio temporariamente ... Parece que echo foo1 2>/dev/nulletc. seria igualmente eficaz e mais legível.
Tyler Rick

Ter rastreamento em seu script pode afetar o desempenho. Em segundo lugar, o redirecionamento & 2 para NULL pode não ser o mesmo, quando você espera outros erros.
kenorb

Corrija-me se eu estiver errado, mas no seu exemplo você já tem o rastreamento ativado no seu script (com bash -x) e já está redirecionando &2para nulo (desde que &3foi redirecionado para nulo), por isso não tenho certeza de como esse comentário é relevante. Talvez apenas precisemos de um exemplo melhor que ilustre seu argumento, mas pelo menos no exemplo dado ainda parece que poderia ser simplificado sem perder nenhum benefício.
Tyler Rick

1

Adoro a resposta abrangente e bem explicada do g-man e considero a melhor fornecida até agora. Ele se importa com o contexto do script e não força as configurações quando elas não são necessárias. Portanto, se você estiver lendo esta resposta, vá em frente e verifique essa, todo o mérito está aí.

No entanto, nessa resposta, falta uma parte importante: o método proposto não funcionará para um caso de uso típico, ou seja, relatar erros:

COMMAND || echo "Command failed!"

Devido à forma como o alias é construído, isso será expandido para

COMMAND || { save_flags="$-"; set +x; } 2>/dev/null; echo_and_restore "Command failed!"

e você adivinhou, echo_and_restoreé executado sempre , incondicionalmente. Como a set +xpeça não foi executada, significa que o conteúdo dessa função também será impresso.

Alterar o último ;para &&também não funcionaria, porque no Bash ||e &&é associativo à esquerda .

Encontrei uma modificação que funciona para este caso de uso:

echo_and_restore() {
    echo "$(cat -)"
    case "$save_flags" in
        (*x*) set -x
    esac
}
alias echo='({ save_flags="$-"; set +x; } 2>/dev/null; echo_and_restore) <<<'

Ele usa um subshell (a (...)parte) para agrupar todos os comandos e passa a string de entrada através de stdin como uma String Here (a <<<coisa) que é impressa por cat -. O -é opcional, mas você sabe, " explícito é melhor que implícito ".

Você também pode usar cat -diretamente sem o eco , mas eu gosto dessa maneira, pois permite adicionar outras strings à saída. Por exemplo, costumo usá-lo assim:

BASENAME="$(basename "$0")"  # Complete file name
...
echo "[${BASENAME}] $(cat -)"

E agora funciona lindamente:

false || echo "Command failed"
> [test.sh] Command failed

0

O rastreamento de execução vai para stderr, filtre-o desta maneira:

./script.sh 2> >(grep -v "^+ echo " >&2)

Alguma explicação, passo a passo:

  • stderr é redirecionado ... - 2>
  • ... para um comando. ->(…)
  • grep é o comando
  • ... o que requer o início da linha ... - ^
  • … A ser seguido por + echo
  • ... então grepinverte a partida ... --v
  • ... e isso descarta todas as linhas que você não deseja.
  • O resultado normalmente iria para stdout; nós o redirecionamos para stderronde ele pertence. ->&2

O problema é (eu acho) que esta solução pode dessincronizar os fluxos. Por causa da filtragem, stderrpode ser um pouco tarde em relação a stdout(onde a echosaída pertence por padrão). Para corrigi-lo, você pode ingressar nos fluxos primeiro, se não se importar de tê-los em stdout:

./script.sh > >(grep -v "^+ echo ") 2>&1

Você pode criar essa filtragem no próprio script, mas essa abordagem é propensa à dessincronização, com certeza (ou seja, ocorreu nos meus testes: o rastreamento da execução de um comando pode aparecer após a saída do seguinte echo).

O código fica assim:

#!/bin/bash -x

{
 # original script here
 # …
} 2> >(grep -v "^+ echo " >&2)

Execute-o sem truques:

./script.sh

Novamente, use > >(grep -v "^+ echo ") 2>&1para manter a sincronização ao custo de ingressar nos fluxos.


Outra abordagem. Você obtém uma saída "um pouco redundante" e de aparência estranha porque o seu terminal mistura stdoutestderr . Esses dois fluxos são animais diferentes por um motivo. Verifique se a análise atende stderrapenas às suas necessidades; descartar stdout:

./script.sh > /dev/null

Se você tiver em seu script uma echomensagem de depuração / erro de impressão stderr, poderá se livrar da redundância da maneira descrita acima. Comando completo:

./script.sh > /dev/null 2> >(grep -v "^+ echo " >&2)

Desta vez, estamos trabalhando stderrapenas, portanto, a dessincronização não é mais uma preocupação. Infelizmente, dessa forma, você não verá um rastreio nem uma saída echoimpressa em stdout(se houver). Poderíamos tentar reconstruir nosso filtro para detectar redirecionamento ( >&2), mas se você olhar para echo foobar >&2, echo >&2 foobare echo "foobar >&2"então você provavelmente vai concordar que as coisas ficam complicadas.

Depende muito dos ecos que você possui em seus scripts. Pense duas vezes antes de implementar algum filtro complexo, pois pode sair pela culatra. É melhor ter um pouco de redundância do que acidentalmente perder algumas informações cruciais.


Em vez de descartar o rastreio de execução de um echo, podemos descartar sua saída - e qualquer saída, exceto os rastreios. Para analisar apenas os rastreamentos de execução, tente:

./script.sh > /dev/null 2> >(grep "^+ " >&2)

Infalível? Não. Pense no que acontecerá se houver echo "+ rm -rf --no-preserve-root /" >&2no script. Alguém pode sofrer um ataque cardíaco.


E finalmente…

Felizmente, há BASH_XTRACEFDvariável ambiental. De man bash:

BASH_XTRACEFD
Se definido como um número inteiro correspondente a um descritor de arquivo válido, o bash gravará a saída de rastreio gerada quando set -xestiver ativada nesse descritor de arquivo.

Podemos usá-lo assim:

(exec 3>trace.txt; BASH_XTRACEFD=3 ./script.sh)
less trace.txt

Observe que a primeira linha gera um subshell. Dessa forma, o descritor de arquivo não permanecerá válido nem a variável atribuída no shell atual posteriormente.

Graças a BASH_XTRACEFDvocê, você pode analisar traços livres de ecos e quaisquer outras saídas, quaisquer que sejam. Não é exatamente o que você queria, mas minha análise me faz pensar que este é (em geral) o caminho certo.

É claro que você pode usar outro método, especialmente quando precisar analisar stdoute / ou stderracompanhar seus rastreamentos. Você só precisa se lembrar de que existem certas limitações e armadilhas. Eu tentei mostrar (alguns) deles.


0

Em um Makefile você pode usar o @símbolo.

Exemplo de uso: @echo 'message'

A partir deste documento GNU:

Quando uma linha começa com '@', o eco dessa linha é suprimido. O '@' é descartado antes da linha ser passada para o shell. Normalmente, você usaria isso para um comando cujo único efeito é imprimir algo, como um comando echo para indicar o progresso no makefile.


Oi, o documento que você está referenciando é para gnu make, não shell. Tem certeza de que funciona? Eu recebo o erro ./test.sh: line 1: @echo: command not found, mas estou usando o bash.

Uau, desculpe, eu li completamente a pergunta. Sim, @só funciona quando você está ecoando em makefiles.
Derek

@derek Eu editei sua resposta original, agora ela afirma claramente que a solução é limitada apenas para Makefiles. Na verdade, eu estava procurando por esse, então queria que seu comentário não tivesse uma reputação negativa. Felizmente, as pessoas também acharão útil.
Shakaron

-1

Editado em 29 de outubro de 2016 por moderador, sugere que o original não continha informações suficientes para entender o que está acontecendo.

Este "truque" produz apenas uma linha de saída de mensagem no terminal quando o xtrace está ativo:

A pergunta original é : existe uma maneira de deixar -x ativado, mas apenas gera uma mensagem em vez das duas linhas . Esta é uma citação exata da pergunta.

Entendo a pergunta: como "deixar o set -x ativado E produzir uma única linha para uma mensagem"?

  • No sentido global, essa questão é basicamente estética - o questionador deseja produzir uma única linha em vez das duas linhas praticamente duplicadas produzidas enquanto o xtrace está ativo.

Portanto, em resumo, o OP exige:

  1. Para definir -x em vigor
  2. Produzir uma mensagem, legível por humanos
  3. Produza apenas uma única linha de saída de mensagem.

O OP não exige que o comando echo seja usado. Eles o citaram como um exemplo de produção de mensagens, usando a abreviação, por exemplo, que significa Latin examplei gratia, ou "por exemplo".

Pensando "fora da caixa" e abandonando o uso de eco para produzir mensagens, observo que uma declaração de atribuição pode atender a todos os requisitos.

Faça uma atribuição de uma sequência de texto (contendo a mensagem) a uma variável inativa.

A adição desta linha a um script não altera nenhuma lógica, mas produz uma única linha quando o rastreamento está ativo: (Se você estiver usando a variável $ echo , altere o nome para outra variável não utilizada)

echo="====================== Divider line ================="

O que você verá no terminal é de apenas 1 linha:

++ echo='====================== Divider line ================='

não dois, como o OP não gosta:

+ echo '====================== Divider line ================='
====================== Divider line =================

Aqui está um exemplo de script para demonstrar. Observe que a substituição de variável em uma mensagem (o nome do diretório $ HOME 4 linhas a partir do final) funciona, para que você possa rastrear variáveis ​​usando esse método.

#!/bin/bash -exu
#
#  Example Script showing how, with trace active, messages can be produced
#  without producing two virtually duplicate line on the terminal as echo does.
#
dummy="====================== Entering Test Script ================="
if [[ $PWD == $HOME ]];  then
  dummy="*** "
  dummy="*** Working in home directory!"
  dummy="*** "
  ls -la *.c || :
else
  dummy="---- C Files in current directory"
  ls -la *.c || :
  dummy="----. C Files in Home directory "$HOME
  ls -la  $HOME/*.c || :
fi

E aqui está a saída de executá-lo na raiz, depois no diretório inicial.

$ cd /&&DemoScript
+ dummy='====================== Entering Test Script ================='
+ [[ / == /g/GNU-GCC/home/user ]]
+ dummy='---- C Files in current directory'
+ ls -la '*.c'
ls: *.c: No such file or directory
+ :
+ dummy='----. C Files in Home directory /g/GNU-GCC/home/user'
+ ls -la /g/GNU-GCC/home/user/HelloWorld.c /g/GNU-GCC/home/user/hw.c
-rw-r--r-- 1 user Administrators 73 Oct 10 22:21 /g/GNU-GCC/home/user/HelloWorld.c
-rw-r--r-- 1 user Administrators 73 Oct 10 22:21 /g/GNU-GCC/home/user/hw.c
+ dummy=---------------------------------

$ cd ~&&DemoScript
+ dummy='====================== Entering Test Script ================='
+ [[ /g/GNU-GCC/home/user == /g/GNU-GCC/home/user ]]
+ dummy='*** '
+ dummy='*** Working in home directory!'
+ dummy='*** '
+ ls -la HelloWorld.c hw.c
-rw-r--r-- 1 user Administrators 73 Oct 10 22:21 HelloWorld.c
-rw-r--r-- 1 user Administrators 73 Oct 10 22:21 hw.c
+ dummy=---------------------------------

1
Observe que mesmo a versão editada da resposta excluída (da qual é uma cópia) ainda não responde à pergunta, pois a resposta não gera apenas a mensagem sem o comando de eco associado, que foi o pedido do OP.
DavidPostill

Observe que esta resposta está sendo discutida na meta. Fui penalizado por uma resposta que um moderador não gostou?
DavidPostill

@ David Eu ampliei a explicação, por comentários no fórum meta.
HiTechHiTouch 30/10

@ David Mais uma vez, incentivo você a ler o OP com muito cuidado, prestando atenção ao latim ".eg". O pôster NÃO requer que o comando echo seja usado. O OP apenas faz referência ao eco como um exemplo (junto com o redirecionamento) de métodos possíveis para resolver o problema. Acontece que você também não precisa,
HiTechHiTouch 30/10

E se o OP quiser fazer algo assim echo "Here are the files in this directory:" *?
G-Man diz 'Reinstate Monica'
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.