Dado um script como o abaixo, se eu tentar redirecionar o stderr para um arquivo, o arquivo será truncado no ponto em que tee
for usado:
$ cat test.sh
#!/bin/bash
set -eux
echo before
echo '{ "foo": "bar" }' | tee /dev/stderr | jq .foo
echo after
$ ./test.sh 2> log
before
"bar"
after
$ cat log
{ "foo": "bar" }
+ echo after
O log
arquivo deve ter toda a saída stderr. Tudo o que vejo se executo o mesmo script sem redirecionar:
$ ./test.sh
+ echo before
before
+ echo '{ "foo": "bar" }'
+ tee /dev/stderr
+ jq .foo
{ "foo": "bar" }
"bar"
+ echo after
after
Então, por que só vejo as linhas que vêm depois tee
? Usar 2>>
não parece ajudar.
Não entendo por que isso está acontecendo. Como posso verificar a saída e ainda ter a capacidade de redirecionar todo o script stderr para um arquivo?
Isso se deve à forma como
/dev/stderr
, ou melhor,/proc/$pid/fd/$num
funciona no Linux. Lá, a abertura/dev/stderr
não duplica o descritor de arquivo 2, mas acessa o recurso ao qual o fd está conectado diretamente.Então, como
tee
normalmente trunca o arquivo no qual ele grava, o mesmo acontece comtee /dev/stderr
. No seu caso, é praticamente o mesmo que fazertee log
.Consulte O que há de errado com var=$(</dev/stdin) para ler stdin em uma variável? para mais detalhes.
Isso é apenas um problema no Linux. Por exemplo, no macOS, funciona como você imagina. Um exemplo um tanto compactado:
vs.
Agora, para fazer isso funcionar, você precisa evitar o uso
/dev/stderr
e, em vez disso, usar algo como>&2
o que diz ao shell para duplicar o descritor de arquivo 2.Eu acho que você poderia fazer isso com
tee >( cat >&2 )
. Isso é um pouco complicado, mas você não pode dizertee
para usar um fd existente, você precisa fornecer um filename e, devido ao problema acima, não pode ser um que se refira diretamente ao stderr do script.No entanto, tem o problema de ser
cat
executado em segundo plano, o que pode atrasar a saída dele, como aqui:Observe que isso simplesmente
tee -a /dev/stderr
não ajuda, pois embora isso signifique que qualquer coisatee
gravada vai para o final do arquivo, qualquer coisa escrita através do stderr original não , mas segue a posição de gravação da descrição do arquivo. Então você obtém coisas assim:onde o último
+ true<nl>
(do stderr do script) é escrito sobretruncated?
(fromtee
). Você também precisa tornar o redirecionamento original um anexo, ou sejabash ... 2>>log
, .Ou apenas faça o redirecionamento original ir para um canal em vez do arquivo. Com um canal,
/dev/stderr
funciona mais como você imagina, já que os tubos não têm posição de gravação e não podem ser truncados. A maneira de fazer isso é um pouco complicada, você precisa de algo como2> >(cat > log)
.Enquanto escrevia isso, percebi que provavelmente posso usar a
tee -a
opção.Ainda não entendo por que isso
tee /dev/stderr
truncaria o arquivo de redirecionamento