GNU/Linux >> Tutoriels Linux >  >> Linux

Itérer sur une liste de fichiers avec des espaces

Il existe également une solution très simple :compter sur bash globbing

$ mkdir test
$ cd test
$ touch "stupid file1"
$ touch "stupid file2"
$ touch "stupid   file 3"
$ ls
stupid   file 3  stupid file1     stupid file2
$ for file in *; do echo "file: '${file}'"; done
file: 'stupid   file 3'
file: 'stupid file1'
file: 'stupid file2'

Notez que je ne suis pas sûr que ce comportement soit celui par défaut, mais je ne vois aucun paramètre spécial dans ma boutique, donc j'irais dire qu'il devrait être "sûr" (testé sur osx et ubuntu).


Il existe plusieurs façons pratiques d'y parvenir.

Si vous souhaitez vous en tenir étroitement à votre version originale, vous pouvez le faire de la manière suivante :

getlist() {
        IFS=$'\n'
        for file in $(find . -iname 'foo*') ; do
                printf 'File found: %s\n' "$file"
        done
}

Cela échouera toujours si les noms de fichiers contiennent des retours à la ligne littéraux, mais les espaces ne le casseront pas.

Cependant, jouer avec IFS n'est pas nécessaire. Voici ma méthode préférée :

getlist() {
    while IFS= read -d $'\0' -r file ; do
            printf 'File found: %s\n' "$file"
    done < <(find . -iname 'foo*' -print0)
}

Si vous trouvez le < <(command) syntaxe peu familière, vous devriez lire sur la substitution de processus. L'avantage de ceci sur for file in $(find ...) est que les fichiers avec des espaces, des retours à la ligne et d'autres caractères sont correctement gérés. Cela fonctionne car find avec -print0 utilisera un null (alias \0 ) comme caractère de fin pour chaque nom de fichier et, contrairement à la nouvelle ligne, null n'est pas un caractère légal dans un nom de fichier.

L'avantage par rapport à la version quasi équivalente

getlist() {
        find . -iname 'foo*' -print0 | while read -d $'\0' -r file ; do
                printf 'File found: %s\n' "$file"
        done
}

Est-ce que toute affectation de variable dans le corps de la boucle while est préservée. Autrement dit, si vous dirigez vers while comme ci-dessus puis le corps du while est dans un sous-shell qui n'est peut-être pas ce que vous voulez.

L'avantage de la version de substitution de processus par rapport à find ... -print0 | xargs -0 est minimal :le xargs La version est correcte si tout ce dont vous avez besoin est d'imprimer une ligne ou d'effectuer une seule opération sur le fichier, mais si vous devez effectuer plusieurs étapes, la version en boucle est plus simple.

MODIFIER :Voici un bon script de test pour que vous puissiez vous faire une idée de la différence entre les différentes tentatives de résolution de ce problème

#!/usr/bin/env bash

dir=/tmp/getlist.test/
mkdir -p "$dir"
cd "$dir"

touch       'file not starting foo' foo foobar barfoo 'foo with spaces'\
    'foo with'$'\n'newline 'foo with trailing whitespace      '

# while with process substitution, null terminated, empty IFS
getlist0() {
    while IFS= read -d $'\0' -r file ; do
            printf 'File found: '"'%s'"'\n' "$file"
    done < <(find . -iname 'foo*' -print0)
}

# while with process substitution, null terminated, default IFS
getlist1() {
    while read -d $'\0' -r file ; do
            printf 'File found: '"'%s'"'\n' "$file"
    done < <(find . -iname 'foo*' -print0)
}

# pipe to while, newline terminated
getlist2() {
    find . -iname 'foo*' | while read -r file ; do
            printf 'File found: '"'%s'"'\n' "$file"
    done
}

# pipe to while, null terminated
getlist3() {
    find . -iname 'foo*' -print0 | while read -d $'\0' -r file ; do
            printf 'File found: '"'%s'"'\n' "$file"
    done
}

# for loop over subshell results, newline terminated, default IFS
getlist4() {
    for file in "$(find . -iname 'foo*')" ; do
            printf 'File found: '"'%s'"'\n' "$file"
    done
}

# for loop over subshell results, newline terminated, newline IFS
getlist5() {
    IFS=$'\n'
    for file in $(find . -iname 'foo*') ; do
            printf 'File found: '"'%s'"'\n' "$file"
    done
}


# see how they run
for n in {0..5} ; do
    printf '\n\ngetlist%d:\n' $n
    eval getlist$n
done

rm -rf "$dir"

Vous pouvez remplacer l'itération basée sur les mots par une itération basée sur les lignes :

find . -iname "foo*" | while read f
do
    # ... loop body
done

Linux
  1. Rechercher des fichiers et des répertoires sous Linux avec la commande find

  2. Comment rechercher tous les fichiers avec une taille de fichier de zéro (0) octet dans un répertoire de manière récursive

  3. Comment puis-je générer une liste de fichiers avec leur chemin absolu sous Linux ?

  4. Répertorier tous les fichiers d'images graphiques avec rechercher ?

  5. liste/trouve tous les fichiers normaux dans tous les sous-répertoires à l'exception des fichiers binaires

Comment transférer des fichiers avec Rsync sur SSH

Commande de recherche Linux avec des exemples pratiques

Comment trouver des fichiers dans Debian 10

Retrouver les fichiers perdus avec Scalpel

Comment trouver des fichiers basés sur l'horodatage sous Linux

Comment rechercher des fichiers avec des dizaines de critères avec la commande Bash Find