Como enviar toda a saída para o `logger` no shell POSIX?

10

Eu gostaria de registrar a saída padrão e o erro padrão separadamente no .xprofileuso logger. No Bash, acho que seria algo assim:

exec 1> >(logger --priority user.notice --tag $(basename $0)) \
     2> >(logger --priority user.error --tag $(basename $0))

Como eu faria isso de uma maneira compatível com POSIX /bin/sh ?

l0b0
fonte

Respostas:

10

Não há equivalente POSIX. Você só pode executar um redirecionamento com exec, e não com um fork. Um cano requer um garfo e a concha aguarda a criança terminar.

Uma solução é colocar todo o seu código em uma função.

all_my_code () {
  
}
{ all_my_code |
  logger --priority user.notice --tag "$(basename "$0")"; } 2>&1 | 
  logger --priority user.error --tag "$(basename "$0")"

(Isso também registra qualquer erro da instância stdout do criador de logs na instância stderr. Você pode evitá-lo com mais embaralhamento do descritor de arquivo.)

Se você deseja que o shell pai saia, mesmo que os loggerprocessos ainda estejam em execução, coloque &no final das loggerinvocações.

{ all_my_code |
  logger --priority user.notice --tag "$(basename "$0")" & } 2>&1 | 
  logger --priority user.error --tag "$(basename "$0")" &

Como alternativa, você pode usar pipes nomeados.

pipe_dir=$(mktemp -d)
mkfifo "$pipe_dir/out" "$pipe_dir/err"
<"$pipe_dir/out" logger --priority user.notice --tag "$(basename "$0")" &
<"$pipe_dir/err" logger --priority user.error --tag "$(basename "$0")" &
exec >"$pipe_dir/out" 2>"$pipe_dir/err" 

rm -r "$pipe_dir"
Gilles 'SO- parar de ser mau'
fonte
Isso aparecerá no syslog como duas entradas (saída e erro) para todo o script. O exemplo na pergunta terá uma (ou duas) entrada por comando.
26915 DarkHeart
@ DarkHeart Eu não entendo o seu comentário. O código na pergunta e as duas soluções que proponho executam o mesmo número de instâncias loggere passam a mesma entrada para cada uma delas.
Gilles 'SO- stop be evil'
Desculpe, meu erro
darkheart
1
Isso parece mais simples , então eu vou com sua solução de pipes nomeados.
L0b0 27/07/2015
9

Comando POSIX / Substituição de Processo


_log()( x=0
    while  [ -e "${TMPDIR:=/tmp}/$$.$((x+=1))" ]
    do     continue; done        &&
    mkfifo -- "$TMPDIR/$$.$x"    &&
    printf %s\\n "$TMPDIR/$$.$x" || exit
    exec >&- >/dev/null
    {  rm -- "$TMPDIR/$$.$x"
       logger --priority user."$1" --tag "${0##*/}"
    }  <"$TMPDIR/$$.$x" &
)   <&- </dev/null

Você deve poder usar isso como:

exec >"$(_log notice)" 2>"$(_log error)"

Aqui está uma versão que faz uso do mktempcomando:

_log()( p=
    mkfifo "${p:=$(mktemp -u)}"    &&
    printf %s "$p"                 &&
    exec  <&- >&- <>/dev/null >&0  &&
    {   rm "$p"
        logger --priority user."$1" --tag "${0##*/}"
    }   <"$p" &
)

... que faz o mesmo, exceto que permite mktempselecionar o nome do arquivo para você. Isso funciona porque a substituição do processo não é de forma alguma mágica e funciona de maneira muito semelhante para comandar a substituição . Em vez de substituir a expansão pelo valor do comando executado dentro da mesma forma que a substituição de comando , a substituição do processo a substitui pelo nome de um link do sistema de arquivos no qual a saída pode ser encontrada.

Enquanto o shell POSIX não fornece um corolário direto para tal coisa, a emulação é muito simples. Tudo o que você precisa fazer é criar um arquivo, imprimir seu nome no padrão a partir de uma substituição de comando e, no segundo plano, executar o comando que produzirá para esse arquivo. Agora você pode apenas redirecionar para o valor dessa expansão - exatamente como na substituição de processos. E, portanto, o shell POSIX fornece todas as ferramentas necessárias, é claro - tudo o que é necessário é que você as use de uma maneira que seja mais adequada para você.

Ambas as versões acima garantem que eles destruam o link do sistema de arquivos para os pipes que eles criam / usam antes de utilizá-los. Isso significa que não há necessidade de limpeza após o fato e, mais importante, seus fluxos estão disponíveis apenas para os processos que os abrem inicialmente - e, portanto, seus links do sistema de arquivos não podem ser usados ​​como um meio de espionar / seqüestrar sua atividade de registro. Deixar seus links fs no sistema de arquivos é uma falha de segurança em potencial.


Outra maneira é envolvê-lo. Isso pode ser feito de dentro do script.

x=${x##*[!0-9]*}
_log(){ 
    logger --priority user."$1" --tag "${0##*/}"
}   2>/dev/null >&2

cd ../"$PPID.$x" 2>/dev/null &&
trap 'rm -rf -- "${TMPDIR:-/tmp}/$PPID.$x"' 0 || 
{   until cd -- "${TMPDIR:=/tmp}/$$.$x"
    do    mkdir -- "$TMPDIR/$$.$((x+=1))"
    done  && 
    x=$x "$0" "$@" | _log notice
    exit
}   2>&1 | _log error

Basicamente, isso permitiria que seu script se chamasse se ainda não o fez e obterá um diretório de trabalho em temp para inicializar.

mikeserv
fonte
1
Feito , obrigado!
L0b0 27/07/2015
@ l0b0 - Estou prestes a atualizar outro tipo como este ... É mais genérico e finalmente consegui lidar com algumas opções relacionadas aos descritores / arquivos de E / S.
mikeserv
@ l0b0 - se você quisesse usar mktempe outros comandos não-padrão, poderia ser: _log()( p=; mkfifo "${p:=$(mktemp -u)}"; printf %s "$p"; exec <&- >&- <>/dev/null >&0; { rm "$p"; logger --priority user."$1" --tag "${0##*/}"; } <"$p" &). Eu o escrevi usando uma linguagem de comando totalmente portátil de todas as maneiras - com exceção da sua. Além disso, basenamenão é um utilitário muito útil. "${0##*/}"é mais robusto e rápido.
mikeserv
Eu mudei porque só preciso que ele funcione em plataformas modernas; a questão principal era que isso .xprofile precisa ser executado sh.
L0b0 27/07/2015
1
@Wildcard - apenas um minuto e vou tentar resolver a primeira parte da pergunta, mas a resposta para a segunda é sim: o caso de borda é um zshw / multios ativado. Quanto à primeira parte: { this; can\'t; happen; before; } < this. Isso ajuda?
mikeserv