Y a-t-il des inconvénients à utiliser rm $ (ls) pour supprimer des fichiers?


13

Je me demandais si l'utilisation rm $(ls)pour supprimer des fichiers (ou rm -r $(ls)pour supprimer des répertoires également) était sûre? Parce que dans tous les sites Web, les gens donnent d'autres moyens de le faire, même si cette commande semble beaucoup plus facile que d'autres commandes.


3
Réponse de base, non. ls ne peut pas gérer les caractères spéciaux. Je peux écrire une réponse un peu pour expliquer cela plus en détail
Sergiy Kolodyazhnyy

5
touch 'foo -r .. bar'; rm $(ls); où est passé mon répertoire parent? De plus, quel genre d'alternatives voyez-vous qui sont encore plus compliquées que cela? rm *est beaucoup plus facile à taper et à penser, et plus sûr (mais pas parfaitement sûr; voir la réponse de Dennis).
Peter Cordes

1
En plus des excellentes réponses, sachez que cela lspeut varier d'une implémentation à l'autre et n'est donc pas standard. En fonction de vos besoins, envisagez des alternatives telles que findet stat. Vous ne devez l'utiliser lsque pour la consommation humaine, jamais pour une utilisation par d'autres commandes ou dans des scripts.
Paddy Landau

Réponses:


7

Qu'est-ce que cela est destiné à faire?

  • ls liste les fichiers dans le répertoire courant
  • $(ls)substitue la sortie des lslieux comme argument pourrm
  • rm $(ls)Est essentiellement destiné à supprimer tous les fichiers du répertoire actuel

Qu'est-ce qui ne va pas avec cette image ?

lsne peut pas gérer correctement les caractères spéciaux dans le nom de fichier. Les utilisateurs d'Unix conseillent généralement d'utiliser différentes approches . J'ai également montré cela dans une question connexe sur le comptage des noms de fichiers . Par exemple:

$ 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
$ 

En outre, comme il est correctement mentionné dans la réponse de Denis, un nom de fichier avec des tirets de tête peut être interprété comme un argument rmaprès substitution, ce qui va à l'encontre de l'objectif de suppression du nom de fichier.

Ce qui fonctionne

Vous souhaitez supprimer des fichiers dans le répertoire actuel. Utilisez donc 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
$ 

Vous pouvez utiliser la findcommande. Cet outil est fréquemment recommandé pour plus que le répertoire actuel - il peut parcourir récursivement toute l'arborescence de répertoires et fonctionner sur des fichiers via-exec . . .{} \;

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

Python n'a pas de problème avec les caractères spéciaux dans les noms de fichiers, nous pourrions donc également l'utiliser (notez que celui-ci est uniquement pour les fichiers, vous devrez l'utiliser os.rmdir()et os.path.isdir()si vous souhaitez opérer sur des répertoires):

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

En fait, la commande ci-dessus pourrait être transformée en fonction ou en alias ~/.bashrcpar souci de concision. Par exemple,

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

}

La version Perl de ce serait

perl -e 'use Cwd;my $d=cwd();opendir(DIR,$d); while ( my $f = readdir(DIR)){ unlink $f;}; closedir(DIR)'

1
Votre "$(ls)"exemple ne fonctionne que s'il n'y a qu'un seul fichier dans le répertoire. Vous pourriez tout aussi bien utiliser la complétion de tabulation pour développer le nom de fichier car c'est la seule complétion.
Peter Cordes du

@PeterCordes en effet, pour une raison étrange, il ne fonctionne qu'avec un seul fichier. C'est un argument de plus contre l'utilisation lsalors :) Je vais le modifier
Sergiy Kolodyazhnyy

Je n'appellerais pas cela une raison étrange: soit vous citez $(ls)pour désactiver le fractionnement de mots, soit vous laissez le fractionnement de mots se produire (avec des résultats désastreux). Le seul moyen propre de contourner une liste de plusieurs chaînes sans traiter les données comme du code est avec des variables de tableau ou avec \0un séparateur, mais le shell lui-même ne peut pas le faire. C'est toujours IFS=$'\n'le moins dangereux, mais ne peut pas correspondre find -print0 | xargs -0. Ou grep -l --null. Ou vous évitez tout le problème avec des choses comme find -exec rm {} +. (Notez le +pour passer plusieurs arguments à chaque invocation de rm; WAY more efficient).
Peter Cordes du

