Travailler avec des tableaux dans bash

Les programmeurs utilisent régulièrement bash pour résoudre de nombreuses tâches liées au développement de logiciels. Dans le même temps, les tableaux bash sont souvent considérés comme l'une des fonctionnalités les plus incompréhensibles de ce shell (probablement, les tableaux sont en deuxième position derrière les expressions régulières à cet égard). L'auteur du matériel, dont nous publions la traduction aujourd'hui, invite tout le monde dans le monde merveilleux des tableaux bash qui, si vous vous habituez à leur syntaxe inhabituelle, peut apporter de nombreux avantages.

image

Le vrai défi que les tableaux bash sont utiles


Écrire sur bash est controversé. Le fait est que les articles sur bash se transforment souvent en guides d'utilisation qui sont consacrés à des histoires sur les fonctionnalités syntaxiques des commandes en question. Cet article est écrit différemment, nous espérons que vous ne le trouverez pas dans le prochain "manuel d'utilisation".

Compte tenu de ce qui précède, imaginez un scénario réel pour l'utilisation de tableaux dans bash. Supposons que vous soyez confronté à la tâche d'évaluer et d'optimiser un utilitaire à partir d'un nouvel ensemble interne d'outils utilisés dans votre entreprise. Dans la première étape de cette étude, vous devez le tester avec différents ensembles de paramètres. Le test vise à étudier le comportement d'un nouvel ensemble d'outils lorsqu'ils utilisent un nombre différent de threads. Pour simplifier la présentation, nous supposons que la «boîte à outils» est une «boîte noire» compilée à partir du code C ++. Lors de son utilisation, le seul paramètre que nous pouvons influencer est le nombre de threads réservés au traitement des données. L'appel du système sous enquête à partir de la ligne de commande ressemble à ceci:

./pipeline --threads 4 

Les bases


Tout d'abord, nous déclarons un tableau contenant les valeurs du paramètre --threads avec lequel nous voulons tester le système. Ce tableau ressemble à ceci:

 allThreads=(1 2 4 8 16 32 64 128) 

Dans cet exemple, tous les éléments sont des nombres, mais en fait, dans les tableaux bash, vous pouvez stocker à la fois des nombres et des chaînes. Par exemple, la déclaration d'un tel tableau est tout à fait acceptable:

 myArray=(1 2 "three" 4 "five") 

Comme pour les autres variables bash, assurez-vous qu'il n'y a pas d'espace autour du signe = . Sinon, bash considérera le nom de la variable comme le nom du programme dont il a besoin pour exécuter, et = son premier argument!

Maintenant que nous avons initialisé le tableau, extrayons-en quelques éléments. Ici, vous pouvez remarquer, par exemple, que la echo $allThreads ne sortira que le premier élément du tableau.

Afin de comprendre les raisons de ce comportement, s'écartons un peu des tableaux et rappelons comment travailler avec des variables dans bash. Prenons l'exemple suivant:

 type="article" echo "Found 42 $type" 

Supposons que vous ayez une variable $type qui contient une chaîne qui représente un nom. Après ce mot, ajoutez la lettre s . Cependant, vous ne pouvez pas simplement ajouter cette lettre à la fin du nom de la variable, car cela transformera la commande pour accéder à la variable en $types , c'est-à-dire que nous travaillerons avec une variable complètement différente. Dans cette situation, vous pouvez utiliser une construction comme echo "Found 42 "$type"s" . Mais il est préférable de résoudre ce problème en utilisant des accolades: echo "Found 42 ${type}s" , ce qui nous permet de dire à bash où le nom de variable commence et se termine (fait intéressant, la même syntaxe est utilisée dans JavaScript ES6 pour incorporer des variables dans les expressions dans les chaînes de modèle ).

Revenons maintenant aux tableaux. Il s'avère que bien que les accolades ne soient généralement pas nécessaires lorsque vous travaillez avec des variables, elles sont nécessaires pour travailler avec des tableaux. Ils vous permettent de définir des index pour accéder aux éléments du tableau. Par exemple, une commande de la forme echo ${allThreads[1]} affichera le deuxième élément du tableau. Si vous oubliez les accolades dans la construction ci-dessus, bash percevra [1] comme une chaîne et traitera ce qui se passe en conséquence.

Comme vous pouvez le voir, les tableaux en bash ont une syntaxe étrange, mais en eux, au moins, la numérotation des éléments commence à partir de zéro. Cela les rend similaires aux tableaux de nombreux autres langages de programmation.

Façons d'accéder aux éléments du tableau


Dans l'exemple ci-dessus, nous avons utilisé des indices entiers dans des tableaux qui sont spécifiés explicitement. Considérez maintenant deux autres façons de travailler avec des tableaux.

