Como redirecionar stdout para um arquivo e stdout + stderr para outro?

32

Como posso alcançar

cmd >> file1 2>&1 1>>file2

Ou seja, o stdout e o stderr devem redirecionar para um arquivo (arquivo1) e apenas o stdout (arquivo2) deve redirecionar para outro (ambos no modo de acréscimo)?

Swarna Gowri
fonte

Respostas:

41

O problema é que, quando você redireciona sua saída, ela não está mais disponível para o próximo redirecionamento. Você pode canalizar teeem um subshell para manter a saída para o segundo redirecionamento:

( cmd | tee -a file2 ) >> file1 2>&1

ou se você gostaria de ver a saída no terminal:

( cmd | tee -a file2 ) 2>&1 | tee -a file1

Para evitar a inclusão do stderr do primeiro teepara file1, você deve redirecionar o stderr do seu comando para alguns descritor de arquivo (por exemplo, 3), e depois adicioná-lo ao stdout novamente:

( 2>&3 cmd | tee -a file2 ) >> file1 3>&1
# or
( 2>&3 cmd | tee -a file2 ) 3>&1 | tee -a file1

(obrigado @ fra-san)

pLumo
fonte
16

Com zsh:

cmd >& out+err.log > out.log

No modo de acréscimo:

cmd >>& out+err.log >> out.log

Em zshe desde que a mult_iosopção não tenha sido desativada, quando um descritor de arquivo (aqui 1) é redirecionado várias vezes para gravação, o shell implementa um built-in teepara duplicar a saída para todos os destinos.

Stéphane Chazelas
fonte
Não consigo entender o que out+erre outdizer aqui. Nomes de arquivos? Fluxos a serem redirecionados?
gronostaj
@gronostaj Pense que o comando dizcmd >& file1 > file2
Isaac
Esta solução preservará a ordem em que a saída foi gerada. Para realmente armazenar o stdout e o stderr (nessa ordem), você precisa de uma abordagem diferente.
Isaac
1
@Isaac, a ordem não será necessariamente preservada, pois a saída stdout passará por um canal (para um processo que a encaminha para cada arquivo) enquanto a saída stderr irá diretamente para o arquivo. De qualquer forma, não parece que o OP pediu que a saída stderr viesse após a saída stdout.
Stéphane Chazelas
3

Você pode: marcar stdout (usando um sed UNBUFFERED, ou seja: sed -u ...:), o stderr também vai para stdout (sem marcação, pois não passou por essa marcação com sed) e, assim, conseguir diferenciar os 2 no arquivo de log resultante.

O seguinte: é lento (pode ser seriamente otimizado, usando por exemplo um script perl em vez de um tempo ...; faça ...; feito, por exemplo, que gerará sub-conchas e comandos em todas as linhas!), Estranho (parece que eu preciso dos 2 {} estágios em um renomear stdout e depois no outro adicionar o "falled through" stderr), etc. Mas é: uma " prova de conceito ", que tentará manter a saída solicita o máximo de stdout e stderr o máximo possível:

#basic principle (some un-necessary "{}" to visually help see the layers):
# { { complex command ;} | sed -e "s/^/TAGstdout/" ;} 2>&1 | read_stdin_and_redispatch

#exemple:
# complex command = a (slowed) ls of several things (some existing, others not)
#  to see if the order of stdout&stderr is kept

#preparation, not needed for the "proof of concept", but needed for our specific exemple setup:
\rm out.file out_AND_err.file unknown unknown2 
touch existing existing2 existing3

#and the (slow, too many execs, etc) "proof of concept":
uniquetag="_stdout_" # change this to something unique, that will NOT appear in all the commands outputs... 
                     # avoid regexp characters ("+" "?" "*" etc) to make it easy to remove with another sed later on.

{
   { for f in existing unknown existing2 unknown2 existing3 ; do ls -l "$f" ; sleep 1; done ;
   } | sed -u -e "s/^/${uniquetag}/" ;
} 2>&1 | while IFS="" read -r line ; do
    case "$line" in
       ${uniquetag}*) printf "%s\n" "$line" | tee -a out_AND_err.file | sed -e "s/^${uniquetag}//" >> out.file ;; 
        *)            printf "%s\n" "$line"       >> out_AND_err.file ;;   
    esac; 
done;

