Eu quero redirecionar stdout e stderr de um processo para um único arquivo. Como faço isso no Bash?
Eu quero redirecionar stdout e stderr de um processo para um único arquivo. Como faço isso no Bash?
Respostas:
Dê uma olhada aqui . Deveria estar:
yourcommand &>filename
(redireciona ambos stdout
e stderr
para o nome do arquivo).
#!/bin/bash
e não #!/bin/sh
, pois in requer bash.
do_something 2>&1 | tee -a some_file
Isso vai redirecionar o stderr para o stdout e o stdout some_file
e imprimi-lo no stdout.
do_something &>filename
não. +1.
Ambiguous output redirect.
alguma idéia do porquê?
$?
não se refere mais ao status de saída de do_something
, mas ao status de saída de tee
.
Você pode redirecionar o stderr para o stdout e o stdout em um arquivo:
some_command >file.log 2>&1
Consulte http://tldp.org/LDP/abs/html/io-redirection.html
Esse formato é preferível ao formato mais popular &> que só funciona no bash. No shell Bourne, pode ser interpretado como executando o comando em segundo plano. Além disso, o formato é mais legível 2 (é STDERR) redirecionado para 1 (STDOUT).
EDIT: alterou a ordem, conforme indicado nos comentários
# Close STDOUT file descriptor
exec 1<&-
# Close STDERR FD
exec 2<&-
# Open STDOUT as $LOG_FILE file for read and write.
exec 1<>$LOG_FILE
# Redirect STDERR to STDOUT
exec 2>&1
echo "This line will appear in $LOG_FILE, not 'on screen'"
Agora, o eco simples será gravado em $ LOG_FILE. Útil para daemonizing.
Para o autor da postagem original,
Depende do que você precisa alcançar. Se você apenas precisar redirecionar a entrada / saída de um comando que você chama do seu script, as respostas já serão fornecidas. O meu é sobre o redirecionamento no script atual, que afeta todos os comandos / built-ins (inclui forks) após o trecho de código mencionado.
Outra solução interessante é redirecionar para std-err / out E para o logger ou arquivo de log de uma só vez, o que envolve a divisão de "um fluxo" em dois. Essa funcionalidade é fornecida pelo comando 'tee', que pode gravar / anexar a vários descritores de arquivos (arquivos, soquetes, tubulações, etc.) de uma só vez: tee FILE1 FILE2 ...> (cmd1)> (cmd2) ...
exec 3>&1 4>&2 1> >(tee >(logger -i -t 'my_script_tag') >&3) 2> >(tee >(logger -i -t 'my_script_tag') >&4)
trap 'cleanup' INT QUIT TERM EXIT
get_pids_of_ppid() {
local ppid="$1"
RETVAL=''
local pids=`ps x -o pid,ppid | awk "\\$2 == \\"$ppid\\" { print \\$1 }"`
RETVAL="$pids"
}
# Needed to kill processes running in background
cleanup() {
local current_pid element
local pids=( "$$" )
running_pids=("${pids[@]}")
while :; do
current_pid="${running_pids[0]}"
[ -z "$current_pid" ] && break
running_pids=("${running_pids[@]:1}")
get_pids_of_ppid $current_pid
local new_pids="$RETVAL"
[ -z "$new_pids" ] && continue
for element in $new_pids; do
running_pids+=("$element")
pids=("$element" "${pids[@]}")
done
done
kill ${pids[@]} 2>/dev/null
}
Então, desde o começo. Vamos supor que temos um terminal conectado a / dev / stdout (FD # 1) e / dev / stderr (FD # 2). Na prática, poderia ser um cano, soquete ou qualquer outra coisa.
O resultado da execução de um script com a linha acima e, além disso, esta:
echo "Will end up in STDOUT(terminal) and /var/log/messages"
... é o seguinte:
$ ./my_script
Will end up in STDOUT(terminal) and /var/log/messages
$ tail -n1 /var/log/messages
Sep 23 15:54:03 wks056 my_script_tag[11644]: Will end up in STDOUT(terminal) and /var/log/messages
Se você quiser ver uma imagem mais clara, adicione estas 2 linhas ao script:
ls -l /proc/self/fd/
ps xf
bash your_script.sh 1>file.log 2>&1
1>file.log
instrui o shell a enviar STDOUT para o arquivo file.log
e 2>&1
instrui-o a redirecionar STDERR (descritor de arquivo 2) para STDOUT (descritor de arquivo 1).
Nota: A ordem importa como liw.fi apontou, 2>&1 1>file.log
não funciona.
Curiosamente, isso funciona:
yourcommand &> filename
Mas isso gera um erro de sintaxe:
yourcommand &>> filename
syntax error near unexpected token `>'
Você tem que usar:
yourcommand 1>> filename 2>&1
&>>
parece funcionar no BASH 4:$ echo $BASH_VERSION 4.1.5(1)-release $ (echo to stdout; echo to stderr > /dev/stderr) &>> /dev/null
Resposta curta: Command >filename 2>&1
ouCommand &>filename
Explicação:
Considere o seguinte código que imprime a palavra "stdout" em stdout e a palavra "stderror" em stderror.
$ (echo "stdout"; echo "stderror" >&2)
stdout
stderror
Observe que o operador '&' informa ao bash que 2 é um descritor de arquivo (que aponta para o stderr) e não um nome de arquivo. Se deixássemos de lado o '&', esse comando seria impresso stdout
em stdout e criaria um arquivo chamado "2" e gravaria stderror
nele.
Ao experimentar o código acima, você pode ver exatamente como os operadores de redirecionamento funcionam. Por exemplo, alterando qual arquivo, qual dos dois descritores 1,2
, é redirecionado para /dev/null
as duas linhas de código a seguir, exclui tudo do stdout e tudo do stderror, respectivamente (imprimindo o que resta).
$ (echo "stdout"; echo "stderror" >&2) 1>/dev/null
stderror
$ (echo "stdout"; echo "stderror" >&2) 2>/dev/null
stdout
Agora, podemos explicar por que a solução, por que o código a seguir não produz saída:
(echo "stdout"; echo "stderror" >&2) >/dev/null 2>&1
Para realmente entender isso, eu recomendo que você leia esta página da Web nas tabelas de descritores de arquivos . Supondo que você tenha feito essa leitura, podemos prosseguir. Observe que o Bash processa da esquerda para a direita; assim, o Bash vê >/dev/null
primeiro (que é o mesmo que 1>/dev/null
) e define o descritor de arquivo 1 para apontar para / dev / null em vez de stdout. Tendo feito isso, Bash então se move para a direita e vê 2>&1
. Isso define o descritor de arquivo 2 para apontar para o mesmo arquivo que o descritor de arquivo 1 (e não para o próprio descritor de arquivo 1 !!!! (consulte este recurso em ponteirospara mais informações)) . Como o descritor de arquivo 1 aponta para / dev / null e o descritor de arquivo 2 aponta para o mesmo arquivo que o descritor de arquivo 1, o descritor de arquivo 2 agora também aponta para / dev / null. Portanto, os dois descritores de arquivo apontam para / dev / null, e é por isso que nenhuma saída é renderizada.
Para testar se você realmente entende o conceito, tente adivinhar a saída quando mudarmos a ordem de redirecionamento:
(echo "stdout"; echo "stderror" >&2) 2>&1 >/dev/null
stderror
O raciocínio aqui é que, avaliando da esquerda para a direita, o Bash vê 2> & 1 e, portanto, define o descritor de arquivo 2 para apontar para o mesmo local que o descritor de arquivo 1, ou seja, stdout. Em seguida, define o descritor de arquivo 1 (lembre-se de que> / dev / null = 1> / dev / null) aponte para> / dev / null, excluindo assim tudo o que normalmente seria enviado para o padrão. Portanto, tudo o que resta é o que não foi enviado para stdout no subshell (o código entre parênteses) - ou seja, "stderror". O interessante a ser observado é que, embora 1 seja apenas um ponteiro para o stdout, o redirecionamento do ponteiro 2 para 1 via 2>&1
NÃO forma uma cadeia de ponteiros 2 -> 1 -> stdout. Se sim, como resultado do redirecionamento de 1 para / dev / null, o código2>&1 >/dev/null
daria a cadeia de ponteiros 2 -> 1 -> / dev / null e, portanto, o código não geraria nada, em contraste com o que vimos acima.
Por fim, observo que existe uma maneira mais simples de fazer isso:
Da seção 3.6.4 aqui , vemos que podemos usar o operador &>
para redirecionar ambos stdout e stderr. Assim, para redirecionar a saída stderr e stdout de qualquer comando para \dev\null
(que exclui a saída), basta digitar
$ command &> /dev/null
ou no caso do meu exemplo:
$ (echo "stdout"; echo "stderror" >&2) &>/dev/null
Principais conclusões:
2>&1 >/dev/null
é! = >/dev/null 2>&1
. Um gera saída e o outro não!Por fim, veja estes ótimos recursos:
Documentação do bash sobre redirecionamento , uma explicação das tabelas de descritores de arquivos , introdução aos ponteiros
LOG_FACILITY="local7.notice"
LOG_TOPIC="my-prog-name"
LOG_TOPIC_OUT="$LOG_TOPIC-out[$$]"
LOG_TOPIC_ERR="$LOG_TOPIC-err[$$]"
exec 3>&1 > >(tee -a /dev/fd/3 | logger -p "$LOG_FACILITY" -t "$LOG_TOPIC_OUT" )
exec 2> >(logger -p "$LOG_FACILITY" -t "$LOG_TOPIC_ERR" )
Está relacionado: Escrevendo stdOut & stderr para syslog.
Quase funciona, mas não de xinted; (
Eu queria que uma solução tivesse a saída do stdout plus stderr gravada em um arquivo de log e o stderr ainda estivesse no console. Então, eu precisava duplicar a saída stderr via tee.
Esta é a solução que encontrei:
command 3>&1 1>&2 2>&3 1>>logfile | tee -a logfile
Para a situação, quando a "tubulação" é necessária, você pode usar:
| &
Por exemplo:
echo -ne "15\n100\n"|sort -c |& tee >sort_result.txt
ou
TIMEFORMAT=%R;for i in `seq 1 20` ; do time kubectl get pods |grep node >>js.log ; done |& sort -h
Essas soluções baseadas em bash podem canalizar STDOUT e STDERR separadamente (de STDERR de "sort -c" ou de STDERR para "sort -h").
As seguintes funções podem ser usadas para automatizar o processo de alternar saídas entre stdout / stderr e um arquivo de log.
#!/bin/bash
#set -x
# global vars
OUTPUTS_REDIRECTED="false"
LOGFILE=/dev/stdout
# "private" function used by redirect_outputs_to_logfile()
function save_standard_outputs {
if [ "$OUTPUTS_REDIRECTED" == "true" ]; then
echo "[ERROR]: ${FUNCNAME[0]}: Cannot save standard outputs because they have been redirected before"
exit 1;
fi
exec 3>&1
exec 4>&2
trap restore_standard_outputs EXIT
}
# Params: $1 => logfile to write to
function redirect_outputs_to_logfile {
if [ "$OUTPUTS_REDIRECTED" == "true" ]; then
echo "[ERROR]: ${FUNCNAME[0]}: Cannot redirect standard outputs because they have been redirected before"
exit 1;
fi
LOGFILE=$1
if [ -z "$LOGFILE" ]; then
echo "[ERROR]: ${FUNCNAME[0]}: logfile empty [$LOGFILE]"
fi
if [ ! -f $LOGFILE ]; then
touch $LOGFILE
fi
if [ ! -f $LOGFILE ]; then
echo "[ERROR]: ${FUNCNAME[0]}: creating logfile [$LOGFILE]"
exit 1
fi
save_standard_outputs
exec 1>>${LOGFILE%.log}.log
exec 2>&1
OUTPUTS_REDIRECTED="true"
}
# "private" function used by save_standard_outputs()
function restore_standard_outputs {
if [ "$OUTPUTS_REDIRECTED" == "false" ]; then
echo "[ERROR]: ${FUNCNAME[0]}: Cannot restore standard outputs because they have NOT been redirected"
exit 1;
fi
exec 1>&- #closes FD 1 (logfile)
exec 2>&- #closes FD 2 (logfile)
exec 2>&4 #restore stderr
exec 1>&3 #restore stdout
OUTPUTS_REDIRECTED="false"
}
Exemplo de uso dentro do script:
echo "this goes to stdout"
redirect_outputs_to_logfile /tmp/one.log
echo "this goes to logfile"
restore_standard_outputs
echo "this goes to stdout"
@ fernando-fabreti
Além do que você fez, mudei ligeiramente as funções e removi o fechamento - e funcionou para mim.
function saveStandardOutputs {
if [ "$OUTPUTS_REDIRECTED" == "false" ]; then
exec 3>&1
exec 4>&2
trap restoreStandardOutputs EXIT
else
echo "[ERROR]: ${FUNCNAME[0]}: Cannot save standard outputs because they have been redirected before"
exit 1;
fi
}
# Params: $1 => logfile to write to
function redirectOutputsToLogfile {
if [ "$OUTPUTS_REDIRECTED" == "false" ]; then
LOGFILE=$1
if [ -z "$LOGFILE" ]; then
echo "[ERROR]: ${FUNCNAME[0]}: logfile empty [$LOGFILE]"
fi
if [ ! -f $LOGFILE ]; then
touch $LOGFILE
fi
if [ ! -f $LOGFILE ]; then
echo "[ERROR]: ${FUNCNAME[0]}: creating logfile [$LOGFILE]"
exit 1
fi
saveStandardOutputs
exec 1>>${LOGFILE}
exec 2>&1
OUTPUTS_REDIRECTED="true"
else
echo "[ERROR]: ${FUNCNAME[0]}: Cannot redirect standard outputs because they have been redirected before"
exit 1;
fi
}
function restoreStandardOutputs {
if [ "$OUTPUTS_REDIRECTED" == "true" ]; then
exec 1>&3 #restore stdout
exec 2>&4 #restore stderr
OUTPUTS_REDIRECTED="false"
fi
}
LOGFILE_NAME="tmp/one.log"
OUTPUTS_REDIRECTED="false"
echo "this goes to stdout"
redirectOutputsToLogfile $LOGFILE_NAME
echo "this goes to logfile"
echo "${LOGFILE_NAME}"
restoreStandardOutputs
echo "After restore this goes to stdout"
Em situações em que você considera o uso de coisas como exec 2>&1
eu acho mais fácil ler, se possível, reescrevendo código usando funções bash como esta:
function myfunc(){
[...]
}
myfunc &>mylog.log