Como você mantém apenas as últimas n linhas de um arquivo de log?

12

Um script que eu escrevi faz alguma coisa e, no final, acrescenta algumas linhas ao seu próprio arquivo de log. Gostaria de manter apenas as últimas n linhas (digamos, 1000 linhas) do arquivo de log. Isso pode ser feito no final do script desta maneira:

tail -n 1000 myscript.log > myscript.log.tmp
mv -f myscript.log.tmp myscript.log

mas existe uma solução mais limpa e elegante? Talvez realizado através de um único comando?

    
por dr01 19.09.2016 / 17:03

6 respostas

15

É possível assim, mas, como outros já disseram, a opção mais segura é a geração de um novo arquivo e a movimentação desse arquivo para substituir o original.

O método abaixo carrega as linhas no BASH, portanto, dependendo do número de linhas de tail , isso afetará o uso de memória do shell local para armazenar o conteúdo das linhas de registro.

O exemplo a seguir também remove linhas vazias, caso existam no final do arquivo de log (devido ao comportamento do BASH que avalia "$(tail -1000 test.log)" ), portanto não fornece truncamento 100% preciso em todos os cenários, mas dependendo da situação , pode ser suficiente.

$ wc -l myscript.log
475494 myscript.log

$ echo "$(tail -1000 myscript.log)" > myscript.log

$ wc -l myscript.log
1000 myscript.log
    
por 19.09.2016 / 17:41
15

O utilitário sponge é projetado apenas para este caso. Se você tiver instalado, suas duas linhas podem ser escritas:

tail -n 1000 myscript.log | sponge myscript.log

Normalmente, a leitura de um arquivo ao mesmo tempo em que você está escrevendo não é confiável. sponge resolve isso não escrevendo para myscript.log até que tail tenha terminado de ler e terminado o pipe.

Instalar

Para instalar o sponge em um sistema parecido com o Debian:

apt-get install moreutils

Para instalar sponge em um sistema RHEL / CentOS, adicione o repositório EPEL e faça:

yum install moreutils

Documentação

De man sponge :

sponge reads standard input and writes it out to the specified file. Unlike a shell redirect, sponge soaks up all its input before writing the output file. This allows constructing pipelines that read from and write to the same file.

    
por 19.09.2016 / 17:51
2

definitivamente "tail + mv" é muito melhor! Mas para o gnu sed podemos tentar

sed -i -e :a -e '$q;N;101,$D;ba' log
    
por 19.09.2016 / 17:55
2

Para o registro, com ed você poderia fazer algo como

ed -s infile <<\IN
0r !tail -n 1000 infile
+1,$d
,p
q
IN

Isso abre infile e r ead na saída de tail -n 1000 infile (ou seja, insere essa saída antes da primeira linha) e, em seguida, exclui do que foi inicialmente a primeira linha até o final do arquivo. Substitua ,p por w para editar o arquivo no local.
Tenha em mente que as soluções ed não são adequadas para arquivos grandes.

    
por 28.09.2016 / 19:55
0

O que você pode fazer no seu script é implementar a lógica de rotação de log. Faça todo o log através de uma função:

log()
{
   ...
}

Essa função, em primeiro lugar, faz algo como:

printf "%s\n" "$*" >> logfile

então, verifica o tamanho do arquivo ou decide de alguma forma que o arquivo requer rotação. Nesse ponto, o arquivo logfile.1 , se existir, é removido, o arquivo logfile.0 , se existir, é renomeado para logfile.1 e logfile é renomeado para logfile.0 .

A decisão de girar pode ser baseada em um contador mantido no próprio script. Quando atinge 1000, é redefinido para zero.

Se sempre for necessário aparar rigorosamente até 1000 linhas, o script poderá contar o número de linhas no arquivo de registro quando for iniciado e inicializar o contador de acordo (ou se a contagem já atender ou exceder 1000, faça a rotação imediatamente ).

Ou você pode obter o tamanho, por exemplo, com wc -c logfile e fazer a rotação com base em exceder um determinado tamanho. Dessa forma, o arquivo nunca precisa ser verificado para determinar a condição.

    
por 28.09.2016 / 19:35
0

Eu usei, em vez de mv , o comando cp para conseguir que você tenha alguns arquivos de log no lugar onde um Software está sendo executado. Talvez no diretório home User diferente ou no diretório app e tenha todos os logs em um só lugar como hardlinks. Se você usar o comando mv , perderá o link físico. Se você usar o comando cp , você manterá esse link físico.

meu código é algo como:

TMP_FILE="$(mktemp "${TMPFILENAME}.XXX")"

for FILE in "${LOGFILE_DIR}"/* ; do
    tail -n $MAXLINES "${FILE}" > "${TMP_FILE}"
    if [ $(ls -g "${TMP_FILE}" | awk '{print $4}') -lt $(ls -g "${FILE}" | awk '{print $4}') ] ; then
        cp "${TMP_FILE}" "${FILE}"
    fi
done   

Portanto, se os arquivos estiverem no mesmo sistema de arquivos, você poderá conceder também alguns direitos diferentes aos usuários e, em ${LOGFILE_DIR} , modificar o comprimento como eu faço.

Se for o comando mv , você perde o hardlink entre os arquivos e, portanto, o segundo arquivo não está mais conectado ao primeiro - talvez colocado em algum outro lugar.

Se, em outro lugar, você não permitir que alguém apague o arquivo, seus logs permanecerão juntos e serão controlados com facilidade pelo seu próprio script.

logrotate talvez melhor. Mas estou feliz com esta solução.

Não seja perturbado pelo "" mas no meu caso existem alguns arquivos com espaços e outras letras especiais e Se eu não fizer o "" redor ou o {} o lote inteiro não funciona bem .

Por exemplo, há um diretório em que arquivos antigos são compactados automaticamente em um OLDFILE.zip e tudo que é compactado também está listado no arquivo .zip_log , então o .zip_log também está nesse diretório, mas no LOGFILE_DIR Eu tenho com:

ln .zip_log "${LOGFILE_DIR}/USER_ZIP_log"

o arquivo igual, pois é um link físico.

    
por 03.05.2017 / 00:24