Leer la salida de un comando en una matriz en Bash

111

Necesito leer la salida 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 menor que tres.

barp
fuente
5
Hola @barp, RESPONDE SUS PREGUNTAS, no sea que su tipo sea una carga para toda la comunidad.
James
9
@James, el problema no es el hecho de que no está respondiendo su pregunta ... este es un sitio de preguntas y respuestas. Simplemente no los marcó como respondidos. Debería marcarlos. Insinuación. @ barp
DDPWNAGE
4
Por favor @barp, marque la pregunta como respondida.
smonff
Relacionado: recorrer el contenido de un archivo en Bash desde que leer la salida de un comando a través de la sustitución del proceso es similar a leer de un archivo.
codeforester

Respuestas:

161

Las otras respuestas se romperá si la salida del comando contiene espacios (lo cual es bastante frecuente) o Glob caracteres 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 el 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 en el 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 solo IFS para la readdeclaración. Por lo tanto, no afectará en absoluto al resto del guión. El propósito de esta variable es indicar readque se rompa la secuencia en el carácter EOL \n.
    • -r: esto es importante. Dice read que no interprete 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 citas cuando Bash analice la declaración. Esto le dice readque deje de leer en el byte nulo. Algunas personas lo escriben como -d $'\0', pero no es realmente necesario. -d ''es mejor.
    • -a my_arrayle dice readque llene la matriz my_arraymientras lee la secuencia.
    • Debe usar la printf '\0'instrucción after my_command , para que readregrese 0; en realidad, no es gran cosa si no lo hace (solo obtendrá un código de retorno 1, lo cual está bien si no lo usa set -e, que de todos modos no debería), pero téngalo en cuenta. Es más limpio y semánticamente correcto. Tenga en cuenta que esto es diferente de printf '', que no genera nada. printf '\0'imprime un byte nulo, necesario readpara 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 usar read, el ciclo (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 tiene 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 que se evalúe una devolución de llamada en cada línea leída y, de hecho, incluso puede decirle que solo llame a esta devolución de llamada cada N líneas leídas; eche un vistazo a help mapfilelas opciones -Cy -callí. (Mi opinión sobre esto es que es un poco torpe, pero se puede usar a veces si solo tienes cosas simples que hacer; ¡realmente no entiendo por qué esto se implementó en primer lugar!).


Ahora les voy a decir por qué el 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 usarlo 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 globs :

$ 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?

Eso es porque 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 usarlo set -fpara deshabilitar 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, realmente estamos luchando contra el caparazón, no trabajando con el caparazón .

$ 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
fuente
4
Esto es genial, nunca había oído hablar mapfileantes, es exactamente lo que me he perdido durante años. Supongo que las versiones recientes de bashtienen tantas características nuevas y agradables que debería pasar unos días leyendo los documentos y escribiendo una buena hoja de referencia.
Gene Pavlovsky
6
Por cierto, para usar esta sintaxis < <(command)en scripts de shell, la línea shebang debería ser #!/bin/bash- si se ejecuta como #!/bin/sh, bash saldrá con un error de sintaxis.
Gene Pavlovsky
1
Ampliando la nota útil de @ GenePavlovsky, el script también debe ejecutarse con el comando bash bash my_script.shy no con el comando shsh my_script.sh
Vito
2
@Vito: de hecho, esta respuesta es solo para Bash, pero esto no debería ser un problema, ya que los shells POSIX estrictamente compatibles ni siquiera implementan arreglos ( shy dashno saben nada sobre arreglos, excepto, por supuesto, para la $@matriz de parámetros posicionales ).
gniourf_gniourf
3
Como otra alternativa que no requiere bash 4.0, considere IFS=$'\n' read -r -d '' -a my_array < <(my_command && printf '\0'): funciona correctamente en bash 3.xy también pasa por un estado de salida fallido de my_commanda read.
Charles Duffy
86

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 formato my_array_length.

Michael Schlottke-Lakemper
fuente
19
¿Qué pasa si la salida de $ (comando) tiene espacios y varias líneas con espacios? Agregué "$ (comando)" y coloca todos los resultados de todas las líneas en el primer elemento [0] de la matriz.
ikwyl6
3
@ ikwyl6 una solución alternativa es asignar la salida del comando a una variable y luego hacer una matriz con ella o agregarla a una matriz. VAR="$(<command>)"y luego my_array=("$VAR")omy_array+=("$VAR")
Vito
10

Imagine que va a poner los archivos y nombres de directorio (debajo de la carpeta actual) en una matriz y contar sus elementos. 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 se considera desinfectada antes, es decir, eliminar caracteres adicionales, manejar cadenas vacías, etc. (que está fuera del tema de este hilo).

Youness
fuente
3
Terrible idea por las razones mencionadas en la respuesta anterior
Hubert Grzeskowiak