Bash: canaliza la salida 'buscar' en 'readarray'

Bash: canaliza la salida 'buscar' en 'readarray'

Estoy intentando buscar archivos usando findy colocar esos archivos en una matriz Bash para poder realizar otras operaciones con ellos (por ejemplo, lso grepellos). Pero no puedo entender por qué readarrayno se lee el findresultado tal como se envía.

Digamos que tengo dos archivos en el directorio actual file1.txty file2.txt. Entonces el findresultado es el siguiente:

$ find . -name "file*"
./file1.txt
./file2.txt

Así que quiero canalizar eso en una matriz cuyos dos elementos sean las cadenas "./file1.txt"y "./file2.txt"(sin comillas, obviamente).

He probado esto, entre algunas otras cosas:

$ declare -a FILES
$ find . -name "file*" | readarray FILES
$ echo "${FILES[@]}"; echo "${#FILES[@]}"

0

Como puede ver en el echoresultado, mi matriz está vacía.

Entonces, ¿qué estoy haciendo mal exactamente aquí? ¿Por qué readarrayno se lee findla salida de como entrada estándar y se colocan esas cadenas en la matriz?

Respuesta1

Cuando se utiliza una canalización, bash ejecuta los comandos en subshells¹. Por lo tanto, la matriz se completa, pero en un subshell, por lo que el shell principal no tiene acceso a él. También es probable que desee la -topción para no almacenar los delimitadores de línea en los miembros de la matriz, ya que no forman parte de los nombres de los archivos.

Utilice la sustitución de procesos:

readarray -t FILES < <(find .)

Tenga en cuenta que no funciona para archivos con nuevas líneas en sus rutas. A menos que pueda garantizar que no será el caso, querrá usar registros delimitados por NUL en lugar de registros delimitados por nueva línea:

readarray -td '' < <(find . -print0)

(la -dopción se agregó en bash 4.4)


¹ excepto el último componente de tubería cuando se usa la lastpipeopción, pero eso es solo para invocaciones no interactivas de bash.

Respuesta2

La solución correcta es:

unset a; declare -a a
while IFS= read -r -u3 -d $'\0' file; do
    a+=( "$file" )        # or however you want to process each file
done 3< <(find /tmp -type f -print0)

Eso es similar a lo queBash de GregPreguntas frecuentes 020explica detalladamente yesta respuesta cubre.

No tiene ningún problema con archivos con nombres impares (que no contienen NUL en el nombre), con espacios o líneas nuevas. Y el resultado se establece en una matriz, lo que lo hace útil para su posterior procesamiento.

Respuesta3

readarraytambién puede leer desde stdin

readarray FILES <<< "$(find . -name "file*")"; echo "${#FILES[@]}"

Respuesta4

Con shopt -s lastpipela solución que se muestra en la pregunta original funciona, ya que la parte posterior a la última |se ejecuta en el mismo proceso que la mayor parte del script:

shopt -s lastpipe

# This is just the code form the original question.
# Of course it could be done with -print0, etc.
declare -a FILES
find . -name "name*" | readarray -t FILES
echo find and readarray exit status: "${PIPESTATUS[@]}"
echo "${FILES[@]}"
echo "${#FILES[@]}"

Sin embargo, esto sólo es práctico si lastpipese puede convertir en un estándar para todo el proyecto (tal vez ya tenga una configuración común para set -u, etc.). También sepa que lastpipesolo funciona para shells no interactivos.

El otro reemplazo de tubería típico < <(...)(sustitución de procesos) tiene el problema de que es bastante difícil verificar si el proceso hijo ha tenido éxito.

información relacionada