web-dev-qa-db-fra.com

Est-ce que ls listera toujours les fichiers que rm va supprimer?

Quelque chose que je pense devoir absolument savoir: si je ls <something>, rm <something> supprimera-t-il exactement les mêmes fichiers que lsname__? Existe-t-il des circonstances dans lesquelles rmpourrait supprimer des fichiers que lsne montrait pas? (Ceci est dans le 18.04 bash)

Edit: merci à tous ceux qui ont répondu. Je pense que la réponse complète est une combinaison de toutes les réponses, aussi j’ai accepté la réponse la plus votée comme "la réponse".

Des choses inattendues que j'ai apprises en cours de route:

  • lsn'est pas aussi simple qu'on pourrait le penser dans le traitement de ses arguments
  • Dans une installation simple et sans faille d'Ubuntu, alias .bashrc lsname__
  • Ne nommez pas vos fichiers en commençant par un tiret, car ils peuvent ressembler à des arguments de commande. Nommer one -r le demande!
33
B.Tanner

Eh bien, ls et rm agissent sur les arguments qui leur sont transmis.

Ces arguments peuvent être un simple fichier, donc ls file.ext et rm file.ext agissent sur le même fichier et le résultat est clair (liste le fichier/supprime le fichier).

Si l'argument est un répertoire, ls directory répertorie le contenu du répertoire, alors que rm directory ne fonctionnera pas tel quel (c'est-à-dire que rm sans drapeaux ne peut pas supprimer les répertoires, tandis que Si vous faites rm -r directory, il supprime récursivement tous les fichiers sous directoryet le répertoire lui-même).

Mais gardez à l'esprit que les arguments en ligne de commande peuvent être soumis à l'expansion du shell , de sorte que ce n'est pas toujours garanti que les mêmes arguments sont transmis aux deux commandes si elles contiennent des caractères génériques, des variables, la sortie d'autres commandes, etc.

Comme exemple extrême, pensez à ls $(Rand).txt et à rm $(Rand).txt, les arguments sont "les mêmes" mais les résultats sont assez différents!

39
Mr Shunz

Si vous pensez à quelque chose comme ls foo*.txt vs. rm foo*.txt, alors oui, ils afficheront et supprimeront les mêmes fichiers. Le shell développe le glob et le passe à la commande en question. Les commandes fonctionnent sur les fichiers répertoriés. On les énumère, on les enlève.

La différence évidente est que si l'un de ces fichiers se trouvait être un répertoire, ls en listerait le contenu, mais rm ne réussirait pas à le supprimer. Ce n'est généralement pas un problème, puisque rm enlèverait moins que ce qui était montré par ls.

Le gros problème provient de exécuter ls * ou rm * dans un répertoire contenant noms de fichiers commençant par un tiret. Ils développeraient les lignes de commande des deux programmes comme si vous les écriviez vous-même, et ls prendrait -r comme signifiant "ordre de tri inversé", tandis que rm prendrait -r comme signifiant une suppression récursive. La différence est importante si vous avez des sous-répertoires d’au moins deux niveaux. (ls * affichera le contenu des répertoires de premier niveau, mais rm -r * dépassera également le premier sous-niveau.)

