Captura e coleta de saída de script, erro “arquivo de entrada é arquivo de saída”?

2

Eu preciso enviar a saída do script atual, então adicionei um trap e set -ex , por exemplo

#!/bin/bash

exec &> /tmp/error.log
trap 'cat /tmp/error.log; curl http://127.0.0.1/error.php?hostname=$(hostname) -F file=@/tmp/error.log' EXIT

set -ex
wtfwtf

Quando eu o executo, estou sempre recebendo este erro, e o script PHP não recebeu o arquivo inteiro

%> cat /tmp/error.log
1.sh: line 6: wtfwtf: command not found
cat: /tmp/error.log: input file is output file

Até agora, a única solução é copiar o error.log para um novo arquivo e enviá-lo, por exemplo

#!/bin/bash

exec &> /tmp/error.log
trap 'cp /tmp/error.log 123; curl http://127.0.0.1/error.php?hostname=$(hostname) -F file=@123' EXIT

set -ex
wtfwtf

Existe alguma maneira melhor de fazer isso?

    
por daisy 07.06.2018 / 05:09

1 resposta

6

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, ? ).

    
por 07.06.2018 / 08:42