Les autres réponses éclateront si la sortie de la commande contient des espaces ( ce qui est assez fréquent) ou des personnages comme glob *
, ?
, [...]
.
Pour obtenir la sortie d'une commande dans un tableau, avec une ligne par élément, il existe essentiellement 3 façons:
Avec Bash≥4 mapfile
, c'est le plus efficace:
mapfile -t my_array < <( my_command )
Sinon, une boucle lisant la sortie (plus lente, mais sûre):
my_array=()
while IFS= read -r line; do
my_array+=( "$line" )
done < <( my_command )
Comme suggéré par Charles Duffy dans les commentaires (merci!), Ce qui suit pourrait fonctionner mieux que la méthode en boucle du numéro 2:
IFS=$'\n' read -r -d '' -a my_array < <( my_command && printf '\0' )
Veuillez vous assurer que vous utilisez exactement ce formulaire, c'est-à-dire que vous disposez des éléments suivants:
IFS=$'\n'
sur la même ligne que l' read
instruction: cela ne définira la variable d'environnement que IFS
pour l' read
instruction uniquement. Cela n'affectera donc pas du tout le reste de votre script. Le but de cette variable est de dire read
de couper le flux au caractère EOL \n
.
-r
: c'est important. Il dit read
de ne pas interpréter les contre-obliques comme des séquences d'échappement.
-d ''
: veuillez noter l'espace entre l' -d
option et son argument ''
. Si vous ne laissez pas d'espace ici, le ''
ne sera jamais vu, car il disparaîtra lors de l' étape de suppression du devis lorsque Bash analysera l'instruction. Cela indique read
d'arrêter la lecture à l'octet nul. Certaines personnes l'écrivent comme -d $'\0'
, mais ce n'est pas vraiment nécessaire. -d ''
est mieux.
-a my_array
dit read
de remplir le tableau my_array
lors de la lecture du flux.
- Vous devez utiliser l'
printf '\0'
instruction après my_command
, de sorte que read
retourne 0
; ce n'est en fait pas un gros problème si vous ne le faites pas (vous obtiendrez simplement un code de retour 1
, ce qui est bien si vous ne l'utilisez pas set -e
- ce que vous ne devriez pas de toute façon), mais gardez cela à l'esprit. C'est plus propre et plus sémantiquement correct. Notez que c'est différent de printf ''
, qui ne produit rien. printf '\0'
affiche un octet nul, nécessaire read
pour arrêter joyeusement la lecture (vous vous souvenez de l' -d ''
option?).
Si vous le pouvez, c'est-à-dire si vous êtes sûr que votre code fonctionnera sur Bash≥4, utilisez la première méthode. Et vous pouvez voir que c'est plus court aussi.
Si vous souhaitez l'utiliser read
, la boucle (méthode 2) peut avoir un avantage sur la méthode 3 si vous souhaitez effectuer un traitement au fur et à mesure que les lignes sont lues: vous y avez un accès direct (via la $line
variable dans l'exemple que j'ai donné), et vous avez également accès aux lignes déjà lues (via le tableau ${my_array[@]}
dans l'exemple que j'ai donné).
Notez que cela mapfile
fournit un moyen d'avoir un rappel évalué sur chaque ligne lue, et en fait vous pouvez même lui dire de n'appeler ce rappel que toutes les N lignes lues; jetez un oeil à help mapfile
et les options -C
et -c
là - dedans. (Mon opinion à ce sujet est que c'est un peu maladroit, mais peut être utilisé parfois si vous n'avez que des choses simples à faire - je ne comprends pas vraiment pourquoi cela a même été implémenté en premier lieu!).
Maintenant, je vais vous dire pourquoi la méthode suivante:
my_array=( $( my_command) )
est cassé lorsqu'il y a des espaces:
$ # 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!
Ensuite, certaines personnes recommanderont d'utiliser IFS=$'\n'
pour le réparer:
$ 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!
Mais maintenant, utilisons une autre commande, avec des 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?
C'est parce que j'ai un fichier appelé t
dans le répertoire courant ... et ce nom de fichier correspond au glob [three four]
... à ce stade, certaines personnes recommanderaient d'utiliser set -f
pour désactiver le globbing: mais regardez-le: vous devez changer IFS
et utiliser set -f
pour pouvoir réparer un technique cassée (et vous ne la réparez même pas vraiment)! en faisant cela, nous luttons vraiment contre le shell, pas avec le 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]")'
ici nous travaillons avec le shell!