Removendo arquivos com uma certa extensão, exceto um arquivo do terminal

36

Eu preciso remover todos os arquivos com extensão .gif, exceto um arquivo com o nome "filename.gif". Qual é a melhor maneira de fazer isso no terminal?

O comando rm *.gifremove todos os arquivos gif, incluindo o arquivo filename.gif.

Seth
fonte

Respostas:

66

Aqui está a solução simples que você provavelmente deve usar:

mv filename.gif filename.gif.keep
rm *.gif
mv filename.gif.keep filename.gif

Não há nada de especial na .keepextensão, isso apenas faz com que o nome do arquivo temporariamente não termine .gif.

Se você não deve renomear o arquivo (e há situações de script em que isso é importante):

for X in *.gif; do
    if [ "$X" != "filename.gif" ]; then
        rm "$X"
    fi
done

Ou você pode escrever mais curto assim:

for X in *.gif; do [ "$X" != "filename.gif" ] && rm "$X"; done

Você pode preferir usar find; é muito poderoso, você pode considerá-lo mais legível e lida melhor com nomes de arquivos estranhos com personagens como *eles .

find . -maxdepth 1 -not -name 'filename.gif' -name '*.gif' -delete

Usei o -notoperador para facilitar a leitura, mas se a conformidade com POSIX for importante - se você não estiver usando o GNU find, ou se for para um script que pretende redistribuir para outras pessoas ou executar em uma variedade de sistemas - você deve use o !operador:

find . -maxdepth 1 ! -name 'filename.gif' -name '*.gif' -delete

Uma coisa útil findé que você pode modificar facilmente o comando para diferenciar maiúsculas de minúsculas, para que ele encontre e exclua arquivos com extensões como .GIF:

find . -maxdepth 1 -not -name 'filename.gif' -iname '*.gif' -delete

Observe que eu usei -inameno lugar do -namepadrão de pesquisa, *.gifmas não o usei filename.gif. Presumivelmente, você sabe exatamente como o seu arquivo é chamado e -inamecorresponderá a maiúsculas e minúsculas alternativas não apenas na extensão , mas em qualquer lugar do nome do arquivo.

Todas essas soluções excluem apenas os arquivos que residem imediatamente no diretório atual . Eles não excluem arquivos não contidos no diretório atual e não excluem arquivos que residem em subdiretórios do diretório atual.

Se você deseja excluir os arquivos em todos os lugares contidos no diretório atual (ou seja, incluindo subdiretórios e subdiretórios desses subdiretórios, e assim por diante - arquivos contidos no diretório atual ou em qualquer um de seus descendentes), use find sem maxdepth -1 :

find . -not -name 'filename.gif' -name '*.gif' -delete

Tenha cuidado com isso!

Você também pode definir outros valores com -maxdepth. Por exemplo, para excluir arquivos no diretório atual e seus filhos e netos, mas não mais:

find . -maxdepth 3 -not -name 'filename.gif' -name '*.gif' -delete

Apenas certifique-se de nunca colocar -deleteprimeiro, antes das outras expressões! Você verá que eu sempre coloco -deleteno final. Se você colocá-lo no início, ele será avaliado primeiro e todos os arquivos em .(incluindo arquivos que não terminam em .gife arquivos em sub-sub-sub-sub ... subdiretórios profundos ... .) serão excluídos!

