Remova todos os arquivos, exceto o maior

Remova todos os arquivos, exceto o maior

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

file 3deverá 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

treelistas por diretório e depois por tamanho decrescente.

  • awkA primeira linha de código de pula treea primeira linha de saída deoulinhas com barras finais (ou seja, diretórios)
  • awkA segunda linha de código de constrói um dirname a partir do caminho completo ( forloop) 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 lswill 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 lsos 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 lsaqui apesar do que acabei de dizer. Isso ocorre porque lsa 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 lsquais 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 lse 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 dentro find -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 interna findsão escapados ( \{\}) para evitar que o externo findos substitua.
  • cutpoderia seguir imediatamente tail, ele executaria um cutpor diretório. Colocá-lo fora da parte externa findfaz com que cuttodo o corte seja feito de uma só vez.
  • A -ropção de xargsimpedir ls( rmna versão funcional) de ser executada quando não há entrada para xargs.

informação relacionada