`Estou lendo uma resposta para https://stackoverflow.com/questions/692000/how-do-i-write-standard-error-to-a-file-while-using-tee-with-a-pipe/692009#692009 , https://stackoverflow.com/a/14737103/5499118 :
{ { ./aaa.sh | tee bbb.out; } 2>&1 1>&3 | tee ccc.out; } 3>&1 1>&2
Como verifiquei, funciona conforme explicado. A resposta leva para https://unix.stackexchange.com/a/18904/266260 , que leva para https://unix.stackexchange.com/a/3540/266260 .
Não entendo por que { ... 1>&3 ... } 3>&1
funciona (como o redirecionamento posterior reverte o efeito do anterior), porque quando eu queria entender man bash:
Observe que a ordem dos redirecionamentos é significativa. Por exemplo, o comando
ls > dirlist 2>&1
direciona a saída padrão e o erro padrão para o arquivo dirlist, enquanto o comando
ls 2>&1 > dirlist
direciona apenas a saída padrão para o arquivo dirlist, porque o erro padrão foi duplicado da saída padrão antes que a saída padrão fosse redirecionada para dirlist.
Encontrei duplicação de descritores de arquivo no redirecionamento :
Os redirecionamentos são implementados por meio da família de funções do sistema dup. dup é a abreviação de duplicação e quando você faz isso, por exemplo:
3>&2
você duplica (dup2) o filedescriptor 2 no filedescriptor 3 ...
Portanto, entendo que 1>&3
duplica 3 em 1 e eles apontam para o mesmo objeto a partir desse comando man dup
.
Após um retorno bem-sucedido, os descritores de arquivo antigo e novo podem ser usados indistintamente. Eles se referem à mesma descrição de arquivo aberto.
Pela dup
explicação, não espero 3>&1
mudar nada, pois 3 e 1 já são iguais. Mas aparentemente não é o caso, pois omitir 3>&1
dos { { ./aaa.sh | tee bbb.out; } 2>&1 1>&3 | tee ccc.out; } 3>&1 1>&2
resultados embash: 3: bad file descriptor
O que (se houver) está incorreto ao explicar o redirecionamento com dup
chamadas? O que acontece internamente durante 1>&3
e 3>&1
? Talvez { }
sejam importantes aqui, mas vejo que são usados apenas para agrupamento e por man bash
:
list é simplesmente executado no ambiente de shell atual.
Então, simplificando um pouco, você tem
Agora, os redirecionamentos fora do grupo de chaves precisam ser tratados separadamente dos de dentro; eles precisam ser definidos antes que qualquer coisa no grupo de colchetes seja executada. Assim, o grupo de colchetes vê os fds 1 e 3 apontando para o mesmo lugar (a saída padrão original).
Em seguida, o pipe redireciona o stdout do lado esquerdo para o stdin do processo do lado direito. Isso também acontece antes da execução de qualquer um dos lados, ou de seus redirecionamentos.
Vamos reescrever isso com a sintaxe de substituição de processo, apenas para mostrar os redirecionamentos em ordem da esquerda para a direita. (
>(b)
executab
, disponibiliza seu stdin como um nome de arquivo e expande para esse nome. Então1> >(b)
redireciona o stdout para esse "arquivo" eb
o stdin de .)ou mesmo,
Então,
a
primeiro o stdout(1) original é copiado para o fd 3 (o redirecionamento que estava fora do grupo de chaves).Em seguida,
b
é iniciado, coma
o stdout(1) do redirecionado parab
o stdin do .a
O stderr(2) do é redirecionado para o mesmo. Agora, os fds 1 e 2 vão parab
, e o fd 3 vai para o stdout original.E, finalmente,
a
o stdout(1) de é redirecionado para onde o fd 3 vai, que é uma cópia do stdout original, desfazendo o redirecionamento anterior do stdout parab
. Agora, o fd 1 vai para o stdout original novamente, enquanto o fd 2 vai parab
. Como mencionado em outra resposta , agora você também pode fechar o fd 3 com ,3>&-
já que ele não é mais necessário.Escrito dessa forma, alguém poderia perguntar se faz algum sentido, por que não fazer isso:
E você poderia, mas a sintaxe do pipe não permite especificar qual fd ou fds redirecionar para o pipe, é sempre apenas stdout. E a substituição de processos não é um recurso POSIX, então nem todos os shells a suportam, e há o recurso irritante de que processos iniciados a partir da substituição de processos podem ser deixados em execução em segundo plano enquanto o shell principal continua, ao contrário dos pipes, onde o shell aguarda por todas as partes de um pipeline.
O
1>&2
que estava lá também "desfaz" o2>&1
fato de que quando o stderr dea
foi redirecionado para o pipe, o stdout do pipe (todo o grupo de chaves) é redirecionado de volta para o stderr do script.Acho que você está confuso porque a maioria das descrições de duplicações de descritores de arquivo são meio vagas e é fácil perder os detalhes essenciais para entender isso.
5>/path/to/file
Quando um processo abre um descritor de arquivo em um arquivo, um canal de entrada/saída é formado entre o processo e o arquivo (isso também se aplica quando o objeto aberto é um diretório, um fluxo, um arquivo de rede, etc.). Você pode imaginar isso como um pipeline entre o processo e o arquivo, e esse é um bom modelo para entender os detalhes essenciais: há duas extremidades no pipeline — uma no processo e a outra no arquivo. Este post do blog tem boas descrições do canal/pipeline de que estou falando aqui.
6>&5
A duplicação do descritor de arquivo pode ser visualizada como a criação de um segundo pipeline. O número desse pipeline é diferente no final do processo, mas a outra extremidade do novo pipeline leva ao mesmo arquivo. Os dois descritores de arquivo têm, cada um, um canal que leva ao mesmo arquivo de destino.
5>/dev/null
Posteriormente no script, o descritor de arquivo original pode ser alterado para um arquivo/dispositivo de destino diferente. Muitas descrições de redirecionamento ignoram o fato de que, quando o descritor já estava aberto, esse redirecionamento executa duas etapas em vez de uma. O canal do descritor de arquivo original (5) é fechado, removendo seu pipeline para o arquivo original, e então o descritor é aberto com um novo canal (pipeline) para o novo arquivo.
Isso não altera o descritor de arquivo duplicado (6), que ainda tem seu pipeline para o arquivo original.
5>&6
Agora, o script deseja restaurar o descritor de arquivo alterado (5) de volta ao arquivo original. O pipeline aberto para o novo destino é fechado e o descritor duplicado (6) é duplicado novamente para o descritor original (5). Agora, o descritor original é novamente conectado ao arquivo original.
6>&-
Alguns scripts agora fecham o descritor duplicado após sua finalidade ter sido cumprida. Agora, apenas o descritor original (5) é aberto para o arquivo original.
O conceito-chave é que um descritor de arquivo aberto é como um pipeline com uma extremidade no processo e a outra em um arquivo/diretório/dispositivo/fluxo/qualquer coisa. Duplicar o descritor cria algo novo na extremidade do processo, mas ele vai para o mesmo lugar na extremidade do arquivo.
Usei os descritores 5 e 6 nos meus exemplos acima, mas esse tipo de cópia de um descritor para outro descritor e sua restauração são feitos com mais frequência com os descritores para stdin (0), stdout (1) ou stderr (2).