8 votes

OSX Bash for loop - problèmes avec les espaces dans les noms de dossiers ?

Je suis un peu perdu sur la façon de gérer les espaces dans les noms de chemin lorsqu'ils sont renvoyés dans une boucle for.

Justification : Je nettoie les autorisations sur les dossiers et les fichiers que je copie depuis Windows. La plupart des fichiers se retrouvent avec -rwx------ o -rwxr-xr-x autorisations, j'aime donc faire " chmod -x * " et ensuite " chmod u+x <folders> "J'essaie donc ce qui suit :

$ alias getdirs='find . -maxdepth 1 -mindepth 1 -type d | cut -c 3-'
$ for i in $(getdirs); do chmod u+x $i; done

ce qui fonctionne bien, tant que les répertoires n'ont pas d'espace dans leur nom.

J'ai essayé différentes permutations de chmod u+x "$i" , chmod u+x '$i' et similaires pour obtenir le comportement que je souhaitais, mais en vain.

Comment améliorer mon code bash, qui fonctionne avec des noms de dossiers contenant des espaces ?

Les objectif de cela est de pouvoir supprimer le bit "exec" des fichiers simples (d'où le nom de chmod -x * ) mais ensuite de le restaurer dans les répertoires pour pouvoir y accéder ( chmod u+x <dirname> ). D'après les commentaires et les réponses reçus jusqu'à présent, je pense qu'il sera probablement plus facile de le faire avec la bonne incantation "trouver".

10voto

nohillside Points 82672

Ce genre de choses peut être délicat dans tous les shells Unix en raison de la façon dont l'espace agit comme un séparateur. L'utilisation d'alias dans le cadre de scripts du shell rend les choses encore plus intéressantes. Je lancerais probablement deux passes de find pour mettre d'abord les répertoires dans l'ordre, puis les fichiers :

find . -maxdepth 1 -mindepth 1 -type d -exec chmod u+x '{}' \;
find . -maxdepth 1 -mindepth 1 -type f -exec chmod u-x '{}' \;

8voto

SooDesuNe Points 3650

En général, la manière "correcte" d'analyser la sortie de find dans une boucle bash est d'utiliser un fichier while read plutôt qu'une boucle for boucle. En bash, les boucles for se séparent en utilisant n'importe quel espace blanc (espace, tabulation, nouvelle ligne) par défaut -- cela peut être changé, mais il est plus facile et (à mon avis) plus propre de l'utiliser read qui, par défaut, lit une ligne à la fois.

find . -maxdepth 1 -mindepth 1 -type d | while read i; do chmod u+x "$i"; done

Notez que j'ai cité le "$i" c'est tout aussi important, car le fait de citer les variables empêche l'interprète de scinder leur contenu (c'est le même problème que celui que pose le fait de citer les variables). for a, mais à l'autre bout). Notez également que vous ne pouvez pas utiliser de guillemets simples : '$i' renverrait une valeur littérale $i plutôt que le contenu de la variable.

Cela ne fonctionnera toujours pas pour les répertoires dont le nom comporte des nouvelles lignes. Il existe une solution de contournement impliquant la fonction -print0 mais je n'ai jamais vu de nouvelles lignes dans des noms de fichiers spécifiquement conçus pour tester des scripts. Je ne sais pas si cela fonctionne avec la version de bash sous OSX (tirée de wiki de greg ) :

find . -maxdepth 1 -mindepth 1 -type d -print0 | while IFS= read -r -d '' i; do chmod u+x "$i"; done

Toutefois, dans ce cas, il est plus facile d'utiliser des globes : un globule se terminant par un / ne s'étendra qu'aux répertoires, vous pouvez donc vous contenter de

chmod u+x */

(cela fonctionnera parfaitement avec les espaces, les nouvelles lignes, etc.) Plus généralement, pour parcourir en boucle tous les répertoires :

for f in */; do stuff with "$f"; done

Malheureusement, aucune version de bash ne permet de sélectionner des fichiers uniquement à l'aide de globs (c'est l'une des raisons pour lesquelles je préfère zsh, où les globs sont suffisamment puissants pour ne pas avoir à s'embêter avec find).

3voto

Flavin Points 553

J'ai deux suggestions à faire :

  1. Utilice sed pour mettre des guillemets à tous les noms de répertoire.
  2. Tube à xargs avec argument -L 1 . Ceci exécutera une commande sur chaque ligne de stdin, évitant ainsi l'utilisation de la commande for boucle.

Essayez ce pipeline :

find . -maxdepth 1 -mindepth 1 -type d | cut -c 3- | sed 's/.*/"&"/' | xargs -L 1 chmod u+x

2voto

kojiro Points 251

Le nœud du problème est que la séparation des mots se fait pour les raisons suivantes substitution de commande de manière à ce que les noms comportant des espaces ne puissent être distingués de noms entièrement distincts. Globbing ne souffre pas de cette difficulté, donc s'il existe un moyen d'identifier les répertoires avec un glob et d'éviter complètement la substitution de commande, vous êtes à l'abri. Et il y en a un :

chmod -x *
chmod u+x */

Mais même cela représente une charge de travail trop importante, car chmod a le X (X majuscule), qui applique le bit exécutable uniquement aux répertoires et aux fichiers qui sont déjà exécutables. Vous pouvez donc vous contenter de faire :

chmod -x *
chmod u+X *

1voto

stuffe Points 25320

Soit votre alias n'extrait que le premier bit avant l'espace, soit votre boucle for ne lit que le premier bit.

J'ai limité l'alias au dernier répertoire trouvé pour isoler les itérations à 1, j'ai imprimé ce que l'alias pense recevoir, et j'ai fait un écho du contenu de la variable pour m'assurer qu'il correspond :

jaravj$ alias getdirs='find . -maxdepth 1 -mindepth 1 -type d | tail -1|  cut -c 3-'
jaravj$ getdirs
jaravj$ for i in $(getdirs); do echo "Filename is "$i; chmod u+x $i; done

EDIT : modifié do; echo ... a do echo ... car il empêchait la ligne d'exécuter

LesApples.com

LesApples est une communauté de Apple où vous pouvez résoudre vos problèmes et vos doutes. Vous pouvez consulter les questions des autres utilisateurs d'appareils Apple, poser vos propres questions ou résoudre celles des autres.

Powered by:

X