La première méthode est applicable si nous avons besoin de l'élément $i ème du tableau, où $i est une variable contenant l'indice de l'élément de tableau souhaité. Vous pouvez extraire cet élément du tableau à l'aide d'une construction de la forme echo ${allThreads[$i]} .

La deuxième méthode vous permet d'afficher tous les éléments du tableau. Il consiste à remplacer l'index numérique par le symbole @ (il peut être interprété comme une commande pointant sur tous les éléments du tableau). Cela ressemble à ceci: echo ${allThreads[@]} .

Itération sur les éléments du tableau en boucles


Les principes ci-dessus de travailler avec des éléments de tableau nous seront utiles pour résoudre le problème d'énumération des éléments de tableau. Dans notre cas, cela signifie lancer la commande de pipeline étude avec chacune des valeurs, qui symbolise le nombre de threads et est stockée dans un tableau. Cela ressemble à ceci:

 for t in ${allThreads[@]}; do ./pipeline --threads $t done 

Énumération des indices de tableau dans les boucles


Considérons maintenant une approche légèrement différente du tri des tableaux. Au lieu d'itérer sur les éléments, nous pouvons itérer sur les index du tableau:

 for i in ${!allThreads[@]}; do ./pipeline --threads ${allThreads[$i]} done 

Analysons ce qui se passe ici. Comme nous l'avons déjà vu, une construction de la forme ${allThreads[@]} représente tous les éléments du tableau. Lorsque nous ajoutons un point d'exclamation ici, nous transformons cette construction en ${!allThreads[@]} , ce qui conduit au fait qu'elle renvoie les index du tableau (de 0 à 7 dans notre cas).

En d'autres termes, la boucle for tous les index du tableau représentés comme la variable $i , et dans le corps de la boucle, les éléments du tableau, qui servent de valeurs au paramètre --thread , sont --thread à l'aide de la construction ${allThreads[$i]} .

La lecture de ce code est plus difficile que celle de l'exemple précédent. Par conséquent, la question se pose de savoir à quoi servent toutes ces difficultés. Et nous en avons besoin car dans certaines situations, lors du traitement de tableaux en boucles, vous devez connaître à la fois les indices et les valeurs des éléments. Par exemple, si vous devez ignorer le premier élément d'un tableau, l'itération sur les indices nous évitera, par exemple, la nécessité de créer une variable supplémentaire et de l'incrémenter en boucle pour travailler avec les éléments du tableau.

Remplissage des matrices


Jusqu'à présent, nous avons exploré le système en appelant la commande pipeline et en lui passant chacune des valeurs du paramètre --threads nous intéressent. Supposons maintenant que cette commande donne la durée d'un certain processus en secondes. Nous aimerions intercepter les données qui lui sont retournées à chaque itération et les enregistrer dans un autre tableau. Cela nous donnera l'occasion de travailler avec les données stockées une fois tous les tests terminés.

Constructions de syntaxe utiles


Avant de parler de la façon d'ajouter des données aux tableaux, regardons quelques constructions de syntaxe utiles. Pour commencer, nous avons besoin d'un mécanisme pour obtenir la sortie de données par les commandes bash. Afin de capturer la sortie d'une commande, vous devez utiliser la construction suivante:

 output=$( ./my_script.sh ) 

Après avoir exécuté cette commande, ce que myscript.sh $output sera stocké dans la variable $output .

La deuxième construction, qui sera très utile très prochainement, nous permet d'attacher de nouvelles données aux tableaux. Cela ressemble à ceci:

 myArray+=( "newElement1" "newElement2" ) 

Résolution de problèmes


Maintenant, si nous rassemblons tout ce que nous venons d'apprendre, nous pouvons créer un script pour tester le système qui exécute une commande avec chacune des valeurs de paramètre du tableau et stocke dans l'autre tableau ce que cette commande affiche.

 allThreads=(1 2 4 8 16 32 64 128) allRuntimes=() for t in ${allThreads[@]}; do runtime=$(./pipeline --threads $t) allRuntimes+=( $runtime ) done 

Et ensuite?


Nous venons d'examiner comment utiliser les tableaux bash pour parcourir les paramètres utilisés lors du démarrage d'un programme et pour enregistrer les données que ce programme renvoie. Cependant, les options d'utilisation des tableaux ne sont pas limitées à ce scénario. Voici quelques autres exemples.

Alertes de problème


