GNU/Linux >> Tutoriales Linux >  >> Linux

Iterar sobre una lista de archivos con espacios

También hay una solución muy simple:confiar en 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'

Tenga en cuenta que no estoy seguro de que este comportamiento sea el predeterminado, pero no veo ninguna configuración especial en mi tienda, así que diría que debería ser "seguro" (probado en osx y ubuntu).


Hay varias formas viables de lograr esto.

Si quisiera ceñirse a su versión original, podría hacerlo de esta manera:

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

Esto seguirá fallando si los nombres de los archivos tienen saltos de línea literales, pero los espacios no lo dividirán.

Sin embargo, jugar con IFS no es necesario. Esta es mi forma preferida de hacer esto:

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

Si encuentra el < <(command) sintaxis desconocida, debe leer sobre la sustitución de procesos. La ventaja de esto sobre for file in $(find ...) es que los archivos con espacios, saltos de línea y otros caracteres se manejen correctamente. Esto funciona porque find con -print0 utilizará un null (también conocido como \0 ) como terminador para cada nombre de archivo y, a diferencia de la nueva línea, nulo no es un carácter legal en un nombre de archivo.

La ventaja de esto sobre la versión casi equivalente

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

Es que se conserva cualquier asignación de variable en el cuerpo del ciclo while. Es decir, si canaliza a while como arriba, entonces el cuerpo del while está en una subcapa que puede no ser lo que desea.

La ventaja de la versión de sustitución de proceso sobre find ... -print0 | xargs -0 es mínimo:El xargs La versión está bien si todo lo que necesita es imprimir una línea o realizar una sola operación en el archivo, pero si necesita realizar varios pasos, la versión en bucle es más fácil.

EDITAR :Aquí hay un buen script de prueba para que pueda tener una idea de la diferencia entre los diferentes intentos de resolver este problema

#!/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"

Puede reemplazar la iteración basada en palabras por una basada en líneas:

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

Linux
  1. Encuentre archivos y péguelos (con espacios)

  2. Listar todos los archivos de imágenes gráficas con find?

  3. Excluir la lista de archivos de la búsqueda

  4. encontrar archivos con ACL establecidas

  5. Verifique el uso del disco de los archivos devueltos con espacios

Cómo transferir archivos con Rsync a través de SSH

Comando Find de Linux con ejemplos prácticos

Cómo encontrar archivos en Debian 10

Encuentra archivos perdidos con Scalpel

Cómo encontrar archivos basados ​​en la marca de tiempo en Linux

Cómo encontrar archivos con docenas de criterios con el comando Bash Find