Leer la salida de un comando en una matriz en Bash

Resuelto barp asked hace 12 años • 0 respuestas

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.

barp avatar Jul 11 '12 13:07 barp
Aceptado

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:

  1. Con el uso de Bash≥4 mapfile, es lo más eficiente:

    mapfile -t my_array < <( my_command )
    
  2. 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 )
    
  3. 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 la readdeclaración: esto solo establecerá la variable de entorno IFS para la readdeclaración únicamente. Por lo tanto, no afectará en absoluto al resto del guión. El propósito de esta variable es indicar readque se interrumpa la transmisión en el carácter EOL \n.
    • -r: esto es importante. Indica read que no se interpreten las barras invertidas como secuencias de escape.
    • -d '': tenga en cuenta el espacio entre la -dopció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 indica readque 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_arrayindica readque se llene la matriz my_arraymientras se lee la secuencia.
    • Debes usar la printf '\0'declaración después my_command , para que readdevuelva 0; en realidad no es gran cosa si no lo haces (sólo obtendrás un código de retorno 1, lo cual está bien si no lo usas set -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 de printf '', que no genera nada. printf '\0'imprime un byte nulo, necesario para readdejar 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 $linevariable 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 mapfileproporciona 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 mapfilea las opciones -Cque -ccontiene. (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 ten el directorio actual... y este nombre de archivo coincide con el glob [three four] ... en este punto algunas personas recomendarían usar set -fpara desactivar el globbing: pero míralo: tienes que cambiar IFSy usar set -fpara 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!

gniourf_gniourf avatar Oct 04 '2015 08:10 gniourf_gniourf

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 IFSvar env de la siguiente manera

  • como lo sugiere @ smac89 en los comentarios y otras respuestas
IFS=$'\n' my_array=( $(<command>) )
Michael Schlottke-Lakemper avatar Jul 11 '2012 06:07 Michael Schlottke-Lakemper

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).

Youness avatar Sep 25 '2015 22:09 Youness