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

18

Um script que escrevi faz alguma coisa e, no final, acrescenta algumas linhas ao seu próprio arquivo de log. Eu 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?

dr01
fonte
logrotateé a solução elegante
Ipor Sircer 19/09/16
1
Eu pensei nisso, mas a configuração logrotate seria mais longo do que o próprio script ...
dr01
Se o logrotate é um exagero, sua solução é o mais elegante possível. Com o sed / awk, você pode fazê-lo em uma linha, mas não sem um arquivo temporário internamente; portanto, provavelmente não é mais eficiente e provavelmente menos legível.
Kba fica com Monica 19/09/16

Respostas:

28

É possível assim, mas, como já foi dito, a opção mais segura é a geração de um novo arquivo e, em seguida, 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 tail, isso afetará o uso de memória do shell local para armazenar o conteúdo das linhas de log.

O abaixo também remove as linhas vazias, caso existam no final do arquivo de log (devido ao comportamento da avaliação do BASH "$(tail -1000 test.log)"), para não fornecer um truncamento 100% exato em todos os cenários, mas, dependendo da sua 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
parkamark
fonte
Inteligente. Marquei isso como a resposta aceita, pois não requer a instalação de ferramentas adicionais. Eu gostaria de poder aceitar a sua e a resposta de @ John1024.
dr01 20/09/16
Sua chamada. Votei na solução de esponja porque não sabia sobre ela e é garantido que não mexa nas linhas de log vazias. Esta solução tem potencial para fazer isso, dependendo do conteúdo do arquivo de log.
parkamark
21

O utilitário spongefoi projetado apenas para este caso. Se você o tiver instalado, suas duas linhas poderão ser escritas:

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

Normalmente, a leitura de um arquivo ao mesmo tempo em que você está gravando nele não é confiável. spongeresolve isso não escrevendo para myscript.logdepois tailque terminar de ler e terminar o pipe.

Instalar

Para instalar spongeem um sistema semelhante ao Debian:

apt-get install moreutils

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

yum install moreutils

Documentação

De man sponge:

spongelê a entrada padrão e a grava no arquivo especificado. Ao contrário de um redirecionamento de shell, spongeabsorve toda a sua entrada antes de gravar o arquivo de saída. Isso permite a construção de pipelines que leem e gravam no mesmo arquivo.

John1024
fonte
2
+1 Obrigado, eu não sabia sponge. Muito útil para todos aqueles que aprendi da maneira mais difícil que você não pode fazer sort importantfile.txt > importantfile.txt:)
dr01
4

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

sed -i -e :a -e '$q;N;101,$D;ba' log
JJoao
fonte
3

Para o registro, com edvocê poderia fazer algo como

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

Isso é aberto infilee rfinalizado na saída de tail -n 1000 infile(ou seja, ele insere essa saída antes da 1ª linha) e exclui do que era inicialmente a 1ª linha até o final do arquivo. Substitua ,ppor wpara editar o arquivo no local.
Lembre-se de que as edsoluções não são adequadas para arquivos grandes.

don_crissti
fonte
0

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

log()
{
   ...
}

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

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

depois, 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.1e logfilerenomeado para logfile.0.

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

Se sempre for necessário aparar estritamente 1000 linhas, o script poderá contar o número de linhas no arquivo de log 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, como com wc -c logfilee fazer a rotação com base em exceder um determinado tamanho. Dessa forma, o arquivo nunca precisa ser verificado para determinar a condição.

Kaz
fonte
0

Eu usei, em vez de mv, o cpcomando para conseguir que você possa ter alguns arquivos de log no local em que um software está sendo executado. Talvez no diretório inicial do usuário diferente ou no diretório do aplicativo e tenha todos os logs em um só lugar como links físicos. Se você usar o mvcomando, perderá o link físico. Se você usar o cpcomando, 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ê também poderá conceder direitos diferentes aos usuários e ${LOGFILE_DIR}modificar o tamanho como eu.

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

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

logrotatetalvez melhor. Mas estou feliz com esta solução.

Não se perturbe com o "", mas no meu caso existem alguns arquivos com espaços e outras letras especiais. Se eu não fizer o "" por aí ou o {}, o lote todo não funcionará bem.

Por exemplo, há um diretório em que arquivos mais antigos são automatizados compactados em um OLDFILE.ziparquivo e tudo o que é compactado também é listado em Arquivo, .zip_logportanto, também .zip_logestá neste diretório, mas no LOGFILE_DIRque eu tenho com:

ln .zip_log "${LOGFILE_DIR}/USER_ZIP_log"

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

Andreas Bartels
fonte