Estou tentando manter um diretório cheio de arquivos de log gerenciáveis. Todas as noites, quero excluir todos, exceto os 10 mais recentes. Como posso fazer isso em um único comando?
Estou tentando manter um diretório cheio de arquivos de log gerenciáveis. Todas as noites, quero excluir todos, exceto os 10 mais recentes. Como posso fazer isso em um único comando?
Respostas:
Para uma solução portátil e confiável, tente o seguinte:
ls -1tr | head -n -10 | xargs -d '\n' rm -f --
A tail -n -10
sintaxe em uma das outras respostas não parece funcionar em todos os lugares (por exemplo, não nos meus sistemas RHEL5).
E usar $()
ou ``
na linha de comando rm
corre o risco de
xargs
corrige esses dois problemas, porque ele descobre automaticamente quantos argumentos pode passar dentro do limite de caracteres e, com o -d '\n'
mesmo, será dividido apenas no limite da linha da entrada. Tecnicamente, isso ainda pode causar problemas para os nomes de arquivos com uma nova linha, mas isso é muito menos comum que os nomes de arquivos com espaços, e a única maneira de contornar as novas linhas seria muito mais complicada, provavelmente envolvendo pelo menos awk, se não perl.
Se você não possui xargs (sistemas AIX antigos, talvez?), Pode fazer um loop:
ls -1tr | head -n -10 | while IFS= read -r f; do
rm -f "$f"
done
Isso será um pouco mais lento porque gera uma separação rm
para cada arquivo, mas ainda evitará as advertências 1 e 2 acima (mas ainda sofre com novas linhas nos nomes dos arquivos).
head(1)
página de manual:-n, --lines=[-]K print the first K lines instead of the first 10; with the leading '-', print all but the last K lines of each file
head
e xargs
dê erros. As opções corretas são head -n 10
e xargs rm -f
. O comando completo éls -1tr ./ | head -n 10 | xargs rm -rf
ls -1tr
número UM não é a letra "L".
O código que você deseja incluir no seu script é
rm -f $(ls -1t /path/to/your/logs/ | tail -n +11)
A opção -1
(numérica) imprime cada arquivo em uma única linha, para segurança. A -f
opção para rm
diz para ignorar arquivos inexistentes para quando ls
não retornar nada.
/path/to/your/logs
é apenas para você entrar no caminho correto. Para encontrar o bug executar as partes ls -t /path/...
e ls -t /path/... | tail -n +11
sozinho e verificar se o resultado está correto.
+
antes do número. Ele tail
não imprime os últimos n elementos, mas todos os elementos que começam no n'ésimo. Fiz o check-in novamente e o comando definitivamente deve remover apenas elementos com mais de 10 arquivos mais recentes em todas as circunstâncias.
ls
imprime um nome de arquivo, não um caminho para que o seu rm -f
vai tentar remover os arquivos no diretório atual
Aparentemente, analisar ls
é mau .
Se cada arquivo for criado diariamente e você quiser manter os arquivos criados nos últimos 10 dias, poderá:
find /path/to/files -mtime 10 -delete
Ou se cada arquivo for criado arbitrariamente:
find /path/to/files -maxdepth 1 -type f -printf '%Ts\t%P\n' | sort -n | head -n -10 | cut -f 2- | xargs rm -rf
-mtime 10 -delete
exclui apenas arquivos modificados exatamente 10 dias atrás. Arquivos mais antigos não serão excluídos. -mtime +11 -delete
excluirá os arquivos que foram modificados há 11 ou mais dias, deixando os últimos 10 dias de arquivos de log.
%p
vez de %P
é necessário no printf
para que os arquivos cheguem tenham o caminho completo. Caso contrário, o rm -rf
não funcionará.
Eu modifiquei a abordagem de Isaac um pouco.
Agora ele funciona com o caminho desejado:
ls -d -1tr /path/to/folder/* | head -n -10 | xargs -d '\n' rm -f
A partir daqui :
#! /bin/sh
# keepnewest
#
# Simple directory trimming tool to handle housekeeping
# Scans a directory and deletes all but the N newest files
#
# Usage: cleanup <dir> <number of files to keep>
#
# v 1.0 Piers Goodhew 1/mar/2007. No rights retained.
if [ $# -ne 2 ]; then
echo 1>&2 "Usage: $0 <dir> <number of files to keep>"
exit 1
fi
cd $1
files_in_dir=`ls | wc -l`
files_to_delete=`expr $files_in_dir - $2`
if [ $files_to_delete -gt 0 ]; then
ls -t | tail -n $files_to_delete | xargs rm
if [ $? -ne 0 ]; then
echo "An error ocurred deleting the files"
exit 1
else
echo "$files_to_delete file(s) deleted."
fi
else
echo "nothing to delete!"
fi
No Bash, você pode tentar:
ls -1t | (i=0; while read f; do
if [ $i -lt 10 ]; then
((i++))
continue
else
rm -f "$f"
fi
done)
Isso pula os 10 als mais novos exclui o restante. logrotate pode ser melhor, eu só quero corrigir as respostas erradas relacionadas ao shell.
Não tenho certeza se isso ajudará alguém, mas para evitar possíveis problemas com caracteres instáveis, usei apenas ls
para executar a classificação, mas depois use os arquivos inode
para a exclusão.
Para o diretório atual, isso mantém os 10 arquivos mais recentes com base no tempo de modificação.
ls -1tri | awk '{print $1}' | head -n -10 | xargs -n1 -t -Iinode find . -inum inode -exec rm -i {} \;
Eu precisava de uma solução elegante para o busybox (roteador), todas as soluções xargs ou array eram inúteis para mim - esse comando não está disponível lá. find e mtime não é a resposta correta, pois estamos falando de 10 itens e não necessariamente de 10 dias. A resposta de Espo foi a mais curta, limpa e provavelmente a mais não universal.
Erros com espaços e quando nenhum arquivo deve ser excluído são simplesmente resolvidos da maneira padrão:
rm "$(ls -td *.tar | awk 'NR>7')" 2>&-
Versão um pouco mais educacional: podemos fazer tudo se usarmos o awk de maneira diferente. Normalmente, eu uso esse método para passar (retornar) variáveis do awk para o sh. Enquanto lemos o tempo todo que não pode ser feito, imploro para diferir: aqui está o método.
Exemplo para arquivos .tar sem problemas em relação aos espaços no nome do arquivo. Para testar, substitua "rm" pelo "ls".
eval $(ls -td *.tar | awk 'NR>7 { print "rm \"" $0 "\""}')
Explicação:
ls -td *.tar
lista todos os arquivos .tar classificados por hora. Para aplicar a todos os arquivos na pasta atual, remova a parte "d * .tar"
awk 'NR>7...
pula as 7 primeiras linhas
print "rm \"" $0 "\""
constrói uma linha: rm "nome do arquivo"
eval
executa
Como estamos usando rm
, eu não usaria o comando acima em um script! O uso mais sábio é:
(cd /FolderToDeleteWithin && eval $(ls -td *.tar | awk 'NR>7 { print "rm \"" $0 "\""}'))
No caso de usar o ls -t
comando, não haverá nenhum dano em exemplos tolos como: touch 'foo " bar'
e touch 'hello * world'
. Não que nós sempre criamos arquivos com esses nomes na vida real!
Nota. Se quiséssemos passar uma variável para o sh dessa maneira, simplesmente modificaríamos a impressão:
print "VarName="$1
para definir a variável VarName
para o valor de $1
. Várias variáveis podem ser criadas de uma só vez. Isso VarName
se torna uma variável sh normal e pode ser normalmente usada em um script ou shell posteriormente. Portanto, para criar variáveis com o awk e devolvê-las ao shell:
eval $(ls -td *.tar | awk 'NR>7 { print "VarName="$1 }'); echo "$VarName"
Com base na resposta de Isaac Freeman, mas trabalhando em qualquer diretório:
ls -1tr $PATH_TO_DELETE/* | head -n -10 | xargs -d '\n' rm -f --
Você também pode definir um prefixo, como $PATH_TO_DELETE/testFi*
Se você usar *
após o PATH
ls
comando, exibirá nomes de arquivos absolutos, pelo menos no "meu" bash :)