
Estoy intentando buscar archivos usando find
y colocar esos archivos en una matriz Bash para poder realizar otras operaciones con ellos (por ejemplo, ls
o grep
ellos). Pero no puedo entender por qué readarray
no se lee el find
resultado tal como se envía.
Digamos que tengo dos archivos en el directorio actual file1.txt
y file2.txt
. Entonces el find
resultado 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 echo
resultado, mi matriz está vacía.
Entonces, ¿qué estoy haciendo mal exactamente aquí? ¿Por qué readarray
no se lee find
la 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 -t
opció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 -d
opción se agregó en bash 4.4)
¹ excepto el último componente de tubería cuando se usa la lastpipe
opció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
readarray
también puede leer desde stdin
readarray FILES <<< "$(find . -name "file*")"; echo "${#FILES[@]}"
Respuesta4
Con shopt -s lastpipe
la 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 lastpipe
se 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 lastpipe
solo 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.