No bash, se eu quiser produzir mensagens de status periódicas, posso substituir a anterior emitindo uma combinação de teclas de atalho, por exemplo, como visto aqui , aqui ou aqui . Quando um usuário executa o script diretamente, isso é bom, pois o mantém informado sem sobrecarregar a tela.
No entanto, se quiserem usar o mesmo script em um contexto automatizado, a saída será (deve) ser redirecionada para um arquivo:
./test.sh >log.txt
Neste ponto, nenhum dos \r
, \b
, \033[0K
funciona.
Como faço para que isso funcione para ambos os modos?
(Por exemplo, seria bastante simples omitir as mensagens intermediárias, se eu pudesse detectar se o fluxo de saída é um arquivo.)
- Se possível, gostaria de evitar introduzir um argumento no script. A versão do mundo real já vem com vários.
- Se possível, eu gostaria de evitar o manuseio dos fluxos de saída dentro do próprio script, por exemplo, conforme esta pergunta , para que não expresse um comportamento surpreendente quando incorporado em outros scripts.
Código de amostra test.sh
:
#!/bin/bash
PREVIOUS_JOB_STATUS=""
SECONDS=0 # auto-incremented by Bash; divide by INTERVAL_SECONDS to get number of retries
INTERVAL_SECONDS=3 # interval between polls to the server
TIMEOUT_SECONDS=10 # how long until we give up, because the server is clearly busy (or dead)
while true; do
RETRY_SECONDS=$SECONDS
if [ $RETRY_SECONDS -lt $INTERVAL_SECONDS ]; then # in the real world, here would be the polling operation
JOB_STATUS='queued'
else
JOB_STATUS='processing'
fi
RESULT=1 # in the real world, here would be ? of the polling operation
if [ $RESULT -eq 0 ]; then # success
exit $RESULT
elif [ $RETRY_SECONDS -gt $TIMEOUT_SECONDS ]; then # failure (or at least surrender)
echo "Timeout exceeded waiting for job to finish. Current Job Status is '$JOB_STATUS'. Current result code is '$RESULT'."
exit $RESULT
elif [ "$PREVIOUS_JOB_STATUS" = "$JOB_STATUS" ]; then # no change yet, replace last status message and try again
echo -en '\033[1A\r\033[K' # move one line up : \033[1A ,
# move to pos1 : \r ,
# delete everything between cursor and end-of-line: \033[K ,
# omit line break : -n
else # job status changed, write new status message and try again
PREVIOUS_JOB_STATUS=$JOB_STATUS
fi
SLEEP_MESSAGE='Waiting for job to finish. Waited '`printf "%02g" $SECONDS`" s. Current job status is '$JOB_STATUS'. Current result code is '$RESULT'."
echo $SLEEP_MESSAGE
sleep $INTERVAL_SECONDS
done
Resultado final de ./test.sh
:
Waiting for job to finish. Waited 00 s. Current job status is 'queued'. Current result code is '1'.
Waiting for job to finish. Waited 09 s. Current job status is 'processing'. Current result code is '1'.
Timeout exceeded waiting for job to finish. Current Job Status is 'processing'. Current result code is '1'.
Resultado final de ./test.sh >log.txt 2>&1
:
Waiting for job to finish. Waited 00 s. Current job status is 'queued'. Current result code is '1'.
Waiting for job to finish. Waited 03 s. Current job status is 'processing'. Current result code is '1'.
^[[1A^M^[[KWaiting for job to finish. Waited 06 s. Current job status is 'processing'. Current result code is '1'.
^[[1A^M^[[KWaiting for job to finish. Waited 09 s. Current job status is 'processing'. Current result code is '1'.
Timeout exceeded waiting for job to finish. Current Job Status is 'processing'. Current result code is '1'.
Produção final desejada de ./test.sh >log.txt 2>&1
: igual à produção final de ./test.sh
.
Use
test -t 1
outest -t 2
para testar se (respectivamente) stdout ou stderr está associado a um terminal.( fonte )
Prova de conceito:
Execute o código acima como está e você verá
foo
ebar
. Redirecione a saída para fora do terminal (por exemplo, para um arquivo normal ou canal paracat
) efoo
não será impressa.Para evitar testes sempre que precisar imprimir uma mensagem intermediária, use este truque:
Agora imprima todas as mensagens intermediárias no descritor de arquivo 5 (por exemplo
echo baz >&5
, ). Ele irá para stdout se for um terminal, caso/dev/null
contrário.