¿Cómo puedo almacenar los resultados del comando "buscar" como una matriz en Bash?

Resuelto Juneyoung Oh asked hace 10 años • 8 respuestas

Estoy intentando guardar el resultado findcomo 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 findcomo 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.

Juneyoung Oh avatar Apr 29 '14 13:04 Juneyoung Oh
Aceptado

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 finden una bashmatriz:

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 findy separar de forma segura los nombres de los archivos entre sí es usar -print0which 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 mapfileadmitieran cadenas separadas por nulos pero no lo hacen. Bash lo readhace 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

  1. La primera línea crea una matriz vacía:array=()

  2. Cada vez que readse ejecuta la declaración, se lee un nombre de archivo separado por nulos de la entrada estándar. La -ropción indica readque se dejen solos los caracteres de barra invertida. Indica que la entrada estará separada por nulos -d $'\0'. readComo omitimos el nombre a read, el shell coloca la entrada en el nombre predeterminado: REPLY.

  3. La array+=("$REPLY")declaración agrega el nuevo nombre del archivo a la matriz array.

  4. La última línea combina redirección y sustitución de comandos para proporcionar la salida finda la entrada estándar del whilebucle.

¿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 findse 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 whilebucle 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, lastpipepuede 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 lastpipele dice a bash que ejecute el último comando en la tubería en el shell actual (no en segundo plano). De esta manera, el arrayoleoducto seguirá existiendo una vez finalizado el oleoducto. Debido a que lastpipesó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 findmodo 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 -dopció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.

John1024 avatar Apr 29 '2014 06:04 John1024

Bash 4.4 introdujo una -dopció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 findsoporte -print0, como por ejemplo lo hace GNU find.

Del manual (omitiendo otras opciones):

mapfile [-d delim] [array]

-d
El primer carácter de delimse utiliza para terminar cada línea de entrada, en lugar de una nueva línea. Si delimes una cadena vacía, mapfileterminará una línea cuando lea un carácter NUL.

Y readarrayes sólo un sinónimo de mapfile.

Benjamin W. avatar Feb 06 '2019 19:02 Benjamin W.