Si vous devez stocker la sortie et que vous voulez un tableau, mapfile
c'est facile.
Tout d'abord, déterminez si vous avez besoin de stocker la sortie de votre commande. Si vous ne le faites pas, exécutez simplement la commande.
Si vous décidez que vous souhaitez lire la sortie d'une commande sous forme de tableau de lignes, il est vrai qu'une façon de le faire consiste à désactiver la globalisation, à définir la IFS
répartition sur les lignes, à utiliser la substitution de commandes à l'intérieur de la (
)
syntaxe de création du tableau et à la réinitialiser IFS
par la suite, ce qui est plus complexe qu'il n'y paraît. La réponse de terdon couvre une partie de cette approche. Mais je vous suggère d'utiliser à mapfile
la place la commande intégrée du shell Bash pour lire le texte sous forme de tableau de lignes. Voici un cas simple où vous lisez un fichier:
mapfile < filename
Cela lit les lignes dans un tableau appelé MAPFILE
. Sans la redirection d'entrée , vous liriez à partir de l' entrée standard du shell (généralement votre terminal) au lieu du fichier nommé par . Pour lire dans un tableau autre que celui par défaut , passez son nom. Par exemple, cela lit dans le tableau :< filename
filename
MAPFILE
lines
mapfile lines < filename
Un autre comportement par défaut que vous pourriez choisir de changer est que les caractères de nouvelle ligne à la fin des lignes sont laissés en place; ils apparaissent comme le dernier caractère de chaque élément du tableau (sauf si l'entrée s'est terminée sans caractère de nouvelle ligne, auquel cas le dernier élément n'en a pas). Pour couper ces nouvelles lignes afin qu'elles n'apparaissent pas dans le tableau, passez l' -t
option à mapfile
. Par exemple, cela lit le tableau records
et n'écrit pas de caractères de fin de ligne dans ses éléments de tableau:
mapfile -t records < filename
Vous pouvez également utiliser -t
sans passer de nom de tableau; c'est-à-dire qu'il fonctionne aussi avec un nom de tableau implicite de MAPFILE
.
Le mapfile
shell intégré prend en charge d'autres options, et il peut également être appelé en tant que readarray
. Exécutez help mapfile
(et help readarray
) pour plus de détails.
Mais vous ne voulez pas lire à partir d'un fichier, vous voulez lire à partir de la sortie d'une commande. Pour ce faire, utilisez la substitution de processus . Cette commande lit les lignes de la commande some-command
avec arguments...
comme arguments de ligne de commande et les place dans le mapfile
tableau par défaut de MAPFILE
, avec leurs caractères de fin de ligne supprimés:
mapfile -t < <(some-command arguments...)
La substitution de processus remplace par un nom de fichier réel à partir duquel la sortie de l'exécution peut être lue. Le fichier est un canal nommé plutôt qu'un fichier normal et, sur Ubuntu, il sera nommé comme (parfois avec un autre numéro que ), mais vous n'avez pas besoin de vous préoccuper des détails, car le shell s'en charge tous dans les coulisses.<(some-command arguments...)
some-command arguments...
/dev/fd/63
63
Vous pourriez penser que vous pourriez renoncer à la substitution de processus en utilisant à la place, mais cela ne fonctionnera pas car, lorsque vous avez un pipeline de plusieurs commandes séparées par , Bash s'exécute exécute toutes les commandes en sous-coquilles . Ainsi , les deux et courir dans leurs propres environnements , initialisées à partir mais distincte de l'environnement de la coquille dans laquelle vous exécutez le pipeline. Dans le sous-shell où s'exécute, le tableau est rempli, mais ce tableau est rejeté à la fin de la commande. n'est jamais créé ou modifié pour l'appelant.some-command arguments... | mapfile -t
|
some-command arguments...
mapfile -t
mapfile -t
MAPFILE
MAPFILE
Voici à quoi ressemble l'exemple de la réponse de terdon , si vous utilisez mapfile
:
mapfile -t dirs < <(find . -type d)
for d in "${dirs[@]}"; do
echo "DIR: $d"
done
C'est ça. Vous n'avez pas besoin de vérifier si elle a IFS
été définie , de savoir si elle a été définie et avec quelle valeur, de la définir sur une nouvelle ligne, puis de la réinitialiser ou de la réinitialiser plus tard. Vous ne devez pas désactiver globbing (par exemple, set -f
) - ce qui est vraiment nécessaire si vous voulez utiliser cette méthode au sérieux, puisque les noms de fichiers peuvent contenir *
, ?
et [
--then réactiviez ( set +f
) par la suite.
Vous pouvez également remplacer cette boucle particulière entièrement par une seule printf
commande - bien que ce ne soit pas réellement un avantage mapfile
, car vous pouvez le faire que vous utilisiez mapfile
une autre méthode pour remplir le tableau. Voici une version plus courte:
mapfile -t < <(find . -type d)
printf 'DIR: %s\n' "${MAPFILE[@]}"
Il est important de garder à l'esprit que, comme le mentionne Terdon , l'exploitation ligne par ligne n'est pas toujours appropriée et ne fonctionnera pas correctement avec les noms de fichiers contenant des sauts de ligne. Je déconseille de nommer les fichiers de cette façon, mais cela peut arriver, y compris par accident.
Il n'y a pas vraiment de solution unique.
Vous avez demandé «une commande générique pour tous les scénarios» et l'approche consistant à utiliser mapfile
quelque peu l'approche de cet objectif, mais je vous invite à reconsidérer vos besoins.
La tâche illustrée ci-dessus est mieux réalisée avec une seule find
commande:
find . -type d -printf 'DIR: %p\n'
Vous pouvez également utiliser une commande externe comme sed
ajouter DIR:
au début de chaque ligne. C'est sans doute quelque peu moche, et contrairement à cette commande find, elle ajoutera des «préfixes» supplémentaires dans les noms de fichiers qui contiennent des nouvelles lignes, mais cela fonctionne indépendamment de son entrée, donc il répond en quelque sorte à vos besoins et il est toujours préférable de lire la sortie dans un variable ou tableau :
find . -type d | sed 's/^/DIR: /'
Si vous devez répertorier et également effectuer une action sur chaque répertoire trouvé, telle que l'exécution some-command
et la transmission du chemin du répertoire en tant qu'argument, find
vous pouvez également le faire:
find . -type d -print -exec some-command {} \;
Comme autre exemple, revenons à la tâche générale d'ajouter un préfixe à chaque ligne. Supposons que je veuille voir la sortie de help mapfile
mais numéroter les lignes. Je n'utiliserais pas réellement mapfile
pour cela, ni aucune autre méthode qui le lit dans une variable shell ou un tableau shell. Supposons help mapfile | cat -n
que je ne donne pas la mise en forme que je veux, je peux utiliser awk
:
help mapfile | awk '{ printf "%3d: %s\n", NR, $0 }'
La lecture de toutes les sorties d'une commande dans une seule variable ou un seul tableau est parfois utile et appropriée, mais elle présente des inconvénients majeurs. Non seulement vous devez faire face à la complexité supplémentaire de l'utilisation de votre shell lorsqu'une commande existante ou une combinaison de commandes existantes peut déjà faire ce dont vous avez besoin ou mieux, mais la sortie entière de la commande doit être stockée en mémoire. Parfois, vous savez peut-être que ce n'est pas un problème, mais parfois vous traitez un fichier volumineux.
Une alternative qui est couramment tentée - et parfois bien faite - consiste à lire l'entrée ligne par ligne read -r
dans une boucle. Si vous n'avez pas besoin de stocker les lignes précédentes lorsque vous travaillez sur des lignes ultérieures et que vous devez utiliser une entrée longue, cela peut être mieux que mapfile
. Mais cela devrait également être évité dans les cas où vous pouvez simplement le rediriger vers une commande qui peut faire le travail, ce qui est la plupart des cas .