pour la boucle dans les dossiers avec un caractère \ n dans leurs noms

9

J'ai quelques dossiers avec du \ncaractère dans leurs noms.

par exemple:

$ ls
''$'\n''Test'

Cela fait référence à un dossier avec le nom du test et une ligne vide devant son nom.

Donc, quand je lance des scripts comme celui-ci, dans son répertoire parent:

while IFS= read -r d; do 
    rmdir $d
done < <(find * -type d)

Ça montre:

rmdir: failed to remove '': No such file or directory
rmdir: failed to remove 'Test': No such file or directory

Parce qu'il s'exécute deux fois, une fois \net l'autre Test, car le nom du dossier a deux lignes.

Alors, comment puis-je résoudre ce problème de telle sorte que le script sache qu'il ne \nTests'agit que d'un dossier?

Tara S Volpe
la source
3
Vous devez utiliser la -print0directive find et l' -doption read. Voir stackoverflow.com/a/40189667/7552
glenn jackman
1
@glennjackman Veuillez répondre!
dessert
@glennjackman Merci pour votre réponse mais la find * -type d -print0 | while IFS= read -d '' file ; do rmdir $file ; donecommande a cette sortie rmdir: failed to remove 'Test': No such file or directory.
Tara S Volpe
5
Vous devez citer la variable:rmdir "$file"
glenn jackman
1
@glennjackman Il suffit de poster ceci comme réponse. C'est une bonne solution et d'ailleurs les commentaires ne sont pas le meilleur endroit pour ça. Vote positif déjà sous-entendu :)
Sergiy Kolodyazhnyy

Réponses:

12

Vous n'avez qu'une seule commande là-bas, il suffit donc d'appeler findavec l' -execindicateur d'appel rmdir:

find -depth -type d -exec rmdir {} \;

Ou utilisez l' -deleteoption comme dans find -type d -delete, mais elle ne fonctionnera pas avec les répertoires non vides. Pour cela, vous aurez également besoin d'un -emptydrapeau. Notez également, -deleteimplique -depthque cela peut être ignoré. Ainsi une autre alternative viable qui garde tout comme un seul processus:

find -type d -empty -delete

Si le répertoire n'est pas vide, utilisez rm -rf {} \;. Pour isoler uniquement les répertoires avec le \nnom de fichier, nous pouvons combiner la citation ANSI-C $'...' de -namebash avec l' option:

find  -type d -name $'*\n*' -empty -delete

POSIX-ly, nous pourrions le gérer de cette façon:

find -depth  -type d -name "$(printf '*\n*' )" -exec rmdir {} \;

Il convient de mentionner que si votre objectif est la suppression des répertoires, cela -deletesuffit, mais si vous souhaitez exécuter une commande sur le répertoire, -execc'est le plus approprié.

Voir également

Sergiy Kolodyazhnyy
la source
2
… À utiliser rm -rf avec précaution . ; P N'a pas findd' -deleteoption btw? Il supprime les répertoires vides et renvoie des erreurs pour les répertoires non vides comme rmdir- pourrait être moins cher.
dessert
3
@dessert C'est le cas et j'ai ajouté cela dans, mais -deletene supprimera pas les répertoires non vides. Par conséquent, l' utilisation prudente derm -rf
Sergiy Kolodyazhnyy
6
Exécutez toujours à sec de telles findcommandes sans aucune charge utile destructrice (c.-à-d. Supprimez le -deleteou -exec whatever \;) pour vérifier si la liste des fichiers affectés est réellement correcte et ne contient pas des éléments qui ne devraient pas être supprimés ...
Byte Commander
2
C'est askubuntu.com afin que nous puissions supposer GNU find. Utilisez -exec rmdir {} +pour regrouper plusieurs arguments sur une seule rmdirligne de commande. Et BTW " mais cela ne fonctionnera pas avec les répertoires non vides. " S'applique rmdirégalement, donc ce n'est pas vraiment une différence avec -delete. C'est une différence rm -r.
Peter Cordes
2
find -type d -empty -delete( -deleteimplique -depth).
Kevin
10

Vous pouvez utiliser des globes shell au lieu de find:

for d in */ ; do 
    rmdir "$d"
done

Le shell glob */correspond à tous les dossiers du répertoire actuel. Cette construction pour boucle assure automatiquement la séparation correcte des mots.

Notez que selon vos options de shell, cela peut ignorer les dossiers cachés (nom commençant par a.). Ce comportement peut être modifié pour correspondre à tous les fichiers de la session en cours à l'aide de la commande shopt -s dotglob.

N'oubliez pas non plus de toujours citer vos variables.

Byte Commander
la source
le glob ne trouvera que les fichiers / répertoires dans le répertoire courant, pas récursivement comme le findfait
ilkkachu
1
Vous avez raison @ilkkachu. Cela pourrait être changé en activant l'option shell globstar shopt -s globstaret en utilisant un **/*/glob à la place.
Byte Commander
6

Les deux réponses écrites jusqu'ici appellent rmdirune fois par répertoire, mais comme rmdirpeuvent prendre plusieurs arguments, je me demande: n'y a-t-il pas un moyen plus efficace?

On pourrait simplement faire

rmdir */

et c'est certainement le moyen le plus simple et le plus efficace, mais cela peut générer une erreur dans le cas de nombreux répertoires (voir Quelle est la longueur maximale des arguments de ligne de commande dans gnome-terminal? ). Si vous souhaitez que cette approche fonctionne récursivement, activez l' globstaroption shell avec shopt -s globstaret utilisez à la **/*/place de */.

Avec GNU find(et si nous ne voulons pas simplement utiliser -delete), nous pourrions faire

find -depth -type d -exec rmdir {} +

qui construit la ligne de commande "de la même manière que celle qui xargsconstruit ses lignes de commande" ( man find). Remplacez -depth-le -maxdepth 1si vous ne voulez pas qu'il fonctionne récursivement.

Un troisième moyen brillant de l'OMI est expliqué par Steeldriver dans cette réponse :

printf '%s\0' */ | xargs -0 rmdir

Cela utilise le shell intégré printfpour construire une liste d'arguments délimitée par zéro, cette liste est ensuite dirigée vers xargslaquelle appelle rmdirexactement aussi souvent que nécessaire. Vous pouvez le faire fonctionner récursivement avec shopt -s globstaret **/*/au lieu de */comme ci-dessus.

dessert
la source
2
findest récursif, mais pas la boucle de l'OP. Pour reproduire ce comportement, utilisez find -maxdepth 1 -type d -exec rmdir {} +. ( -depthest redondant dans ce cas.) Astuce intéressante printfpour émuler find -print0, je n'avais jamais vu ça auparavant.
Peter Cordes
1
Oh! J'ai vu le lset la whileboucle, j'ai raté qu'ils redirigeaient à partir d'une substitution de processus avec findau lieu de canaliser lsdans la boucle et ne le montraient pas pour une raison quelconque. C'est find *vraiment enterré. Oui, il est récursif et échouera s'il y a trop d'entrées de répertoire dans le répertoire, y compris des non-répertoires car il n'utilise pas */. find .serait beaucoup mieux, car il findest plus rapide à stating que l'expansion globale de bash.
Peter Cordes