
Eu tenho uma pasta com muitas subpastas. Quero remover todos os arquivos menores de cada subpasta, deixando apenas o arquivo maior.
Por exemplo:
Subfolder1
---------- File 1 ---- 300k
---------- File 2 ---- 299k
---------- File 3 ---- 800k
Só file 3
deverá permanecer o com 800k. Se a pasta tiver apenas um arquivo, ele permanecerá.
Este código funciona, mas não consigo colocá-lo em um loop for (para diretório recursivo):
find . -type f -maxdepth 1 | sort -n -r | tail -n +2 | xargs -I{} rm -v {}
Como posso fazer isso?
Responder1
~$ tree -fQFi --sort=size pluto
"pluto"
"pluto/pluto1"/
"pluto/pluto1/pluto3"/
"pluto/pluto1/pluto3/nozero.txt"
"pluto/pluto1/pluto3/zero ed.txt"
"pluto/pluto1/nozero.txt"
"pluto/pluto2"/
"pluto/pluto2/nozero.txt"
"pluto/pluto2/nozer.txt"
"pluto/pluto2/zero.txt"
"pluto/pluto4"/
"pluto/pluto4/zeroed.txt"
"pluto/zeroed.txt"
4 directories, 8 files
~$ tree -fQFic --noreport --sort=size pluto | \
> awk -F"/" 'NR==1||/\/$/{next}; \
> {path=""; for(i=1;i<NF;i++) path=path$i; if(a[path]++) print}'
"pluto/pluto1/pluto3/zero ed.txt"
"pluto/pluto2/nozer.txt"
"pluto/pluto2/zero.txt"
~$ tree -fQFic --noreport --sort=size pluto | \
> awk -F"/" 'NR==1||/\/$/{next}; \
> {path=""; for(i=1;i<NF;i++) path=path$i; if(a[path]++) print}' | \
> xargs rm -v
'pluto/pluto1/pluto3/zero ed.txt' rimosso
'pluto/pluto2/nozer.txt' rimosso
'pluto/pluto2/zero.txt' rimosso
~$ tree -fQFi --sort=size pluto
"pluto"
"pluto/pluto1"/
"pluto/pluto1/pluto3"/
"pluto/pluto1/pluto3/nozero.txt"
"pluto/pluto1/nozero.txt"
"pluto/pluto2"/
"pluto/pluto2/nozero.txt"
"pluto/pluto4"/
"pluto/pluto4/zeroed.txt"
"pluto/zeroed.txt"
4 directories, 5 files
tree
listas por diretório e depois por tamanho decrescente.
awk
A primeira linha de código de pulatree
a primeira linha de saída deoulinhas com barras finais (ou seja, diretórios)awk
A segunda linha de código de constrói um dirname a partir do caminho completo (for
loop) e, em seguida, imprime nomes de caminhos completos se dirname foi encontrado uma vez nas linhas anteriores (ou seja, imprime, para cada diretório, a partir do segundo arquivo listado)
Responder2
Justificação
Esta é minha tentativa de construir um comando que funcione comqualquerdiretório e nome(s) de arquivo(s). Em geral, os caminhos no Linux (e os nomes nos sistemas de arquivos) podem conter qualquer caractere, exceto null ( 0x00
) e /
. Caracteres problemáticos podem ser " " (espaço), qualquer outro caractere branco,
'
, "
, nova linha, outros caracteres não imprimíveis. Portanto é importante:
- abandonar ferramentas que substituem alguns caracteres por outros (por exemplo, muitas implementações de
ls
will print?
para não-imprimíveis); - passe todos os nomes como strings terminadas em nulo (escolha ferramentas que possam analisá-los);
- citar corretamente.
Fui inspirado pela discussão emesta outra resposta.
Comandos reais
Versão de teste, serão apenas ls
os arquivos que seriam removidos:
find -type d -exec sh -c 'find "$0" -maxdepth 1 -mindepth 1 -type f -exec stat --printf "%s %n\0" \{\} + | sort -znr | tail -zn +2' {} \; | cut -zf 2- -d " " | xargs -0r ls -l
Sim, estou usando ls
aqui apesar do que acabei de dizer. Isso ocorre porque ls
a saída não está sendo analisada posteriormente. Estou usando apenas para exibir o resultado. Se acontecer de você ter diretórios ou arquivos com caracteres problemáticos em seus nomes, você observará o comportamento dos ls
quais deverá convencê-lo anunca analisels
(a menos que você saiba que está absolutamente seguro com isso). Mesmo assim, os nomes problemáticos passarão por todo o caminho ls
e esse é o ponto.
Entenda a versão de teste(veja abaixo alguma explicação)e experimente antes de deixar a versão funcional(logo abaixo)remova seus arquivos.Lembre-se de que sou apenas um cara aleatório na Internet.
Versão funcional, removerá seus arquivos:
find -type d -exec sh -c 'find "$0" -maxdepth 1 -mindepth 1 -type f -exec stat --printf "%s %n\0" \{\} + | sort -znr | tail -zn +2' {} \; | cut -zf 2- -d " " | xargs -0r rm
Explicação
Aqui está a versão de teste dividida em várias linhas (embora ainda seja uma linha bash
; observe que eu usoesse truquepara comentários embutidos):
find -type d -exec `# Find all directories under (and including) the current one.` \
sh -c ' `# In every directory separately...` \
find "$0" -maxdepth 1 -mindepth 1 -type f -exec `# ...find all files,...` \
stat --printf "%s %n\0" \{\} + | # ...get their sizes and names,...
sort -znr | # ...sort by size...
tail -zn +2' `# ...and discard the "biggest" entry.` \
{} \
\; | # (All the directories have been processed).
cut -zf 2- -d " " | # Then extract filenames...
xargs -0r ls -l # ...and ls them (rm in the working version).
Técnicas utilizadas, obstáculos superados:
- As ferramentas que analisam strings são instruídas a trabalhar com strings terminadas em nulo:
stat --printf "…\0"
;sort -z
,tail -z
,cut -z
;xargs -0 …
;find -print0
(não é necessário neste exemplo, mas é muito comum em geral, por isso menciono isso de qualquer maneira).
sh -c '…'
é a maneira de usar tubos dentrofind -exec
.find -type d -exec sh -c 'find "{}" …
irá quebrar para o nome do diretório contendo"
;find -type d -exec sh -c 'find "$0" … ' {} \;
funciona bem.{}
na instrução internafind
são escapados (\{\}
) para evitar que o externofind
os substitua.cut
poderia seguir imediatamentetail
, ele executaria umcut
por diretório. Colocá-lo fora da parte externafind
faz com quecut
todo o corte seja feito de uma só vez.- A
-r
opção dexargs
impedirls
(rm
na versão funcional) de ser executada quando não há entrada paraxargs
.