Leer la salida de un comando en una matriz en Bash
Necesito leer el resultado de un comando en mi script en una matriz. El comando es, por ejemplo:
ps aux | grep | grep | x
y da la salida línea por línea así:
10
20
30
Necesito leer los valores de la salida del comando en una matriz, y luego trabajaré un poco si el tamaño de la matriz es inferior a tres.
Las otras respuestas se interrumpirán si la salida del comando contiene espacios (lo cual es bastante frecuente) o caracteres globales como *
,, ?
.[...]
Para obtener el resultado de un comando en una matriz, con una línea por elemento, existen esencialmente 3 formas:
Con el uso de Bash≥4
mapfile
, es lo más eficiente:mapfile -t my_array < <( my_command )
De lo contrario, un bucle que lee la salida (más lento, pero seguro):
my_array=() while IFS= read -r line; do my_array+=( "$line" ) done < <( my_command )
Como sugirió Charles Duffy en los comentarios (¡gracias!), lo siguiente podría funcionar mejor que el método de bucle del número 2:
IFS=$'\n' read -r -d '' -a my_array < <( my_command && printf '\0' )
Asegúrese de utilizar exactamente este formulario, es decir, asegúrese de tener lo siguiente:
IFS=$'\n'
en la misma línea que laread
declaración: esto solo establecerá la variable de entornoIFS
para laread
declaración únicamente. Por lo tanto, no afectará en absoluto al resto del guión. El propósito de esta variable es indicarread
que se interrumpa la transmisión en el carácter EOL\n
.-r
: esto es importante. Indicaread
que no se interpreten las barras invertidas como secuencias de escape.-d ''
: tenga en cuenta el espacio entre la-d
opción y su argumento''
. Si no deja un espacio aquí,''
nunca se verá, ya que desaparecerá en el paso de eliminación de comillas cuando Bash analice la declaración. Esto indicaread
que se detenga la lectura en el byte nulo. Algunas personas lo escriben como-d $'\0'
, pero en realidad no es necesario.-d ''
es mejor.-a my_array
indicaread
que se llene la matrizmy_array
mientras se lee la secuencia.- Debes usar la
printf '\0'
declaración despuésmy_command
, para queread
devuelva0
; en realidad no es gran cosa si no lo haces (sólo obtendrás un código de retorno1
, lo cual está bien si no lo usasset -e
, lo cual no deberías hacer de todos modos), pero tenlo en cuenta. Es más limpio y semánticamente más correcto. Tenga en cuenta que esto es diferente deprintf ''
, que no genera nada.printf '\0'
imprime un byte nulo, necesario pararead
dejar felizmente de leer allí (¿recuerdas la-d ''
opción?).
Si puede, es decir, si está seguro de que su código se ejecutará en Bash≥4, utilice el primer método. Y puedes ver que también es más corto.
Si desea utilizar read
, el bucle (método 2) podría tener una ventaja sobre el método 3 si desea realizar algún procesamiento a medida que se leen las líneas: tiene acceso directo a él (a través de la $line
variable en el ejemplo que di), y también tienes acceso a las líneas ya leídas (a través de la matriz ${my_array[@]}
en el ejemplo que di).
Tenga en cuenta que mapfile
proporciona una manera de evaluar una devolución de llamada en cada línea leída y, de hecho, incluso puede indicarle que solo llame a esta devolución de llamada cada N líneas leídas; Eche un vistazo help mapfile
a las opciones -C
que -c
contiene. (Mi opinión sobre esto es que es un poco complicado, pero puede usarse a veces si solo tienes cosas simples que hacer. ¡Realmente no entiendo por qué se implementó esto en primer lugar!).
Ahora te voy a decir el porqué del siguiente método:
my_array=( $( my_command) )
se rompe cuando hay espacios:
$ # I'm using this command to test:
$ echo "one two"; echo "three four"
one two
three four
$ # Now I'm going to use the broken method:
$ my_array=( $( echo "one two"; echo "three four" ) )
$ declare -p my_array
declare -a my_array='([0]="one" [1]="two" [2]="three" [3]="four")'
$ # As you can see, the fields are not the lines
$
$ # Now look at the correct method:
$ mapfile -t my_array < <(echo "one two"; echo "three four")
$ declare -p my_array
declare -a my_array='([0]="one two" [1]="three four")'
$ # Good!
Entonces algunas personas recomendarán usar IFS=$'\n'
para solucionarlo:
$ IFS=$'\n'
$ my_array=( $(echo "one two"; echo "three four") )
$ declare -p my_array
declare -a my_array='([0]="one two" [1]="three four")'
$ # It works!
Pero ahora usemos otro comando, con globos :
$ echo "* one two"; echo "[three four]"
* one two
[three four]
$ IFS=$'\n'
$ my_array=( $(echo "* one two"; echo "[three four]") )
$ declare -p my_array
declare -a my_array='([0]="* one two" [1]="t")'
$ # What?
Esto se debe a que tengo un archivo llamado t
en el directorio actual... y este nombre de archivo coincide con el glob [three four]
... en este punto algunas personas recomendarían usar set -f
para desactivar el globbing: pero míralo: tienes que cambiar IFS
y usar set -f
para poder arreglar un ¡Técnica rota (y ni siquiera la estás arreglando realmente)! Al hacer eso, en realidad estamos luchando contra el shell, no trabajando con el shell .
$ mapfile -t my_array < <( echo "* one two"; echo "[three four]")
$ declare -p my_array
declare -a my_array='([0]="* one two" [1]="[three four]")'
¡Aquí estamos trabajando con el caparazón!
Puedes usar
my_array=( $(<command>) )
para almacenar la salida del comando <command>
en la matriz my_array
.
Puede acceder a la longitud de esa matriz usando
my_array_length=${#my_array[@]}
Ahora la longitud se almacena en my_array_length
.
Manejo de problemas de expansión y espacios.
Para evitar problemas de expansión, puede configurar la IFS
var env de la siguiente manera
- como lo sugiere @ smac89 en los comentarios y otras respuestas
IFS=$'\n' my_array=( $(<command>) )
He aquí un ejemplo sencillo. Imagine que va a colocar los archivos y nombres de directorios ( en la carpeta actual ) en una matriz y contarlos. El guión sería como;
my_array=( `ls` )
my_array_length=${#my_array[@]}
echo $my_array_length
O puede iterar sobre esta matriz agregando el siguiente script:
for element in "${my_array[@]}"
do
echo "${element}"
done
Tenga en cuenta que este es el concepto central y la entrada debe desinfectarse antes del procesamiento , es decir, eliminar caracteres adicionales, manejar cadenas vacías, etc. (lo cual está fuera del tema de este hilo).