Para mais informações , consulte as páginas do manual para bashe she para os comandos utilizados nestes exemplos: mv, rm, [, e (especialmente) find.

Eliah Kagan
fonte
11
Obrigado Eliah. Fiz isso de maneira semelhante ao primeiro método que você sugeriu, ou seja, converter o arquivo em um formato diferente e convertê-lo novamente. Mas isso parecia um pouco subótimo e impuro. Gosto da segunda abordagem que você e o @efthialex sugeriram e estou usando agora. Obrigado!
11
Marvis @ Estou feliz que isso atendeu às suas necessidades. A propósito, eu expandi essa resposta com informações sobre outro método (ou classe de métodos) usando find, o que é altamente versátil.
Eliah Kagan
3
Obrigado pela resposta detalhada findé realmente um bom achado.
11
+1 para a resposta do senso comum (mover-excluir-mover).
tu restabelece Monica-dor duh
Nit-pick: como findlidar com nomes de arquivos estranhos com *eles melhor do que o loop for? O loop for lida com arquivos com *precisão, porque você usou aspas duplas.
Flimm
28

Se você estiver usando bash(o shell padrão), a extglobopção shell permitirá que você use uma sintaxe de correspondência de padrão estendida. Para habilitá-lo, use o shoptcomando embutido:

shopt -s extglob

(Incluo essa linha no meu .bashrcarquivo.)

Entre outras coisas, concede acesso ao !()operador, que corresponde a qualquer padrão que não esteja dentro do parens. Para seu propósito:

rm !(filename).gif

Mais informações estão disponíveis em man bash"Correspondência de padrões".

Eswald
fonte
11
Excelente resposta! É específico do bash, portanto, pode não ser adequado para scripts portáteis, mas tarefas interativas e até alguns scripts, esse é o melhor caminho a percorrer. Ele carrega a complexidade adicional que extglobdeve ser ativada, mas nada de ruim acontece se estiver desativado (nesse caso, isso simplesmente não funciona). E parece estar ativado por padrão, mesmo que eu não possua shopt -s extglobnenhum dos meus arquivos de configuração (ou os globais). Se houver uma explicação simples sobre por que ela funciona para mim imediatamente, você pode adicioná-la a esta resposta; ou posso postar uma nova pergunta.
Eliah Kagan
13

Abra um terminal com Ctrl+ Alt+ Te digite:

find . -type f -name "*.gif" -and -not -name "filename.gif" -exec rm -vf {} \;

A diferença entre -deletee -exec rm -vf {} \;é que, com a segunda opção, você poderá ver quais arquivos foram removidos devido ao -vsinalizador. Isso é algo que não pode ser feito com a -deleteopção. ( -name -deleteImprimirá os nomes de arquivos, mas rmcom -vrevela se cada arquivo foi com êxito excluído.)

efthialex
fonte
1

Existe a GLOBIGNOREvariável. De man bash:

GLOBIGNORE
      A colon-separated list of patterns defining the set of filenames
      to be ignored by pathname expansion.  If a filename matched by a
      pathname expansion pattern also matches one of the  patterns  in
      GLOBIGNORE, it is removed from the list of matches.

Portanto, uma maneira simples é simplesmente definir GLOBGINOREo nome do arquivo em questão:

$ touch {a,b,c,d}.png
$ echo *.png
a.png b.png c.png d.png
$ GLOBIGNORE=c.png
$ echo *.png
a.png b.png d.png

Então, no seu caso:

GLOBIGNORE=filename.gif; rm *.gif

Obviamente, como GLOBIGNOREcontém padrões, você pode usá-lo sempre que quiser excluir um padrão:

$ GLOBIGNORE='[a-c].png'; echo *.png
d.png
$ touch bd.png; GLOBIGNORE='?.png'; echo *.png
bd.png

Uma vantagem (?!) Disso GLOBIGNOREé que a configuração exclui automaticamente .e.. não corresponde, e permite dotglob:

The  GLOBIGNORE shell variable may be used to restrict the set of file‐
names matching a pattern.  If GLOBIGNORE is set, each matching filename
that also matches one of the patterns in GLOBIGNORE is removed from the
list of matches.  The filenames ``.''  and ``..''  are  always  ignored
when  GLOBIGNORE is set and not null.  However, setting GLOBIGNORE to a
non-null value has the effect of enabling the dotglob shell option,  so
all other filenames beginning with a ``.''  will match.  To get the old
behavior of ignoring filenames beginning with a ``.'', make ``.*''  one
of  the  patterns  in  GLOBIGNORE.  The dotglob option is disabled when
GLOBIGNORE is unset.

Portanto, uma maneira simples de excluir todos os arquivos e pastas em um diretório:

GLOBIGNORE=.; rm -r *

Os *nomes de arquivos correspondentes serão iniciados com ., desde GLOBIGNOREativado dotglob, mas não serão correspondentes .ou .., portanto, você não afetará o diretório pai dessa maneira.

muru
fonte