Estou executando scripts de shell do Jenkins, que iniciam scripts de shell com as opções shebang #!/bin/sh -ex
.
De acordo comBash Shebang para manequins?, -x
, "faz com que o shell imprima um rastreamento de execução", o que é ótimo para a maioria dos propósitos - exceto para ecos:
echo "Message"
produz a saída
+ echo "Message"
Message
o que é um pouco redundante e parece um pouco estranho. Tem como deixar -x
habilitado, mas só 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?
Responder1
Quando você está com crocodilos até o pescoço, é fácil esquecer que o objetivo era drenar o pântano. - ditado popular
A questão é sobreecho
, e ainda assim a maioria das respostas até agora se concentrou em como inserir um set +x
comando. Há uma solução muito mais simples e direta:
{ echo "Message"; } 2> /dev/null
(Reconheço que talvez 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 echo
comandos 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 usandoideia de Kenorb
de abrir /dev/null
permanentemente em um descritor de arquivo não padrão (por exemplo, 3) e depois dizer 2>&3
em vez de 2> /dev/null
o tempo todo.
As primeiras quatro respostas no momento da redação deste artigo exigem fazer algo especial (e, na maioria dos casos, complicado)
todovez que você faz um echo
. Se você realmente quertodos echo
comandos para suprimir o rastreamento de execução (e por que não?), você pode fazer isso globalmente, sem usar 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 que /bin/sh
seja um link para o bash. Mas, se o shebang for #!/bin/bash
, você precisará adicionar um shopt -s expand_aliases
comando para que os aliases funcionem em um script.)
Então, para 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 set
comando (usando a técnica apresentada primeiro emresposta do usuário5071535) e, em seguida, executa o echo
comando real. Isso nos permite obter um efeito semelhante ao da resposta do usuário5071535 sem a necessidade de editar o código emtodo echo
comando. No entanto, isso deixa o modo de rastreamento desativado. Não podemos colocar a set -x
no alias (ou pelo menos não facilmente) porque um alias só permite que uma string seja substituída por uma palavra; nenhuma parte da string do alias pode ser injetada no comando
depoisos 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
então você ainda precisa reativar a opção de rastreamento depois de exibir a(s) mensagem(ns) — mas apenas uma vez após cadabloquearecho
de comandos consecutivos :
date
echo "The quick brown fox"
echo "jumps over the lazy dog."
set -x
date
Seria bomse pudéssemos fazer o set -x
automá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 #!/bin/sh -ex
shebang. Implicitamente, o usuário poderia remover o x
do shebang e ter um script que funcionasse normalmente, sem rastreamento de execução. Seria bom se pudéssemos desenvolver uma solução que mantivesse essa propriedade. As primeiras respostas aqui falham nessa propriedade porque ativam o rastreamento “de volta” após echo
as declarações, incondicionalmente, independentemente de já estar ativado.
Esta respostaclaramente falha em reconhecer essa questão, poissubstitui echo
saída com saída de rastreamento; portanto, todas as mensagens desaparecem se o rastreamento estiver desativado. Apresentarei agora uma solução que ativa o rastreamento após uma echo
declaraçãocondicionalmente- somente se já estiver ligado. Rebaixar isso para uma solução que ative o rastreamento “de volta” 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 e
e x
estiverem definidas, $-
haverá uma confusão de letras que inclui e
e x
. Meu novo alias (acima) salva o valor $-
antes de desligar o rastreamento. Então, com o rastreamento desativado, ele transfere o controle para uma função shell. Essa função faz o real echo
e depois verifica se a x
opção estava ativada quando o alias foi invocado. Se a opção estava ativada, a função a ativa novamente; se estiver desligado, a função o deixa desligado.
Você pode inserir as sete linhas acima (oito, se incluir um shopt
) no início do script e deixar o resto de lado.
Isso permitiria que você
- para usar qualquer uma das seguintes linhas shebang:
#!/bin/sh -ex #!/bin/sh -e #!/bin/sh –x
ou simplesmente#!/bin/sh
e deve funcionar conforme o esperado. - ter código como
(Shebang) comando 1 comando 2 comando 3 definir -x comando 4 comando 5 comando 6 definir +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
, caso em que será executado, mas não rastreado. (Mas mesmo que o comando 5 seja umecho
, o comando 6 ainda será rastreado.) - Os comandos 7, 8 e 9 não serão rastreados. Mesmo que o comando 8 seja 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 se o shebang inclui
x
.
- Os comandos 4, 5 e 6 serão rastreados — a menos que um deles seja um
PS: descobri que, no meu sistema, posso omitir a builtin
palavra-chave na minha resposta do meio (aquela que é apenas um apelido para echo
). Isto não é surpreendente; 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. Isto significa que se pode alias
ls
parals -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 builtin
palavra-chave for omitida 1 . Mas, estranhamente, funciona se eu excluir builtin
e mudar 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 da recursão ilimitada),
- uma
/dev/null: Bad address
mensagem de erro e - um dump principal.
Responder2
Encontrei uma solução parcial emInformeIT:
#!/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? :)
Responder3
Dessa forma, melhora sua própria solução, eliminando a set +x
saída:
#!/bin/bash -ex
{ set +x; } 2>/dev/null
echo "shell tracing is disabled here"; set -x;
echo "but is enabled here"
Responder4
Coloque set +x
entre colchetes, para que se aplique 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