Com o exec
, você está redirecionando toda a saída do script para um arquivo de log específico.
Na armadilha, você deseja exibir o conteúdo do arquivo de log usando cat
. Como todas as saídas também são redirecionadas para esse arquivo, o GNU cat
percebe que seu arquivo de entrada e fluxo de saída padrão (que é herdado do shell) são a mesma coisa e se recusa a executar sua tarefa.
O BSD cat
não faz a mesma verificação que o GNU cat
, o que, se o script não for interrompido, resulta em um arquivo de log infinitamente grande com as mesmas poucas linhas repetidas várias vezes.
Uma solução alternativa é salvar o descritor de arquivo de saída padrão original, fazer o redirecionamento como antes e, em seguida, restabelecê-lo na armadilha.
#!/bin/bash
exec 3>&1 # make fd 3 copy of original fd 1
exec >/tmp/error.log 2>&1
# in the trap, make fd 1 copy of fd 3 and close fd 3 (i.e. move fd 3 to fd 1)
trap 'exec 1>&3-; cat /tmp/error.log; curl "http://127.0.0.1/error.php?hostname=$(hostname)" -F file=@/tmp/error.log' EXIT
set -ex
wtfwtf
Isso faz uma cópia do descritor de arquivo 1 (como fd 3) antes de redirecioná-lo para o arquivo de log. Na armadilha, nós movemos essa cópia de volta para fd 1 e fazemos a saída.
Observe que o fluxo de erros padrão, no trap, neste exemplo, ainda está conectado ao arquivo de log. Portanto, se o curl
gerar uma mensagem de diagnóstico, ela será salva no arquivo de log em vez de ser exibida no terminal (ou onde quer que o fluxo de erro padrão original tenha sido conectado).
Tomando o comentário de Stéphane Chazelas em conta:
#!/bin/sh
exit_handler () {
# 1. Make standard output be the original standard error
# (by using fd 3, which is a copy of original fd 2)
# 2. Do the same with standard error
# 3. Close fd 3.
exec >&3 2>&3 3>&-
cat "$logfile"
curl "some URL" -F "file=@$logfile"
}
logfile='/var/log/myscript.log'
# Truncate the logfile.
: >"$logfile"
# 1. Make fd 3 a copy of standard error (fd 2)
# 2. Redirect original standard output to the logfile (appending)
# 3. Redirect original standard error to the logfile (will also append)
exec 3>&2 >>"$logfile" 2>&1
# Use shell function for exit trap (for neatness)
trap exit_handler EXIT
set -ex
wtfwtf
Seu ponto é que o arquivo de log é apenas para mensagens de diagnóstico de qualquer maneira, então faz mais sentido produzir o arquivo de log para o fluxo de erro padrão original.
Ele também aponta que é perigoso usar um nome de arquivo fixo em um diretório gravável do mundo, como /tmp
. Isso ocorre porque nenhuma verificação é colocada no script para garantir que esse arquivo ainda não exista (alguém ou algum malware poderia ter criado um link simbólico /tmp/error.log
para /etc/passwd
ou seu ~/.bashrc
, por exemplo). Sua solução para isso é usar um arquivo de log persistente dedicado para o script em /var/log
(o arquivo é persistente, mas o conteúdo será limpo ao executar o script).
Uma variação disso seria usar mktemp
para criar um nome de arquivo exclusivo em $TMPDIR
(e, em seguida, remover esse arquivo na EXIT
trap, a menos que curl
falhe, caso em que rm
não ser executado desde que set -e
esteja em vigor):
#!/bin/sh
exit_handler () {
# 1. Make standard output be the original standard error
# (by using fd 3, which is a copy of original fd 2)
# 2. Do the same with standard error
# 3. Close fd 3.
exec >&3 2>&3 3>&-
cat "$logfile"
curl "some URL" -F "file=@$logfile"
rm -f "$logfile"
}
logfile=$( mktemp )
# 1. Make fd 3 a copy of standard error (fd 2)
# 2. Redirect original standard output to the logfile (appending)
# 3. Redirect original standard error to the logfile (will also append)
exec 3>&2 >>"$logfile" 2>&1
# Use shell function for exit trap (for neatness)
trap exit_handler EXIT
set -ex
wtfwtf
Seu segundo exemplo funciona, mas apenas porque você não está usando cat
no arquivo de log, não por copiá-lo.
Minip nitpick: URLs na linha de comando devem ser sempre, pelo menos, aspas duplas, pois tendem a conter caracteres que o shell pode interpretar como especiais (por exemplo, ?
).