Existem desvantagens em usar rm $ (ls) para excluir arquivos?

13

Eu queria saber se o uso rm $(ls)para excluir arquivos (ou rm -r $(ls)excluir diretórios também) era seguro? Porque em todos os sites, as pessoas dão outras maneiras de fazer isso, mesmo que esse comando pareça muito mais fácil do que outros comandos.

posixKing
fonte
3
Resposta básica, não. Não é possível lidar com caracteres especiais. Eu posso escrever uma resposta em um pouco para explicar isso com mais detalhes
Sergiy Kolodyazhnyy
5
touch 'foo -r .. bar'; rm $(ls); onde foi meu diretório pai? Além disso, que tipo de alternativas você vê que são ainda mais complicadas do que isso? rm *é muito mais fácil digitar e pensar, e mais seguro (mas não perfeitamente seguro; veja a resposta de Dennis).
Peter Cordes
1
Além das excelentes respostas, saiba que lspode variar entre implementações e, portanto, não é padrão. Dependendo do que você precisa, considere alternativas como finde stat. Você deve usar lsapenas para consumo humano, nunca para uso por outros comandos ou scripts.
Paddy Landau

Respostas:

7

O que isso pretende fazer?

  • ls lista arquivos no diretório atual
  • $(ls)substitui a saída de lslugares que, como argumento pararm
  • Essencialmente, o rm $(ls)objetivo é excluir todos os arquivos no diretório atual

O que está errado com esta imagem ?

lsnão pode lidar adequadamente com caracteres especiais no nome do arquivo. Usuários do Unix geralmente recomendam usar abordagens diferentes . Também mostrei isso em uma pergunta relacionada sobre a contagem de nomes de arquivos . Por exemplo:

$ touch file$'\n'name                                                                                                    
$ ls                                                                                                                     
file?name
$ rm $(ls)
rm: cannot remove 'file': No such file or directory
rm: cannot remove 'name': No such file or directory
$ 

Além disso, como corretamente mencionado na resposta de Denis, um nome de arquivo com os principais traços, poderia ser interpretado como argumento para rmapós a substituição, o que frustra a finalidade de remover nome de arquivo.

O que funciona

Você deseja excluir arquivos no diretório atual. Então use glob rm *:

$ ls                                                                                                                     
file?name
$ rm $(ls)
rm: cannot remove 'file': No such file or directory
rm: cannot remove 'name': No such file or directory
$ rm *
$ ls
$ 

Você pode usar o findcomando Essa ferramenta é frequentemente recomendada para mais do que apenas o diretório atual - ela pode percorrer recursivamente toda a árvore de diretórios e operar em arquivos via-exec . . .{} \;

$ touch "file name"                                
$ find . -maxdepth 1 -mindepth 1                                                                                         
./file name
$ find . -maxdepth 1 -mindepth 1 -exec rm {} \;                                                                          
$ ls
$ 

O Python não possui problemas com caracteres especiais nos nomes de arquivos, portanto, podemos empregá-lo também (observe que este é apenas para arquivos, você precisará usar os.rmdir()e os.path.isdir()se desejar operar em diretórios):

python -c 'import os; [ os.remove(i) for i in os.listdir(".") if os.path.isfile(i) ]'

De fato, o comando acima pode ser transformado em função ou alias ~/.bashrcpor uma questão de brevidade. Por exemplo,

rm_stuff()
{
    # Clears all files in the current working directory
    python -c 'import os; [ os.remove(i) for i in os.listdir(".") if os.path.isfile(i) ]'

}

Versão Perl disso seria

