Eu explorei quase todas as perguntas semelhantes disponíveis , sem sucesso.
Deixe-me descrever o problema em detalhes:
Eu executo alguns scripts autônomos e eles podem produzir saída padrão e linhas de erro padrão. Quero capturá-los em sua ordem precisa, conforme exibido por um emulador de terminal, e depois adicionar um prefixo como "STDERR:" e "STDOUT:" a eles.
Eu tentei usar pipes e até mesmo uma abordagem baseada em epolls, sem sucesso. Eu acho que a solução está em uso pty, embora eu não seja mestre nisso. Também examinei o código-fonte do VTE do Gnome , mas isso não foi muito produtivo.
Idealmente, eu usaria o Go em vez do Bash para fazer isso, mas não consegui. Parece que os tubos proíbem automaticamente manter uma ordem correta das linhas devido ao buffer.
Alguém foi capaz de fazer algo semelhante? Ou é simplesmente impossível? Eu acho que se um emulador de terminal pode fazer isso, então não é - talvez criando um pequeno programa C manipulando os PTY (s) de maneira diferente?
Idealmente, eu usaria entrada assíncrona para ler esses 2 fluxos (STDOUT e STDERR) e depois os reimprimiria segundo minhas necessidades, mas a ordem de entrada é crucial!
NOTA: Conheço stderred, mas ele não funciona para mim com scripts Bash e não pode ser facilmente editado para adicionar um prefixo (já que basicamente envolve muitos syscalls).
Atualização: adicionada abaixo de duas sugestões
(atrasos aleatórios de menos de um segundo podem ser adicionados no script de amostra que forneci para provar um resultado consistente)
Atualização: a solução para essa pergunta também resolveria essa outra questão , como apontou o @Gilles. No entanto, cheguei à conclusão de que não é possível fazer o que é solicitado aqui e ali. Quando o uso de 2>&1
ambos os fluxos é mesclado corretamente no nível pty / pipe, mas para usá- los separadamente e na ordem correta, deve-se realmente usar a abordagem de stderred que invole o gancho do syscall e pode ser vista como suja de várias maneiras.
Ficarei ansioso para atualizar esta pergunta se alguém puder refutar o que foi dito acima.
fonte
Respostas:
Você pode usar coprocessos. Wrapper simples que alimenta as duas saídas de um determinado comando para duas
sed
instâncias (uma parastderr
a outrastdout
), que fazem a marcação.Observe várias coisas:
É um encantamento mágico para muitas pessoas (inclusive eu) - por um motivo (veja a resposta vinculada abaixo).
Não há garantia de que ocasionalmente não troque algumas linhas - tudo depende do agendamento dos coprocessos. Na verdade, é quase garantido que, em algum momento, isso acontecerá. Dito isto, se manter a ordem estritamente a mesma, você deverá processar os dados de ambos
stderr
estdin
no mesmo processo, caso contrário, o agendador do kernel pode (e irá) estragar tudo.Se eu entendi o problema corretamente, significa que você precisaria instruir o shell para redirecionar os dois fluxos para um processo (o que pode ser feito no AFAIK). O problema começa quando esse processo começa a decidir o que agir primeiro - ele teria que pesquisar as duas fontes de dados e, em algum momento, entrar em um estado em que estaria processando um fluxo e os dados chegarem aos dois fluxos antes de terminar. E é exatamente aí que quebra. Isso também significa que agrupar os syscalls de saída
stderred
é provavelmente a única maneira de alcançar o resultado desejado (e mesmo assim você poderá ter um problema quando algo se tornar multithread em um sistema multiprocessador).Quanto aos coprocessos, leia a excelente resposta de Stéphane em Como você usa o comando coproc no Bash? para uma visão aprofundada.
fonte
./test1.sh: 3: ./test1.sh: Syntax error: "(" unexpected
por copiar / colar o script)bash
com/bin/sh
(não sei por que eu tinha lá).eval $@
é bastante buggy. Use"$@"
se você deseja executar seus argumentos como uma linha de comando exata - adicionando uma camada deeval
interpretações em várias previsões difíceis de prever (e potencialmente maliciosas, se você estiver passando nomes de arquivos ou outro conteúdo que você não controla como argumentos), e falhando em citar até mais (divide nomes com espaços em várias palavras, expande os globs, mesmo que tenham sido citados anteriormente como literais, etc.).eval
fechar os descritores de arquivo nomeados em uma variável.exec {SEDo[1]}>&-
funcionará como está (sim, a falta de um$
antes do{
deliberado).Método 1. Usando descritores de arquivo e awk
Que tal algo assim usando as soluções desta seção de perguntas e respostas com o título: Existe um utilitário Unix para acrescentar carimbos de data e hora às linhas de texto? e este SO Q&A intitulado: canalize STDOUT e STDERR para dois processos diferentes no shell script? .
A abordagem
Etapa 1, criamos 2 funções no Bash que executam a mensagem de carimbo de data / hora quando chamadas:
Etapa 2, você usaria as funções acima para obter as mensagens desejadas:
Exemplo
Aqui inventei um exemplo que grava
a
em STDOUT, dorme por 10 segundos e grava a saída em STDERR. Quando colocamos essa sequência de comandos em nossa construção acima, recebemos mensagens conforme especificado.Método # 2. Usando anotação-saída
Existe uma ferramenta chamada
annotate-output
que faz parte dodevscripts
pacote que fará o que você deseja. Sua única restrição é que ele deve executar os scripts para você.Exemplo
Se colocarmos nossa sequência de comandos de exemplo acima em um script chamado
mycmds.bash
assim:Podemos então executá-lo assim:
O formato da saída pode ser controlado para a parte do registro de data e hora, mas não além disso. Mas é um resultado semelhante ao que você está procurando, por isso pode ser adequado.
fonte
stderred
você não pode determinar facilmente os limites das linhas (tentar isso seria um hack). Eu queria ver se alguém poderia me ajudar com este problema, mas, aparentemente, todo mundo quer desistir da restrição único ( ordem ), que é a base para a pergunta