¿Cómo puedo almacenar los resultados del comando "buscar" como una matriz en Bash?
Estoy intentando guardar el resultado find
como matrices. Aquí está mi código:
#!/bin/bash
echo "input : "
read input
echo "searching file with this pattern '${input}' under present directory"
array=`find . -name ${input}`
len=${#array[*]}
echo "found : ${len}"
i=0
while [ $i -lt $len ]
do
echo ${array[$i]}
let i++
done
Obtengo 2 archivos .txt en el directorio actual. Entonces espero '2' como resultado de ${len}
. Sin embargo, imprime 1. La razón es que toma todos los resultados find
como un solo elemento. ¿Cómo puedo arreglar esto?
PD:
encontré varias soluciones en StackOverFlow sobre un problema similar. Sin embargo, son un poco diferentes por lo que no puedo aplicarlos en mi caso. Necesito almacenar los resultados en una variable antes del ciclo. Gracias de nuevo.
Actualización 2020 para usuarios de Linux:
Si tiene una versión actualizada de bash (4.4-alfa o mejor), como probablemente la tenga si está en Linux, entonces debería usar la respuesta de Benjamin W.
Si está en Mac OS, que (la última vez que verifiqué) todavía usa bash 3.2, o está usando un bash más antiguo, continúe con la siguiente sección.
Respuesta para bash 4.3 o anterior
Aquí hay una solución para obtener la salida find
en una bash
matriz:
array=()
while IFS= read -r -d $'\0'; do
array+=("$REPLY")
done < <(find . -name "${input}" -print0)
Esto es complicado porque, en general, los nombres de archivos pueden tener espacios, nuevas líneas y otros caracteres hostiles al script. La única forma de usar find
y separar de forma segura los nombres de los archivos entre sí es usar -print0
which imprime los nombres de los archivos separados con un carácter nulo. Esto no sería un gran inconveniente si las funciones readarray
/ de bash mapfile
admitieran cadenas separadas por nulos pero no lo hacen. Bash lo read
hace y eso nos lleva al bucle anterior.
[Esta respuesta se escribió originalmente en 2014. Si tiene una versión reciente de bash, consulte la actualización a continuación.]
Cómo funciona
La primera línea crea una matriz vacía:
array=()
Cada vez que
read
se ejecuta la declaración, se lee un nombre de archivo separado por nulos de la entrada estándar. La-r
opción indicaread
que se dejen solos los caracteres de barra invertida. Indica que la entrada estará separada por nulos-d $'\0'
.read
Como omitimos el nombre aread
, el shell coloca la entrada en el nombre predeterminado:REPLY
.La
array+=("$REPLY")
declaración agrega el nuevo nombre del archivo a la matrizarray
.La última línea combina redirección y sustitución de comandos para proporcionar la salida
find
a la entrada estándar delwhile
bucle.
¿Por qué utilizar la sustitución de procesos?
Si no usáramos la sustitución de procesos, el bucle podría escribirse como:
array=()
find . -name "${input}" -print0 >tmpfile
while IFS= read -r -d $'\0'; do
array+=("$REPLY")
done <tmpfile
rm -f tmpfile
En lo anterior, la salida de find
se almacena en un archivo temporal y ese archivo se usa como entrada estándar para el bucle while. La idea de la sustitución de procesos es hacer innecesarios estos archivos temporales. Entonces, en lugar de que el while
bucle obtenga su entrada estándar de tmpfile
, podemos hacer que obtenga su entrada estándar de <(find . -name ${input} -print0)
.
La sustitución de procesos es muy útil. En muchos lugares donde un comando quiere leer de un archivo, puede especificar la sustitución del proceso, <(...)
en lugar de un nombre de archivo. Existe una forma análoga, >(...)
que se puede utilizar en lugar de un nombre de archivo donde el comando quiere escribir en el archivo.
Al igual que las matrices, la sustitución de procesos es una característica de bash y otros shells avanzados. No forma parte del estándar POSIX.
Alternativa: último tubo
Si lo desea, lastpipe
puede usarse en lugar de sustitución de procesos (consejo: César ):
set +m
shopt -s lastpipe
array=()
find . -name "${input}" -print0 | while IFS= read -r -d $'\0'; do array+=("$REPLY"); done; declare -p array
shopt -s lastpipe
le dice a bash que ejecute el último comando en la tubería en el shell actual (no en segundo plano). De esta manera, el array
oleoducto seguirá existiendo una vez finalizado el oleoducto. Debido a que lastpipe
sólo tiene efecto si el control de trabajos está desactivado, ejecutamos set +m
. (En un script, a diferencia de la línea de comando, el control de trabajos está desactivado de forma predeterminada).
Notas adicionales
El siguiente comando crea una variable de shell, no una matriz de shell:
array=`find . -name "${input}"`
Si quisiera crear una matriz, necesitaría poner pares alrededor de la salida de buscar. Entonces, ingenuamente, se podría:
array=(`find . -name "${input}"`) # don't do this
El problema es que el shell realiza la división de palabras en los resultados de find
modo que no se garantiza que los elementos de la matriz sean los que usted desea.
Actualización 2019
A partir de la versión 4.4-alfa, bash ahora admite una -d
opción para que el bucle anterior ya no sea necesario. En su lugar, se puede utilizar:
mapfile -d $'\0' array < <(find . -name "${input}" -print0)
Para obtener más información sobre esto, consulte (y vote a favor) la respuesta de Benjamin W.
Bash 4.4 introdujo una -d
opción para readarray
/ mapfile
, por lo que ahora esto se puede resolver con
readarray -d '' array < <(find . -name "$input" -print0)
para un método que funcione con nombres de archivos arbitrarios, incluidos espacios en blanco, nuevas líneas y caracteres globales. Esto requiere que su find
soporte -print0
, como por ejemplo lo hace GNU find.
Del manual (omitiendo otras opciones):
mapfile [-d delim] [array]
-d
El primer carácter dedelim
se utiliza para terminar cada línea de entrada, en lugar de una nueva línea. Sidelim
es una cadena vacía,mapfile
terminará una línea cuando lea un carácter NUL.
Y readarray
es sólo un sinónimo de mapfile
.