Dans ce scénario, nous allons examiner une application qui se décompose en modules. Chacun de ces modules possède son propre fichier journal. Nous pouvons écrire un script de tâche cron qui, si des problèmes sont détectés dans le fichier journal correspondant, notifiera par email la personne responsable de chacun des modules:

 #  -    logPaths=("api.log" "auth.log" "jenkins.log" "data.log") logEmails=("jay@email" "emma@email" "jon@email" "sophia@email") #         for i in ${!logPaths[@]}; do log=${logPaths[$i]} stakeholder=${logEmails[$i]} numErrors=$( tail -n 100 "$log" | grep "ERROR" | wc -l ) #       5  if [[ "$numErrors" -gt 5 ]]; then   emailRecipient="$stakeholder"   emailSubject="WARNING: ${log} showing unusual levels of errors"   emailBody="${numErrors} errors found in log ${log}"   echo "$emailBody" | mailx -s "$emailSubject" "$emailRecipient" fi done 

Demandes d'API


Supposons que vous souhaitiez collecter des informations sur les commentaires des utilisateurs sur vos publications sur Medium. Comme nous n'avons pas d'accès direct à la base de données de ce site, nous ne discuterons pas des requêtes SQL. Cependant, vous pouvez utiliser différentes API pour accéder à ce type de données.

Afin d'éviter de longues conversations sur l'authentification et les jetons, nous utiliserons, en tant que point de terminaison, le service de test d'API public JSONPlaceholder . Après avoir reçu une publication du service et extrait les données de son code aux adresses e-mail des commentateurs, nous pouvons mettre ces données dans un tableau:

 endpoint="https://jsonplaceholder.typicode.com/comments" allEmails=() #   10  for postId in {1..10}; do #    API       response=$(curl "${endpoint}?postId=${postId}") #  jq   JSON       allEmails+=( $( jq '.[].email' <<< "$response" ) ) done 

Veuillez noter que l'outil jq est utilisé ici, ce qui permet d'analyser JSON sur la ligne de commande. Nous n'entrerons pas dans les détails de l'utilisation de jq si vous êtes intéressé par cet outil - consultez la documentation correspondante.

Bash ou Python?


Tableaux - une fonctionnalité utile et elle est disponible non seulement dans bash. Celui qui écrit des scripts pour la ligne de commande peut avoir une question logique sur les situations dans lesquelles il vaut la peine d'utiliser bash et dans quel cas, par exemple, Python.

À mon avis, la réponse à cette question réside dans combien le programmeur dépend d'une technologie particulière. Disons que si le problème peut être résolu directement sur la ligne de commande, rien n'empêche l'utilisation de bash. Cependant, dans le cas où, par exemple, le script qui vous intéresse fait partie d'un projet écrit en Python, vous pouvez bien utiliser Python.

Par exemple, pour résoudre le problème considéré ici, vous pouvez utiliser un script écrit en Python, cependant, cela reviendra à écrire des wrappers pour Python pour bash:

 import subprocess all_threads = [1, 2, 4, 8, 16, 32, 64, 128] all_runtimes = [] #         for t in all_threads: cmd = './pipeline --threads {}'.format(t) #   subprocess   ,    p = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True) output = p.communicate()[0] all_runtimes.append(output) 

Peut-être que la solution à ce problème avec bash, sans impliquer d'autres technologies, est plus courte et plus compréhensible, et ici vous pouvez complètement vous passer de Python.

Résumé


Dans ce document, nous avons analysé de nombreux modèles utilisés pour travailler avec des tableaux. Voici un tableau où vous trouverez ce que nous avons examiné et quelque chose de nouveau.
Construction de syntaxeLa description
arr=()Créer un tableau vide
arr=(1 2 3)Initialisation du tableau
${arr[2]}Obtention du troisième élément d'un tableau
${arr[@]}Obtention de tous les éléments du tableau
${!arr[@]}Obtention d'indices de tableau
${#arr[@]}Calcul de la taille du tableau
arr[0]=3Écraser le premier élément d'un tableau
arr+=(4)Rejoindre un tableau de valeurs
str=$(ls)Enregistrement de la sortie de la ls sous forme de chaîne
arr=( $(ls) )Enregistrement de la sortie de la ls tant que tableau de noms de fichiers
${arr[@]:s:n}Obtention d'éléments de tableau d'un élément avec index s à un élément avec index s+(n-1)

À première vue, les tableaux bash peuvent sembler plutôt étranges, mais les possibilités qu'ils offrent en valent la peine pour faire face à ces bizarreries. Nous pensons qu'ayant maîtrisé les tableaux bash, vous les utiliserez assez souvent. Il est facile d'imaginer d'innombrables scénarios dans lesquels ces tableaux peuvent être utiles.

Chers lecteurs! Si vous avez des exemples intéressants d'utilisation de tableaux dans des scripts bash, partagez-les.

Source: https://habr.com/ru/post/fr413725/


All Articles