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.


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)'

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.


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
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.