Redirecionando stderr e stdout separadamente para uma função em um script bash

3

Estou trabalhando em um script de wrapper para registrar a saída do cronjob no journald.

Eu tenho vários objetivos:

  • deve registrar stderr e stdout para journald com diferentes níveis de prioridade e tags de prefixo
  • também deve gerar stderr do comando wrapped para stderr do script wrapper (para ser capturado pelo cron e enviado por e-mail em um alerta)
  • deve preservar a ordem da linha em todo

Até agora eu pareço ter dois problemas:

  1. Não sei como redirecionar stderr e stdout separadamente para minha função de log. Tudo que eu tentei até agora não redireciona nada mais do que NULL. Em minha defesa, o redirecionamento não é um dos meus pontos strongs. (Resolvido, ver comentários)
  2. Minha abordagem atual { $_EXEC $_ARGS 2>&1 1>&7 7>&- | journaldlog error; } 7>&1 1>&2 | journaldlog info parece executar a função de registro exatamente duas vezes, uma para stderr e outra para stdout. Isso não preservará a ordem da linha. Eu preferiria executar a função uma vez por linha de saída.

Eu deveria desistir de fazer isso no bash e tentar o Perl? Tenho certeza de que minha experiência Perl voltaria para mim ... eventualmente.

Eu tentei muitas abordagens diferentes. A maioria dos quais encontrados online e, em particular, na troca de pilha. Eu honestamente não consigo lembrar o que mais eu tentei ... é apenas um borrão neste momento. E os documentos básicos também não ajudaram muito.

    
por Cliff Armstrong 21.01.2018 / 23:41

3 respostas

3

Você pode usar a substituição de processos diretamente nos redirecionamentos:

gen > >(one) 2> >(two)

Isso fornecerá a saída padrão de gen como entrada padrão para one e o erro padrão de gen como entrada para two . Aqueles poderiam ser comandos executáveis ou funções; aqui estão as funções que usei:

one() {
    while read line
    do
        echo $'\e[31m'"$line"$'\e[22m'
    done
}

two() {
    while read line
    do
        echo $'\e[32m'"$line"$'\e[22m'
        echo "$line" >&2
    done
}

Eles exibem suas linhas de entrada em vermelho e verde, respectivamente, e two também grava no erro padrão comum. Você pode fazer o que quiser dentro dos loops ou enviar a entrada para outro programa ou o que for necessário.

Observe que não existe realmente algo como "preservando a ordem da linha" quando você está nesse ponto - eles são dois fluxos separados e processos separados, e é bem possível que o agendador seja executado um processo por um tempo antes que ele dê o outro, com os dados mantidos no buffer do kernel até que ele seja lido. Eu tenho usado uma função de saída de números ímpares para stdout e até mesmo para stderr para testar, e é comum obter uma série de uma dúzia ou mais em uma linha de cada.

Seria possível obter algo mais próximo da ordem que aparece no terminal, mas provavelmente não no Bash. Programa de AC usando pipe e select poderia reconstruir uma ordenação (embora ainda não seja garantida a mesma exibição, pelo mesmo motivo: seu processo pode não estar programado por um tempo e uma vez que há um backlog, não há como dizer o que veio primeiro 1 ). Se a ordenação for de importância vital, você precisará de outra abordagem e, provavelmente, da cooperação do executável de base. Se for um requisito difícil, talvez seja necessário reconsiderar.

1 Também pode haver métodos específicos da plataforma para controlar o buffer de tubulação, mas é improvável que eles também o ajudem bastante. No Linux, você pode reduzir o tamanho do buffer tão baixo quanto o tamanho da página do sistema, mas não menor. Não conheço um que permita forçar gravações imediatamente (ou bloquear até que sejam lidas). Em qualquer caso, eles poderiam ser armazenados em buffer no final da fonte na forma de fluxos de stdio e, em seguida, você nunca veria um pedido.

    
por 22.01.2018 / 05:27
0

Esta é uma maneira de capturar stdout e stderr para diferentes pipelines de comando

#!/bin/bash
#
(
    exec 3>&1
    (
        # This is where your command would be put
        # e.g. stdbuf -oL -eL rsync -a /from /to
        #
        ( echo this is stdout; echo this is stderr >&2 ) |
            logger-for-stdout
    ) 2>&3
) | logger-for-stderr

Não tenho certeza se é possível garantir a ordenação de linhas intercaladas mesmo com stdbuf -oL -eL .

    
por 21.01.2018 / 23:57
0

Aqui está o que estou usando para redirecionar stderr e stdout separadamente para uma função:

tag() { awk '$0="['$1'] "$0; fflush();' ; }

{
    {
        echo std1; sleep 0.1;
        echo error2 >&2; sleep 0.1;
        echo error3 >&2; sleep 0.1;
        echo std4; sleep 0.1;
    } 2>&1 1>&3 | tag STDERR 1>&2 ;
} 3>&1 | tag STDOUT 3>&- ;

Isso deve fornecer a seguinte saída:

[STDOUT] std1
[STDERR] error2
[STDERR] error3
[STDOUT] std4

Funciona para mim em bash e ksh (93u +).

Observe que adiciono "sleep 0.1" entre o comando para preservar a ordem das linhas nesse caso.

    
por 21.02.2018 / 01:47