
Contexto: tenho um script Bash que copia arquivos
function log () {
read IN
if [ "$IN" == "" ]; then
:
else
echo "$datetime"$'\t'"$IN" | tee -a logfile
fi
}
function copy () {
command cp -L --parents $@
}
...
copy -R /etc . 2>&1 | log
...
Problema: Quando cp -L --parents -R /etc 2>&1
é executado manualmente, recebo cerca de 10 falhas (symlinks quebrados, esperado) e todo o /etc foi copiado.
Mas quando o script é executado, apenas 1 falha é relatada e /etc é copiado apenas até o ponto onde ocorreu a 1 falha.
Na tentativa de solucionar o problema, tudo que fiz foi remover 2>&1
o script e a cópia ocorreu conforme o esperado.
Pergunta: Minha log
função está causando o problema ou é algum problema sintático (embora não seja uma quebra de script) na forma como o script é escrito?
Responder1
Sua log
função é a culpada. O que ele faz é: ler uma linha¹ e, se a linha não estiver vazia, imprimir um carimbo de data/hora e depois o conteúdo da linha. Isso é tudo que faz: depois de processada uma linha, ela retorna.
Ao cp
emitir uma primeira mensagem de erro, a log
função a lê e a processa. Como a log
função retorna, o processo no lado direito do tubo sai, o que faz com que a extremidade de leitura do tubo feche. Quando cp
emite uma segunda mensagem de erro, ele tenta escrever em um pipe fechado, o que faz com que ele morra devido a um erro.Sinal SIGPIPE. O erro padrão é armazenado em buffer de linha (por padrão, e cp
não tenta mudar isso), portanto, o buffer não entra em ação.
Para processar todas as linhas de entrada, você precisa read
de um loop.
log () {
while IFS= read -r IN; do
echo "$datetime"$'\t'"$IN"
done | tee -a logfile >&2
}
Também consertei a read
ligação paraIFS= read -r
para realmente ler uma linha. Removi o tratamento especial para linhas vazias, o que é inútil (não haveria linhas vazias na entrada). Presumo que você o tenha colocado para lidar com o caso quando a entrada está vazia (zero linhas de entrada), mas a maneira correta de lidar com isso é verificar o status de retorno do read
comando. Também corrigi log
a impressão com seu erro padrão, já que é usado para processar mensagens de erro.
VerAnexando um carimbo de data/hora a cada linha de saída de um comandopara outras maneiras de fazer isso.
Esteja ciente de que colocar o comando no lado esquerdo de um tubo tem uma grande desvantagem:faz com que o status de saída seja ignorado. Portanto, se cp
falhar, seu script continuará feliz. Os erros serão registrados em algum lugar, mas os comandos subsequentes serão executados normalmente e nada irá alertá-lo sobre o fato de que você deve ler os logs. No bash, ksh ou zsh, você pode definir opipefail
opçãoalém de set -e
que seu script saia com status de erro assim que qualquer comando falhar, mesmo no lado esquerdo de um pipeline.
set -o errexit -o pipefail
copy … |& log
Alternativamente, usesubstituição de processoem vez de um canal para canalizar a saída de erro através de outro processo. A substituição de processos tem advertências ligeiramente diferentes das de tubos; erros log
são efetivamente ignorados e o comando pode retornar antes de log
ser concluído (no bash, log
executado em segundo plano).
set -e
copy … 2> >(log)
¹ Quase, você IFS= read -r IN
nunca precisaria ler mais e possivelmente não estragaria a linha.