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:
Con el uso de Bash≥4 mapfile
, es el 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 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 read
declaración: esto solo establecerá la variable de entorno solo IFS
para la read
declaración. Por lo tanto, no afectará en absoluto al resto del guión. El propósito de esta variable es indicar read
que 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 -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 citas cuando Bash analice la declaración. Esto le dice read
que deje de leer en el byte nulo. Algunas personas lo escriben como -d $'\0'
, pero no es realmente necesario. -d ''
es mejor.
-a my_array
le dice read
que llene la matriz my_array
mientras lee la secuencia.
- Debe usar la
printf '\0'
instrucción after my_command
, para que read
regrese 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 read
para 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 $line
variable 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 mapfile
proporciona 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 mapfile
las opciones -C
y -c
allí. (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 t
en el directorio actual ... y este nombre de archivo coincide con el glob [three four]
... en este punto, algunas personas recomendarían usarlo set -f
para deshabilitar 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, 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!