perl -e 'use Cwd;my $d=cwd();opendir(DIR,$d); while ( my $f = readdir(DIR)){ unlink $f;}; closedir(DIR)'
Sergiy Kolodyazhnyy
fonte
1
Seu "$(ls)"exemplo funciona apenas se houver apenas um arquivo no diretório Você também pode usar a conclusão de tabulação para expandir o nome do arquivo, pois é a única conclusão.
Peter Cordes
@ PeterCordes, de fato, por uma estranha razão, funciona apenas com um arquivo. Isso é mais um argumento contra o uso de lsentão :) Vou editar it out
Sergiy Kolodyazhnyy
Eu não chamaria isso de um motivo estranho: você cita $(ls)para desativar a divisão de palavras ou permite que a divisão de palavras aconteça (com resultados desastrosos). A única maneira limpa de passar uma lista de várias seqüências de caracteres sem tratar os dados como código é com variáveis ​​de matriz ou \0como separador, mas o próprio shell não pode fazer isso. Ainda IFS=$'\n'é o menos perigoso, mas não pode igualar find -print0 | xargs -0. Or grep -l --null. Ou você evita todo o problema com coisas do tipo find -exec rm {} +. (Observe +para passar vários argumentos a cada chamada de rm; MUITO MAIS eficiente).
6266 Peter Cordes
@ PeterCordes Sim, concordo totalmente lá. Mas IFS=$'\n'também falhará neste caso, já que eu tenho uma nova linha no nome do arquivo, portanto, a divisão de palavras o tratará como dois nomes de arquivos em vez de um. A estranha razão, no entanto, é o fato de que, por padrão, IFSque é espaço, guia, nova linha, original rm "$(ls)", também deve falhar, deve tratar como eu disse o nome do arquivo como dois separados, mas não o fez. Normalmente eu uso findcom -exec, ou find . . .-print0 | while IFS= read -d'' FILENAME ; do . . . doneestrutura, para lidar com nomes de arquivos. Ou alguém poderia usar python, eu adicionei exemplo disso.
Sergiy Kolodyazhnyy 3/09/16
"$(ls)"sempre se expande para um argumento, porque as aspas protegem a expansão da $(ls)divisão de palavras. Assim como eles protegem "$foo".
Peter Cordes
26

Não, não é seguro, e a alternativa comumente usada rm *não é muito mais segura.

Existem muitos problemas com rm $(ls). Como outros já abordaram em suas respostas, a saída de lsserá dividida nos caracteres presentes no separador de campo interno .

Na melhor das hipóteses, simplesmente não funciona. No pior cenário, você pretendia remover apenas arquivos (mas não diretórios) - ou remover seletivamente alguns arquivos -i- mas há um arquivo com o nome c -rfno diretório atual. Vamos ver o que acontece.

$ mkdir a
$ touch b
$ touch 'c -rf'
$ rm -i $(ls)
$ ls
c -rf

O comando rm -i $(ls)deveria remover apenas arquivos e perguntar antes de remover cada um, mas o comando que acabou sendo executado leu

rm -i a b c -rf

então fez algo completamente diferente.

Observe que rm *é apenas um pouco melhor. Com a estrutura de diretórios como antes, ela se comportará como pretendido aqui, mas se você tiver um arquivo chamado -rf, ainda estará sem sorte.

$ mkdir a
$ touch b
$ touch ./-rf
$ rm -i *
$ ls
-rf

Existem várias alternativas melhores. Os mais fáceis envolvem apenas rm e globbing.

  • O comando

    rm -- *
    

    funcionará exatamente como pretendido, onde --sinaliza que tudo depois não deve ser interpretado como uma opção.

    Isso faz parte das diretrizes de sintaxe do utilitário POSIX há mais de duas décadas. É generalizada, mas você não deve esperar que ela esteja presente em todos os lugares.

  • O comando

    rm ./*
    

    faz com que a glob se expanda de maneira diferente e, portanto, não requer suporte do utilitário chamado.

    Para o meu exemplo acima, você pode ver o comando que será executado em última análise, com o eco precedente .

    $ echo rm ./*
    rm ./a ./b ./-rf
    

    O líder ./impede que a rm trate acidentalmente qualquer um dos nomes de arquivo como opções.

Dennis
fonte
1
Muito bom ponto, nomes de arquivos com - após expansão se tornam sinalizadores para rm. +1
Sergiy Kolodyazhnyy
1
Ainda pior: touch 'foo -rf .. bar. Eu não acho que um invasor possa ficar mais alto que o diretório pai, a menos que possamos produzir um separador de caminho na lssaída.
Peter Cordes
O atacante @ Peter teria que ter permissões de gravação para remover o diretório de patentes em primeiro lugar, não?
Sergiy Kolodyazhnyy 3/09/16
1
@PeterCordes Não tenho certeza se todas as versões do rm têm essa proteção contra falhas, mas no Ubuntu, openSUSE e Fedora, ele diz rm: refusing to remove '.' or '..' directory: skipping '..'ou algo semelhante ao tentar remover o diretório pai.
Dennis
@Serg: o atacante apenas envia um .zip com esse nome de arquivo e permite que você atire no próprio pé, extraindo-o e tentando excluir o conteúdo. Ou criando esse nome de arquivo em / var / tmp ou algo assim.
Peter Cordes