$ touch "aaaaaa bbbbbbb ccc" dddddd "eeeee^Mffffff" zzzzz
$ for i in $(ls); do echo "[$i]"; done
[aaaaaa]
[bbbbbbb]
[ccc]
[dddddd]
ffffff]
[zzzzz]
$ for i in *; do echo "[$i]"; done
[aaaaaa bbbbbbb ccc]
[dddddd]
ffffff]
[zzzzz]

Mettons nous deux secondes dans la peau d'un shell basique qui analyse ligne par ligne la ligne de commande. Il réalise les opérations suivantes :

  1. Vérifie la syntaxe,
  2. S'il y a des caractères d'expansion (Joker par exemple), on déroule ce caractère puis on échappe les caractères importants,
  3. Développement/Expansion des variables (Remplacer $foo ou $(find...) par sa valeur),
  4. Découpage des arguments, (Découper les arguments en "mots"),
  5. Éxécution de la commande,

Maintenant, prenons l'exemple suivant :

$ foo="maou fait le chat"
$ for i in $foo; do
      echo $i;
   done

Pour la ligne "for i in $foo; do", voici le traitement :

  1. Syntaxe OK
  2. Pas de Joker,
  3. On remplace la variable par sa valeur :
    for i in maou fait le chat; do
  4. On découpe ce qui est entre "in" et le point-virgule (;),
    Les mots sont séparés par un des caractères contenus dans $IFS for i in "maou" "fait" "le" "chat"; do
  5. On éxécute la boucle.
    $i reçoit alors les valeurs "maou", "fait", "le", "chat"

Appliquons cela à la ligne suivante :

$ for i in $(ls); do
    ... ;
  done

Dans un premier temps, $(ls) va être étendue, puis découpé en mots. Aucun échappement de caractère ne sera fait, c'est là le problème, donc les mots seront découpés avec les séparateurs d'$IFS.

Alors que pour la ligne suivante :

$ for i in *; do
    ... ;
  done

Le shell rencontre un caractère Joker (*), donc il fait une expansion de nom de fichier, et en profite pour échapper tous les caractères qui ont une signification (Tout ce qui est dans $IFS entre autre).

Lorsque le shell passera à l'étape suivante, il ne découpera pas les mots avec les caractères espaces et tout ça car ils sont échappés...