@PeterCordes Yup, totalement d'accord là-bas. Mais IFS=$'\n'échouera également dans ce cas, car j'ai un retour à la ligne dans le nom de fichier, donc le découpage de mots le traitera comme deux noms de fichiers au lieu d'un. La raison étrange, cependant, est le fait qu'avec la valeur par défaut IFSqui est l'espace, la tabulation, la nouvelle ligne, l'original rm "$(ls)"devrait également échouer, il devrait traiter comme je l'ai dit le nom de fichier comme deux noms distincts, mais ce n'est pas le cas. En règle générale, j'utilise findavec -exec, ou une find . . .-print0 | while IFS= read -d'' FILENAME ; do . . . donestructure pour gérer les noms de fichiers. Ou on pourrait utiliser python, j'ai ajouté un exemple de cela.
Sergiy Kolodyazhnyy

"$(ls)"s'étend toujours à un argument, car les guillemets protègent l'expansion de la $(ls)séparation des mots. Tout comme ils protègent "$foo".
Peter Cordes

26

Non, ce n'est pas sûr, et l'alternative couramment utilisée rm *n'est pas beaucoup plus sûre.

Il y a beaucoup de problèmes avec rm $(ls). Comme d'autres l'ont déjà couvert dans leurs réponses, la sortie de lssera divisée en caractères présents dans le séparateur de champ interne .

Dans le meilleur des cas, cela ne fonctionne tout simplement pas. Dans le pire des cas, vous vouliez supprimer uniquement les fichiers (mais pas les répertoires) - ou supprimer sélectivement certains fichiers avec -i- mais il y a un fichier avec le nom c -rfdans le répertoire actuel. Voyons ce qui se passe.

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

La commande rm -i $(ls)était censée supprimer uniquement les fichiers et demander avant de supprimer chacun d'eux, mais la commande qui a finalement été exécutée était en lecture

rm -i a b c -rf

il a donc fait autre chose entièrement.

Notez que ce rm *n'est que légèrement mieux. Avec la structure du répertoire comme précédemment, il se comportera comme prévu ici, mais si vous avez un fichier appelé -rf, vous n'avez toujours pas de chance.

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

Il existe plusieurs meilleures alternatives. Les plus simples n'impliquent que rm et globbing.

  • La commande

    rm -- *
    

    fonctionnera exactement comme prévu, où --signale que tout ce qui suit ne doit pas être interprété comme une option.

    Cela fait partie des directives de syntaxe de l'utilitaire POSIX depuis plus de deux décennies maintenant. Il est répandu, mais il ne faut pas s'attendre à ce qu'il soit présent partout.

  • La commande

    rm ./*
    

    rend le glob se développer différemment et ne nécessite donc aucun support de l'utilitaire appelé.

    Pour mon exemple ci-dessus, vous pouvez voir la commande qui sera finalement exécutée en ajoutant l' écho .

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

    Le caractère principal ./empêche rm de traiter accidentellement les noms de fichiers comme des options.


1
Très bon point, les noms de fichiers avec - après expansion deviennent des drapeaux rm. +1
Sergiy Kolodyazhnyy

1
Encore plus méchant: touch 'foo -rf .. bar. Je ne pense pas qu'un attaquant puisse obtenir plus haut que le répertoire parent, à moins que nous ne puissions produire un séparateur de chemin dans lsla sortie de.
Peter Cordes

L'attaquant @Peter devrait avoir des autorisations d'écriture pour supprimer le répertoire des brevets en premier lieu, non?
Sergiy Kolodyazhnyy

1
@PeterCordes Je ne sais pas si toutes les versions de rm ont cette sécurité intégrée, mais sur Ubuntu, openSUSE et Fedora, il est dit rm: refusing to remove '.' or '..' directory: skipping '..'ou quelque chose de similaire lorsque vous essayez de supprimer le répertoire parent.
Dennis

@Serg: l'attaquant vous envoie simplement un .zip avec ce nom de fichier et vous permet de vous tirer une balle dans le pied en l'extrayant puis en essayant de supprimer le contenu. Ou en créant ce nom de fichier dans / var / tmp ou quelque chose.
Peter Cordes
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.