# see the results:
grep "^" out.file out_AND_err.file
Olivier Dulac
fonte
Isso é realmente difícil de entender. (1) Por que você usa casos de uso tão complicados ( ls unknown) para imprimir algo no stderr? >&2 echo "error"seria ótimo. (2) teepode acrescentar vários arquivos ao mesmo tempo. (3) Por que não apenas em catvez de grep "^"? (4) seu script falhará quando a saída stderr começar _stdout_. (5) por que?
pLumo
@RoVo: as duas primeiras linhas comentadas mostram o algoritmo, mais simples que o exemplo de prova de conceito. 1): isso ls loopproduzirá tanto stdout quanto stderr, misturados (alternativamente), em uma ordem controlada; para que possamos verificar se mantivemos essa ordem stderr / stdout, apesar da marcação de stdout 2): gnu tail, talvez, mas não cauda regular (por exemplo, em Aix.). 3): grep "^" também mostra os dois nomes de arquivos. 4): isso pode ser alterado pela variável 5): o exemplo complicado funciona em discos antigos (ex, antigo Aix) onde eu testei (nenhum perl disponível).
Olivier Dulac
(1) o eco múltiplo para stderr e stout seria bom, mas tudo bem, não é importante, seria mais fácil de ler. (3) concordo, (4) claro, mas falhará se começar com o que a variável contiver. (5) Entendo.
pLumo
@RoVo Concordo com o seu 1). para 4), a variável pode ser tão complexa quanto a necessária para fazer o pb desaparecer (ex: uniquetag="banaNa11F453355B28E1158D4E516A2D3EDF96B3450406...)
Olivier Dulac
1
Com certeza, não é muito provável, mas de qualquer maneira isso pode introduzir um problema de segurança mais tarde. E então você pode remover essa sequência antes de imprimir em arquivo ;-)
pLumo
2

Se a ordem de saída deve ser: stdout then stderr ; não há solução apenas com redirecionamento.
O stderr deve ser armazenado em um arquivo temporal

cmd 2>>file-err | tee -a file1 >>file2
cat file-err >> file1
rm file-err

Descrição:

A única maneira de redirecionar uma saída (um fd como stdout ou stderr) para dois arquivos é reproduzi-la. O comando teeé a ferramenta correta para reproduzir um conteúdo do descritor de arquivo. Portanto, uma idéia inicial de ter uma saída em dois arquivos seria usar:

... |  tee file1 file2

Isso reproduz o stdin do tee nos dois arquivos (1 e 2), deixando a saída do tee ainda não utilizada. Mas precisamos acrescentar (usar -a) e precisamos apenas de uma cópia. Isso resolve os dois problemas:

... | tee -a file1 >>file2

Para fornecer teecom stdout (aquele a repetir), precisamos consumir stderr diretamente fora do comando. De uma maneira, se a ordem não for importante (a ordem de saída (provavelmente) será preservada conforme gerada, o que ocorrer primeiro será armazenado primeiro). Ou:

  1. cmd 2>>file1 | tee -a file2 >>file1
  2. cmd 2>>file1 > >( tee -a file2 >>file1 )
  3. ( cmd | tee -a file2 ) >> file1 2>&1

A opção 2 funciona apenas em algumas conchas. A opção 3 usa um subshell adicional (mais lento), mas usa os nomes de arquivo apenas uma vez.

Porém, se o stdout precisar ser o primeiro (qualquer que seja a saída da ordem gerada), precisamos armazenar o stderr para anexá-lo ao arquivo no final (primeira solução publicada).

Isaac
fonte
1
Ou armazene na memória como sponge:(cmd | tee -a out >> out+err) 2>&1 | sponge >> out+err
Stéphane Chazelas
1

No interesse da diversidade:

Se o seu sistema suportar /dev/stderr, então

(cmd | tee -a /dev/stderr) 2>> file1 >> file2

vai funcionar. A saída padrão do cmd é enviada para o stdout e o stderr do pipeline. O erro padrão do cmddesvio tee e sai do stderr do pipeline.

tão

  • o stdout do pipeline é apenas o stdout do cmde
  • o stderr do pipeline é o stdout e o stderr do cmd, misturado.

É uma simples questão de enviar esses fluxos para os arquivos corretos.

Como em quase qualquer abordagem como essa (incluindo a resposta de Stéphane ), as file1linhas podem ficar fora de ordem.

Scott
fonte