Pour éviter cela, écrivez des globs permissifs avec un ./ en tête pour indiquer le répertoire en cours, et/ou mettez un -- pour signaler la fin du traitement des options avant le glob (c'est-à-dire rm ./* ou rm -- *).

Avec un glob comme *.txt, ce n'est pas un problème car le point est un caractère d'option invalide et causera une erreur (jusqu'à ce que quelqu'un développe les utilitaires pour en inventer un sens), mais il est toujours plus sûr d'y placer le ./.


Bien sûr, vous pouvez également obtenir des résultats différents pour les deux commandes si vous avez modifié les options de globing du shell ou créé/déplacé/supprimé des fichiers entre les commandes, mais je doute que vous ayez pensé à l'un de ces cas. (Traiter avec des fichiers nouveaux/déplacés serait extrêmement compliqué à faire en toute sécurité.)

20
ilkkachu

Laissant de côté le comportement de Shell, concentrons-nous uniquement sur ce que rmet lspeuvent traiter eux-mêmes. Au moins un cas où lsindiquera ce que rmne peut pas supprimer implique des autorisations de répertoire, et l'autre - répertoires spéciaux . et ...

Autorisations de dossier

rmest une opération sur un répertoire, car en supprimant un fichier, vous modifiez le contenu du répertoire (ou, autrement dit, la liste des entrées de répertoire, car d irectory n'est rien d'autre qu'une liste de noms de fichiers et d'inodes ). Cela signifie que vous avez besoin d'autorisations d'écriture sur un répertoire. Même si vous êtes le propriétaire du fichier , sans autorisations de répertoire, vous ne pouvez pas supprimer de fichiers. Le reverse est également vrai : rmpeut supprimer les fichiers pouvant appartenir à d'autres personnes, si vous êtes le propriétaire du répertoire.

Donc, vous pouvez très bien avoir lu et exécuté des permissions sur un répertoire, ce qui vous permettra de parcourir le répertoire et d’afficher tout son contenu, par exemple ls /bin/echo, mais vous ne pouvez pas rm /bin/echo à moins d’être propriétaire de /bin ou d’élever vos privilèges avec Sudoname__.

Et vous verrez des cas comme celui-ci partout. En voici un exemple: https://superuser.com/a/331124/418028


Annuaires spéciaux '.' et '..'

Les répertoires . et .. constituent un autre cas particulier. Si vous faites ls . ou ls .., il vous montrera avec plaisir le contenu, mais rmname __ 'ing n'est pas autorisé:

$ rm -rf .
rm: refusing to remove '.' or '..' directory: skipping '.'
16

Si vous tapez ls * puis rm *, il est possible que vous supprimiez plus de fichiers que ls - ils ont peut-être été créés dans un intervalle de temps très court entre la fin de ls et le début de rm.

15
choroba

ls * et rm * ne sont pas responsables du développement du glob - c'est le shell qui le fait avant de le transmettre à la commande.

Cela signifie que vous pouvez utiliser la commande any avec la liste de fichiers étendue - je voudrais donc utiliser quelque chose qui fait le moins possible.

Donc, une meilleure façon de faire cela (ou du moins une autre façon) est de sauter l’intermédiaire.

echo * vous montrera exactement ce qui serait passé à votre commande rm.

9
Shadow

Si vous ne faites que lsau lieu de ls -a, oui rmpeut supprimer les fichiers cachés que vous n'avez pas vus avec lssans -a.

Exemple:

Selon :

dir_test
├── .test
└── test2

ls dir_test: affichera uniquement test2

ls -A dir_test: affichera test2 + .test

rm -r dir_test: va tout supprimer (.test + test2)

J'espère que cela vous aidera.

4
DevHugo

Que diriez-vous:

$ mkdir what
$ cd what
$ mkdir -p huh/uhm ./-r
$ ls *
uhm
$ rm *
$ ls
-r
$ ls -R
.:
-r

./-r:

Fondamentalement, les caractères génériques étendus à des éléments commençant par - (ou des éléments saisis manuellement commençant par - mais qui ressemble un peu plus à de la triche) peuvent être interprétés différemment par ls et rm.

4
user868053

There are Edge cas où lsn'est pas ce que rmsupprime. Un cas extrême, mais heureusement bénin, est si l'argument que vous passez est un lien symbolique vers un répertoire: lsvous montrera tous les fichiers du répertoire en lien symbolique, tandis que rmsupprimera le lien symbolique en laissant le répertoire d'origine et son contenu intacts:

% ln -s $HOME some_link
% ls some_link    # Will display directory contents  
bin    lib    Desktop ...
% rm some_link
% ls $HOME
bin    lib    Desktop ...
4
alexis

Il y a déjà beaucoup de bonnes réponses, mais je veux ajouter un aperçu plus profond.

Posez-vous la question: combien de paramètres sont passés à ls, si vous écrivez

ls *

...? Notez que la commande ls n'obtient pas le * en tant que paramètre s'il existe des fichiers pour lesquels * peut être développé. Au lieu de cela, le Shell effectue d'abord une analyse par globalisation avant d'appeler la commande. Par conséquent, la commande ls obtient en fait autant de paramètres qu'il y a de fichiers correspondant à cette analyse. Pour supprimer la suppression, citez le paramètre.

Cela est vrai pour toute commande: echo * vs echo '*'.

Il existe un script, appelez-le countparams.sh pour tester l'effet. Il vous indique le nombre de paramètres passés et les répertorie.

#!/bin/bash
echo "This script was given $# parameters."
arr=( "$@" )
for ((i=0;i<$#;i++)); do
        echo "Parameter $((i+1)): ${arr[$i]}"
done

Rendez-le exécutable et exécutez ./countparams.sh *. Apprenez de sa sortie!

3
rexkogitans

Le glob se développera de la même manière les deux fois si le contenu du répertoire est le même à ces deux moments différents.


Si vous voulez vraiment vérifier ce qui sera supprimé, utilisez rm -i *.txt. Il vous demandera séparément pour chaque fichier avant de (essayer de) le supprimer.

Ceci est garanti pour être sûr contre les conditions de course:
ls *.txt/un nouveau fichier est créé/rm *.txt
parce que vous êtes invité à chaque fichier par le même programme que celui qui effectue la suppression.


Cela est trop lourd pour une utilisation normale, et si vous alias rmà rm -i, vous utiliserez assez souvent \rm ou rm -f. Mais il convient au moins de mentionner qu’il existe une solution à la situation de concurrence critique. (Il est même portable sur des systèmes non-GNU: POSIX rm(1) spécifie l'option -i .)

Une autre option serait un tableau bash: to_remove=(*.txt), puis demandez à l'utilisateur de confirmer (peut-être après avoir effectué ls -ld -- "${to_remove[@]}"), puis rm -- "${to_remove[@]}". Ainsi, l’expansion globale n’est effectuée qu’une seule fois et la liste est transmise intégralement à rmname__.

Une autre option pratiquement utilisable est GNU rm -I ( page de manuel ), qui vous invite à supprimer plus de 4 éléments. (Mais ne vous montre pas la liste, mais le total.) J'utilise alias rm='rm -I' sur mon bureau.

C'est une bonne protection contre le retour du gros doigt avec un motif à moitié tapé qui correspond trop. Toutefois, l'utilisation de lsest généralement préférable dans un répertoire que vous possédez ou sur un système mono-utilisateur, et lorsqu'il n'y a pas de processus en arrière-plan qui pourrait créer de manière asynchrone de nouveaux fichiers. Pour vous protéger des gros doigtés, ne tapez pas rm -rf /foo/bar/baz de gauche à droite. rm -rf / est un cas spécial, mais rm -rf /usr ne l'est pas! Laissez la partie -rf ou commencez par lset ajoutez uniquement la partie rm -rf après la saisie du chemin.

1
Peter Cordes