tail - segue vários arquivos e preserva linhas atômicas

0

Eu quero seguir vários arquivos de log e enviar as linhas de entrada para um único pipe. No entanto, isso sem pensar muito (por exemplo, tail -F ) pode produzir linhas quebradas: por exemplo, as linhas ABC\n e XYZ\n de dois logs podem ser misturadas e se tornar ABXYZ\nC\n .

Veja um exemplo:

$ >a >b
$ (echo -n a >>a; sleep 2; echo A >>a) &
$ (echo -n b >>b; sleep 2; echo B >>b) &
$ tail -Fq a b

Idealmente, isso produz aA\n e bB\n . Na realidade, algo como abA\nB\n é produzido.

Como faço para produzir essas linhas sem que elas se misturem?

Aqui estão algumas coisas que eu tentei

  • Em vez de usar um único tail -Fq , tentei separar tail instâncias para cada arquivo:

    $ (trap 'kill 0' EXIT; tail -F a & tail -F b & wait)
    

    No entanto, acho que isso apenas move o problema de tail para o buffer de pipe e o problema não é resolvido.

  • Use instâncias separadas e use grep para armazenar em buffer cada linha.

    $ (trap 'kill 0' EXIT; tail -F a | grep -F '' & tail -F b | grep -F '' & wait)
    

    Isso parece funcionar. No entanto, não tenho certeza de como isso é durável. Eu acho que tem as mesmas restrições discutidas nesta questão: É echo atômico quando se escreve um single linhas

    (Além disso, existe uma maneira melhor de fazer o que o grep -F '' faz aqui?)

por antak 17.01.2017 / 01:59

1 resposta

0

Aqui está uma solução que funciona sem instalar programas extras, como multitail .

É muito semelhante ao segundo exemplo da pergunta, mas o comando a usar é grep -F '' --line-buffered . grep -F here pode ser substituído por fgrep para breve. Em relação a grep -F / fgrep versus normal regex grep, usar grep de cadeia fixa é ligeiramente mais rápido do que algo como grep ^ --line-buffered para o mesmo propósito.

Juntando tudo, uma versão com várias linhas é:

(
trap 'kill 0' EXIT
tail -F a | fgrep '' --line-buffered &
tail -F b | fgrep '' --line-buffered &
wait
)

A subshell ( ) pode não ser necessária se isso estiver ocorrendo em um script de shell. Para transformá-lo em um one-liner, livre-se das quebras de linha e coloque ponto e vírgula ( ; ) no final das linhas que não terminam com um e comercial ( & ).

A solução em profundidade

Na verdade, existem dois problemas que isso lida:

Primeiro, tail -F consumirá e produzirá os bytes nos arquivos da maneira como eles são exibidos, sem esperar pelo fim da linha. Este é apenas o jeito que é e tail atualmente não fornece nenhuma maneira de alterar isso. Portanto, não podemos fazer tail -Fq a b e, em vez disso, usar processos separados para cada arquivo.

Em segundo lugar, depois de fazer tail -F em cada arquivo, o problema continua sendo que a saída pode ser misturada no buffer do tubo. Como tail é liberado em bytes arbitrários e não em linhas inteiras, há uma ampla causa para isso. A especificação de stdbuf -oL para o buffer de linha tail não altera isso, pois a cauda parece sobrescrever isso.

Para contornar o segundo problema, precisamos usar algo como grep para esperar em linhas inteiras antes de produzir. Além disso, precisamos especificar --line-buffered caso contrário, o próprio grep armazenará em buffer sua saída e liberará quando o buffer estiver cheio, o que pode não estar em um limite de linha.

Diversos

Para explicar o que trap 'kill 0' EXIT ... wait faz, é necessário evitar que tail -F processos sejam deixados para trás quando algo como Ctrl - C é costumava sair.

    
por 18.01.2017 / 06:15

Tags