Os problemas com as respostas existentes:
- incapacidade de lidar com nomes de arquivos com espaços incorporados ou novas linhas.
- no caso de soluções que invocam
rm
diretamente em uma substituição de comando não citada ( rm `...`
), há um risco adicional de globbing não intencional.
- incapacidade de distinguir entre arquivos e diretórios (ou seja, se os diretórios estiverem entre os 5 itens do sistema de arquivos modificados mais recentemente, você efetivamente reterá menos de 5 arquivos e a aplicação
rm
nos diretórios falhará).
A resposta do wnoise trata desses problemas, mas a solução é específica para o GNU (e bastante complexa).
Aqui está uma solução pragmática, compatível com POSIX , que vem com apenas uma ressalva : ela não pode lidar com nomes de arquivos com novas linhas incorporadas - mas não considero isso uma preocupação do mundo real para a maioria das pessoas.
Para o registro, eis a explicação de por que geralmente não é uma boa ideia analisar a ls
saída: http://mywiki.wooledge.org/ParsingLs
ls -tp | grep -v '/$' | tail -n +6 | xargs -I {} rm -- {}
O acima exposto é ineficiente , porque xargs
precisa chamar rm
uma vez para cada nome de arquivo.
Sua plataforma xargs
pode permitir que você solucione este problema:
Se você possui o GNU xargs
, use -d '\n'
, o que xargs
considera cada linha de entrada um argumento separado, mas passa tantos argumentos quanto cabem na linha de comando ao mesmo tempo :
ls -tp | grep -v '/$' | tail -n +6 | xargs -d '\n' -r rm --
-r
( --no-run-if-empty
) garante que rm
não seja chamado se não houver entrada.
Se você possui BSD xargs
(inclusive no macOS ), pode usar -0
para manipular NUL
entradas separadas, depois de traduzir novas linhas para NUL
( 0x0
) chars., Que também passa (normalmente) todos os nomes de arquivos de uma só vez (também funcionará com o GNU xargs
):
ls -tp | grep -v '/$' | tail -n +6 | tr '\n' '\0' | xargs -0 rm --
Explicação:
ls -tp
imprime os nomes dos itens do sistema de arquivos classificados por quão recentemente foram modificados, em ordem decrescente (itens modificados mais recentemente primeiro) ( -t
), com diretórios impressos com um final /
para marcá-los como tal ( -p
).
grep -v '/$'
em seguida, elimina os diretórios da lista resultante, omitindo ( -v
) as linhas que possuem um final /
( /$
).
- Advertência : Como um link simbólico que aponta para um diretório não é tecnicamente um diretório, esses links simbólicos não serão excluídos.
tail -n +6
ignora os primeiros 5 entradas no perfil, na verdade retornando todos , mas os 5 arquivos modificados mais recentemente, se houver.
Observe que, para excluir N
arquivos, N+1
deve ser passado para tail -n +
.
xargs -I {} rm -- {}
(e suas variações) então invoca rm
todos esses arquivos; se não houver correspondências, xargs
não fará nada.
xargs -I {} rm -- {}
define o espaço reservado {}
que representa cada linha de entrada como um todo ; portanto, rm
é chamado uma vez para cada linha de entrada, mas com nomes de arquivos com espaços incorporados manipulados corretamente.
--
em todos os casos garante que quaisquer nomes que acontecem para começar -
não são confundidos com opções por rm
.
Uma variação no problema original, caso os arquivos correspondentes precisem ser processados individualmente ou coletados em uma matriz de shell :
# One by one, in a shell loop (POSIX-compliant):
ls -tp | grep -v '/$' | tail -n +6 | while IFS= read -r f; do echo "$f"; done
# One by one, but using a Bash process substitution (<(...),
# so that the variables inside the `while` loop remain in scope:
while IFS= read -r f; do echo "$f"; done < <(ls -tp | grep -v '/$' | tail -n +6)
# Collecting the matches in a Bash *array*:
IFS=$'\n' read -d '' -ra files < <(ls -tp | grep -v '/$' | tail -n +6)
printf '%s\n' "${files[@]}" # print array elements
ls
no diretório atual, os caminhos para os arquivos conterão '/', o que significa quegrep -v '/'
não corresponderá a nada. Eu acreditogrep -v '/$'
que é o que você deseja excluir apenas diretórios.grep
comando conceitualmente mais claro. Observe, no entanto, que o problema que você descreve não teria surgido com um único caminho de diretório; por exemplo,ls -p /private/var
ainda imprimiria apenas meros nomes de arquivos. Somente se você passasse vários argumentos de arquivo (normalmente via glob) você veria os caminhos reais na saída; por exemplo,ls -p /private/var/*
(e você também veria o conteúdo dos subdiretórios correspondentes, a menos que você também incluísse-d
).Remova todos, exceto 5 (ou qualquer número) dos arquivos mais recentes de um diretório.
fonte
ls -t
parals -td *.bz2
ls -t | awk 'NR>1'
(eu só queria o mais recente). Obrigado!ls -t | awk 'NR>5' | xargs rm -f
se você prefere pipes e precisa suprimir o erro, se não houver nada a ser excluído.touch 'hello * world'
, isso excluirá absolutamente tudo no diretório atual .Esta versão suporta nomes com espaços:
fonte
(ls -t|head -n 5;ls)
é um grupo de comandos . Imprime os 5 arquivos mais recentes duas vezes.sort
coloca linhas idênticas juntas.uniq -u
remove duplicatas, para que todos, exceto os 5 arquivos mais recentes, permaneçam.xargs rm
chamarm
cada um deles.--no-run-if-empty
axargs
como(ls -t|head -n 5;ls)|sort|uniq -u|xargs --no-run-if-empty rm
, atualize a resposta.touch 'foo " bar'
descartará todo o restante do comando.xargs -d $'\n'
do que injetar aspas no seu conteúdo, embora a delimitação NUL do fluxo de entrada (que requer o uso de algo diferente dols
que realmente seja feito corretamente) seja a opção ideal.Variante mais simples da resposta de thelsdj:
ls -tr exibe todos os arquivos, os mais antigos primeiro (-t os mais novos primeiro, -r reverso).
head -n -5 exibe todas, exceto as 5 últimas linhas (ou seja, os 5 arquivos mais recentes).
xargs rm chama rm para cada arquivo selecionado.
fonte
-1
é padrão quando a saída é para um pipeline, portanto, não é obrigatório aqui. Isso tem problemas muito maiores, relacionados ao comportamento padrãoxargs
ao analisar nomes com espaços, aspas, etc.--no-run-if-empty
não é reconhecido no meu shell. Estou usando o Cmder no Windows.-0
opção se os nomes de arquivos puderem conter espaços em branco. Ainda não testei ainda. fonteRequer localização GNU para -printf, e classificação GNU para -z, e GNU awk para "\ 0" e GNU xargs para -0, mas lida com arquivos com novas linhas ou espaços incorporados.
fonte
awk
lógica. Estou perdendo alguns requisitos da pergunta do OP que o tornam necessário?while read -r -d ' '; IFS= -r -d ''; do ...
loop de shell - a primeira leitura termina no espaço, enquanto a segunda continua no NUL.sed -z -e 's/[^ ]* //; 1,5d'
é o mais claro. (ou talvezsed -n -z -e 's/[^ ]* //; 6,$p'
.Todas essas respostas falham quando há diretórios no diretório atual. Aqui está algo que funciona:
Este:
funciona quando existem diretórios no diretório atual
tenta remover cada arquivo, mesmo que o anterior não possa ser removido (devido a permissões etc.)
falha na segurança quando o número de arquivos no diretório atual é excessivo e
xargs
normalmente atrapalhava você (o-x
)não atende a espaços nos nomes de arquivos (talvez você esteja usando o sistema operacional errado?)
fonte
find
retornar mais nomes de arquivo do que pode ser passado em uma única linha de comandols -t
? (Dica: você obtém várias execuçõesls -t
, cada uma das quais é classificada apenas individualmente, em vez de ter uma ordem de classificação globalmente correta; portanto, essa resposta é muito quebrada ao executar com diretórios suficientemente grandes).Listar nomes de arquivos por hora da modificação, citando cada nome de arquivo. Excluir os 3 primeiros (3 mais recentes). Remova o restante.
EDITAR após um comentário útil de mklement0 (obrigado!): Corrigiu o argumento -n + 3 e observe que isso não funcionará conforme o esperado se os nomes de arquivos contiverem novas linhas e / ou o diretório contiver subdiretórios.
fonte
-Q
opção parece não existir na minha máquina.-Q
. Sim,-Q
é uma extensão GNU (aqui está a especificação POSIXls
). Uma pequena ressalva (raramente um problema na prática):-Q
codifica novas linhas incorporadas nos nomes de arquivos como literais\n
, querm
não serão reconhecidas. Para excluir os 3 primeiros , oxargs
argumento deve+4
. Finalmente, uma ressalva que se aplica à maioria das outras respostas também: seu comando só funcionará como planejado se não houver subdiretórios no diretório atual.--no-run-if-empty
opção:ls -tQ | tail -n+4 | xargs --no-run-if-empty rm
Ignorar novas linhas é ignorar a segurança e a boa codificação. Wnoise teve a única boa resposta. Aqui está uma variação dele que coloca os nomes dos arquivos em uma matriz $ x
fonte
IFS
- caso contrário, você correria o risco de perder espaço em branco nos nomes de arquivos. Pode escopo isso para o comando de leitura:while IFS= read -rd ''; do
"${REPLY#* }"
?Se os nomes de arquivos não tiverem espaços, isso funcionará:
Se os nomes de arquivos tiverem espaços, algo como
Lógica básica:
fonte
while read
truque para lidar com espaços:ls -C1 -t | awk 'NR>5' | while read d ; do rm -rvf "$d" ; done
while IFS= read -r d
seria um pouco melhor --r
evita que os literais de barra invertida sejam consumidosread
eIFS=
evita o corte automático do espaço em branco à direita.touch $'hello \'$(rm -rf ~)\' world'
; as aspas literais dentro do nome do arquivo contrariam as aspas literais adicionadassed
, resultando na execução do código no nome do arquivo.| sh
formulário, que é o que possui a vulnerabilidade de injeção do shell).Com zsh
Supondo que você não se preocupe com os diretórios atuais e não terá mais de 999 arquivos (escolha um número maior, se desejar, ou crie um loop while).
Em
*(.om[6,999])
, os.
arquivos de meios, oso
meios ordem de classificação se, osm
meios por data de modificação (colocara
em tempo de acesso ouc
para a mudança inode), o[6,999]
escolhe uma gama de arquivo, assim não rm a 5 pela primeira vez.fonte
om
) funcionar (qualquer classificação que eu tentei não teve efeito - nem no OSX 10.11.2 (tente com o zsh 5.0.8 e 5.1.1) , nem no Ubuntu 14.04 (zsh 5.0.2)) - o que estou perdendo ?. Quanto ao endpoint intervalo: não há necessidade de codificar-lo, basta usar-1
para se referir à última entrada e, portanto, incluir todos os arquivos restantes:[6,-1]
.Sei que esse é um tópico antigo, mas talvez alguém se beneficie disso. Este comando encontrará arquivos no diretório atual:
Isso é um pouco mais robusto do que algumas das respostas anteriores, pois permite limitar seu domínio de pesquisa a arquivos correspondentes a expressões. Primeiro, encontre os arquivos que correspondem às condições desejadas. Imprima esses arquivos com os carimbos de data e hora próximos a eles.
Em seguida, classifique-os pelos registros de data e hora:
Em seguida, retire os 4 arquivos mais recentes da lista:
Pegue a segunda coluna (o nome do arquivo, não o carimbo de data e hora):
E então embrulhe tudo isso em uma declaração for:
Esse pode ser um comando mais detalhado, mas tive uma sorte muito maior de poder direcionar arquivos condicionais e executar comandos mais complexos contra eles.
fonte
achei cmd interessante em Sed-Onliners - Exclua as últimas 3 linhas - encontre perfeito para outra maneira de esfolar o gato (tudo bem, não), mas a ideia:
fonte
Remove todos, exceto os 10 arquivos mais recentes (mais recentes)
Se menos de 10 arquivos, nenhum arquivo for removido e você terá: erro head: contagem ilegal de linhas - 0
Para contar arquivos com bash
fonte
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:
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".
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 primeiras 7 linhasprint "rm \"" $0 "\""
constrói uma linha: rm "nome do arquivo"eval
executaComo estamos usando
rm
, eu não usaria o comando acima em um script! O uso mais sábio é:No caso de usar o
ls -t
comando, não haverá nenhum dano em exemplos tolos como:touch 'foo " bar'
etouch '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 (forma simples, sem espaços tolerados):
para definir a variável
VarName
para o valor de$1
. Várias variáveis podem ser criadas de uma só vez. IssoVarName
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:fonte
fonte
xargs
sem-0
ou no mínimo-d $'\n'
não é confiável; observe como isso se comporta com um arquivo com espaços ou caracteres de aspas em seu nome.Eu fiz isso em um script de shell bash. Uso:
keep NUM DIR
onde NUM é o número de arquivos a serem mantidos e DIR é o diretório a ser limpo.fonte