Forum Programmation.shell problème find et espaces dans les noms de fichiers

Posté par  . Licence CC By‑SA.
Étiquettes : aucune
0
2
oct.
2019

Bonjour

Ce code coupe les fichiers au niveau des espaces dans les noms :

for fichier in $(find * -maxdepth 0 -prune -type f )
si je fais :

IFS='
'
for fichier in $(find * -maxdepth 0 -prune -type f ) ça fonctionne.

J'aimerais savoir s'il n'y a pas un autres moyen que le changement de l'ISF pour faire marcher cette ligne.

Merci

  • # -print0 ?

    Posté par  . Évalué à 3.

    j'ai l'impression de manquer un peu de contexte pour savoir si je ne réponds pas à côté de la plaque, mais find dispose de l'option print0 qui permet de séparer les objets trouvés par le caractère nul plutôt que l'espace. Il faut du coup utiliser quelque chose qui sait se débrouiller avec des éléments séparés par des caractères nuls plutôt que des espaces (souvent, c'est xargs avec l'option -0).

    Difficile d'en dire plus sans savoir comment ça s'intègre dans ce que tu essaies de faire.

  • # Plusieurs solutions

    Posté par  (site web personnel) . Évalué à 5. Dernière modification le 02 octobre 2019 à 18:11.

    mkdir toto && cd toto
    touch "bla bla"
    touch "bli bli"
    find . -type f -print0 | xargs --null ls -l         # un seul processus ls consommant les données
    find . -type f -exec ls -l {} \;                    # un processus ls par fichier
    for f in "$(find . -type f)"; do ls -l "$f" ; done  # un processus ls par fichier

    Souvent la méthode avec xargs est mieux (un seul processus lancé), sauf dans le cas où tu as beaucoup de fichiers et tu risques de dépasser la liste d'arguments maximale qu'on peut passer à un process.

    Autre liste de méthodes ici:
    https://stackoverflow.com/questions/9612090/how-to-loop-through-file-names-returned-by-find

    Je note sur leur réponse que tu peux te passer de find en utilisant shopt -s globstar et glober en récursif avec **/*.

    • [^] # Re: Plusieurs solutions

      Posté par  . Évalué à 1.

      Bonjour

      Désolé de répondre un peu tard. Merci pour ces différentes solutions qui fonctionnent parfaitement dans mon cas.

    • [^] # Re: Plusieurs solutions

      Posté par  . Évalué à 1. Dernière modification le 05 octobre 2019 à 10:58.

      Re :

      Si je fais par exemple

      for f in "$(find * -maxdepth 0 -type f)"; do echo "$f"; echo "fin de ligne"; done

      ça me donne bien la liste souhaité, mais le "fin de ligne ne s'affiche qu'une fois à la fin de la liste alors qu'il se trouve bien dans la boucle 'for'. Problème de syntaxe ?

      • [^] # Re: Plusieurs solutions

        Posté par  (site web personnel) . Évalué à 1.

        La boucle n'est pas bonne, il suffit d'ajouter un peu de texte autour de $f pour s'en convaincre :

        for f in "$(find * -maxdepth 0 -type f)"; do echo "1 seul résultat >>>$f<<<"; echo "fin de ligne"; done

        Normal : le résultat de find est encadré par des guillemets, for opère sur un seul mot, donc un seul tour de boucle.

        Au passage, je ne comprends pas à quoi sert find * plutôt que find. Cela va même provoquer une erreur quand un répertoire est vide, puisque * ne sera pas remplacé, donc find va essayer de travailler sur (littéralement) *

        Debian Consultant @ DEBAMAX

        • [^] # Re: Plusieurs solutions

          Posté par  (site web personnel) . Évalué à 2. Dernière modification le 07 octobre 2019 à 16:14.

          Ah, effectivement, mea culpa. Mais on ne peut pas non plus enlever les guillemets autour du $(find ...) car alors on ne gère plus les fichiers ayant un espace dans leur nom.

          • [^] # Re: Plusieurs solutions

            Posté par  (site web personnel) . Évalué à 1. Dernière modification le 07 octobre 2019 à 18:46.

            C'est donc une mauvaise solution, puisque cela met en place une boucle qui ne fait qu'un seul tour de boucle avec comme seul objet la concaténation de l'ensemble de la sortie de find, non ?

            Debian Consultant @ DEBAMAX

  • # Autres solutions

    Posté par  . Évalué à 1.

    Attention, j'écris ça de tête, sans vérifier.

    D'abord, une solution simple mais bourrin et pas sans poser problème :

    find ... | while read truc1 truc2 ... trucn
    do
        ...
    done
    

    Le problème ? Si une variable est initialisée hors de la boucle, elle ne peut pas être modifiée dans cette boucle :

    v=1
    find ... | while read truc1 truc2 ... trucn
    do
        v=$((v+1))
    done
    echo $v
    

    affichera 1, quelque soit le nombre de tour de boucle, car le while est à droite d'un pipe ('|'), et s'exécute donc dans un shell fils.
    Pour que le while reste dans le même shell, et toujours en étant bourrin :

    v=1
    
    find ... > /tmp/fichierIntermediaire
    
    while read truc1 truc2 ... trucn
    do
        v=$((v+1))
    done < /tmp/fichierIntermediaire
    echo $v
    

    Cette fois, v va changer en fonction du nombre de lignes dans le fichier temporaire.

    Pour éviter ce fichier temporaire, il faut utiliser quelque chose du genre :

    v=1
    
    while read truc1 truc2 ... trucn
    do
        v=$((v+1))
    done < <(find ...)
    echo $v
    

    C'est surtout ici que j'ai un doute sur la syntaxe. Mais si erreur il y a, on a l'idée.

    • [^] # Re: Autres solutions

      Posté par  . Évalué à 1.

      [Complément 1]
      Toutes ces formes d'écriture pallient au problème du for. Le for reçoit les paramètres après interprétation par le shell, et si ces paramètres comportent des espaces, ils deviennent deux ou plusieurs paramètres du for.

      [Complément 2]
      La version avec fichier temporaire présente l'inconvénient du … fichier temporaire ! Mais, dans certains cas, le résultat du find peut être utile à plusieurs boucle indépendantes, et donc, ce fichier temporaire permet d'exécuter ce find une fois pour toutes

  • # re : problème supplémentaire

    Posté par  . Évalué à 1.

    Merci pour les différentes solutions mais du coup j'ai un problème supplémentaire :

    Si je fais par exemple

    for f in "$(find * -maxdepth 0 -type f)"; do echo "$f"; echo "fin de ligne"; done

    j'obtiens bien la liste souhaité, mais le "fin de ligne ne s'affiche qu'une fois à la fin de la liste alors qu'il se trouve bien dans la boucle 'for'. Problème de syntaxe ?

    • [^] # Re: re : problème supplémentaire

      Posté par  . Évalué à 1. Dernière modification le 06 octobre 2019 à 12:19.

      [oh, ma réponse d'hier n'est pas passée ? Je la refais]

      Oui, c'est tout à fait normal.
      L'expression $(find * -maxdepth 0 -type f) est entre guillemets, et de ce fait, elle constitue un et un seul paramètre du for.
      f prend donc une et une seule valeur, d'où un seul tour de boucle et un seul message "fin de ligne".

      Problème : si tu supprimes les guillemets, tu retombes sur le problèmes espaces dans les noms de fichier. Chaque espace divisera le nom en deux et créera un paramètre supplémentaire au for là où il ne faudrait pas.

      On en revient aux solutions à base de read et de while.

      Pourquoi s'acharner sur le for sinon ? Je ne l'utilise quasiment jamais, cette limitation est trop contraignante.

Suivre le flux des commentaires

Note : les commentaires appartiennent à celles et ceux qui les ont postés. Nous n’en sommes pas responsables.