Question Quelle est la meilleure façon de transformer une chaîne en un tableau associatif


Par souci d'exercice, j'ai écrit les quelques lignes ci-dessous dans un script bash pour transformer une entrée de publication HTTP en un tableau associatif. Suite à mon apprentissage, je me demandais s'il y avait des façons différentes, voire plus élégantes de le faire. Je veux dire, je sais qu'il y a différentes manières. Je me demande quels sont ceux qui existent et quels sont les avantages et les inconvénients de chaque méthode.

Note: J'utilise post comme entrée ici. L'exercice consiste cependant à créer un tableau à partir d'une chaîne de paires nom / valeur multiples. L'entrée peut provenir de n'importe quel fichier ou autre source d'entrée.

Note2: la chaîne que je traite pourrait ressembler à ceci: name=myName&age=myAge. Il y a donc 2 délimiteurs. Un séparant les paires nom / valeur (&) et l'autre séparant la valeur de son nom (=).

#!/bin/bash
read post;
declare -A myArr;
IFS_bck=$IFS;
IFS="=";
while read name value; do
  myArr[$name]=$value;
done < <(sed -rn -e 's/&/\n/g p' <<<"$post");
IFS=$IFS_bck;

P.S. Je ne veux pas commencer une guerre de religion. Je me demande simplement comment vous feriez cela et pourquoi vous choisiriez votre proposition plutôt que la mienne.


1
2018-03-09 12:18


origine


Vous voulez juste une paire clé-valeur dans le tableau? - heemayl
Ce n'est pas à propos d'Ubuntu - vous feriez mieux de demander sur stackoverflow.com - Carl H
@CarlH Ceci est bien sûr sur le sujet ici .. - heemayl
Petite question cependant, vous avez dit multiple name/value pair string. L'exemple montre deux paires, mais cela pourrait-il être aussi name=myName&age=myAge&married=true ? Si tel est le cas, vous avez besoin de quelque chose d'autre qu'un tableau associatif. De même, une liste chaînée serait préférable. Et probablement une langue différente - bash n'est pas le bon outil pour cela - Sergiy Kolodyazhnyy
"… Comment tu ferais ça et pourquoi tu choisirais…" Le problème est que vous nous avez donné un problème XY. Vous avez déjà choisi un tableau associatif comme solution et nous vous demandons comment en construire un. Selon le problème réel, le reste d'entre nous pourrait même ne pas utiliser de tableau associatif. En ce qui concerne les enregistrements et les champs, awk balaie le sol avec d’autres outils, ce qui explique pourquoi Si vous voulez vraiment apprendre cela, commencez par le problème réel. - muru


Réponses:


Je le ferais simplement en utilisant bash lui-même:

#!/bin/bash
read -e -p 'Enter the string: ' post
declare -A myArr

while IFS='&' read name age; do
    myArr["${name##*=}"]="${age##*=}"
done <<<"$post"

printf 'NAME: %s, AGE: %s\n' "${!myArr[@]}" "${myArr[@]}"

Voici ce que je reçois dans la sortie:

Enter the string: name=Foo Bar&age=40
NAME: Foo Bar, AGE: 40
  • La chaîne d'entrée est spinte sur & en utilisant IFS variable environnementale

  • Les valeurs de nom et d'âge sont analysées à l'aide du modèle d'extension de paramètre ${var##*=}

  • Vous pouvez obtenir toutes les clés en utilisant ${!myArr[@]} et toutes les valeurs en utilisant ${!myArr[@]}

En pratique, je ne pense pas que vous feriez simplement un tableau associatif d'un élément. Si vous avez plusieurs éléments, remplacez le dernier printf ligne avec un simple for construire pour boucler les clés:

for key in "${!myArr[@]}"; do
    printf 'NAME: %s, AGE: %s\n' "$key" "${myArr["$key"]}"
done

2
2018-03-09 14:13





Ce que vous avez n'a pas l'air mal, mais ni Bash ni sed jouer trop bien avec la division sur plusieurs délimiteurs; Personnellement, j'utiliserais AWK et simplifierais un peu le script (en utilisant AWK IFS devient redondant; remarquez que vous n'avez pas besoin de points-virgules à la fin des instructions dans Bash):

#!/bin/bash
read post
declare -A myArr
while read name value; do
    myArr[$name]=$value
done < <(<<<"$post" awk -F= '{print $1,$2}' RS='&|\n')

La commande AWK:

  • Lit $post;
  • Divise les enregistrements sur des séquences d'amperstands / newlines (la division sur des nouvelles lignes est un truc pour empêcher while boucle de l'échec sur le dernier enregistrement vide);
  • Divise les champs sur des séquences de signes égaux;
  • Imprime les champs séparés par le séparateur par défaut, un espace.
% cat script.sh 
#!/bin/bash
read post
declare -A myArr
while read name value; do
    myArr[$name]=$value
done < <(<<<"$post" awk -F= '{print $1,$2}' RS='&|\n')
printf '%s %s\n' "${myArr[name]}" "${myArr[age]}"
% bash script.sh 
name=myName&age=myAge
myName myAge
% 

3
2018-03-09 13:03



Kossy, pourquoi pas juste while IFS=\= .... et se débarrasser des analyses inutiles et de la substitution de processus .. également OP devrait savoir qu'ils doivent citer leurs variables..à tout le moins, vous avez toujours mon +1. - heemayl
@heemayl Je me souviens avoir vu un commentaire mentionnant ici read's -d option. Comment se fait-il que ni l'un ni l'autre ne l'ait utilisé et que le commentaire a disparu? - muru
@muru duh..need coffee..kos, allez-y..je suis sorti .. - heemayl
@muru C’était le mien, mais ce n’est pas viable car ils lisent avec read; si j'utilise & comme délimiteur je les force aussi à taper & à la fin. - kos
(S'ils copient-collent. Sinon, je les force à lire une paire clé / valeur unique). - kos


Encore une autre manière, en utilisant juste IFS:

#!/bin/bash
declare -A myArr
IFS='&=' read -a post
for ((i = 0; i < ${#post[@]}; i += 2))
do
    myArr[${post[i]}]=${post[i + 1]}
done
for key in "${!myArr[@]}"
do
    printf "%s: %s\n" "$key" "${myArr[$key]}"
done

read divise la ligne entrante en mots sur tout personnages dans IFS, vous pouvez donc utiliser les deux & et = dans IFS à diviser sur les deux. Étant donné que l’entrée POST a toujours une valeur pour une clé, cela fonctionnera.

Cependant, cette méthode ne peut pas vérifier s'il y a une alternance stricte entre & et =. Ainsi, par exemple, age&myAge=name=myName sera analysé pour que age=myAge et name=myName.


Une note sur IFS

Vous avez sauvegardé IFS et l'a restauré. Mais vous n'avez besoin que de IFS pour read, donc appliquer IFS seulement pour READ:

IFS='...' read ... # or
while IFS='...' read ...; ...

La restauration IFS est difficile, car un non défini IFS et un vide IFS affecter le shell différemment, mais sont les mêmes lorsque vous prenez la valeur de IFS lui-même C'est:

IFS=
IFS_BAK="$IFS"

donnera la même valeur pour IFS_BAKcomme:

unset IFS
IFS_BAK="$IFS"

Un vide IFS est exactement cela: une chaîne vide. Cependant, un non défini IFS fait que le shell se comporte comme si le défaut IFS (espace, tabulation, nouvelle ligne) a été utilisé:

$ foo='a  b  c'
$ printf "|%s|\n" $foo
|a|
|b|
|c|
$ IFS=; printf "|%s|\n" $foo
|a  b  c|
$ unset IFS; printf "|%s|\n" $foo
|a|
|b|
|c|

Donc, si vous vous êtes trouvé avec un IFS non configuré, puis que vous avez essayé l'ancien truc de sauvegarde-restauration-IFS, vous pourriez obtenir des résultats surprenants. Il est préférable de limiter les modifications à IFS être seulement pour les commandes qui en ont besoin.


2
2018-03-09 16:35





Tu as dit:

Il y a donc 2 délimiteurs. Un nom / valeur séparant les paires (&) et l'autre séparant la valeur forme son nom (=).

Eh bien, nous pouvons diviser les paires nom / valeur en utilisant & comme IFS dans des variables, et utilisez la suppression de suffixe / préfixe pour libérer le nom réel et les valeurs d’âge.

$> cat post-parse.sh                                                           
#!/bin/bash
IFS='&' read PAIR1 PAIR2
# if necessary use these as well
# name_key=${PAIR1%%=*}
# age_key=${PAIR2%%=*}
name_val=${PAIR1##*=}
age_val=${PAIR2##*=}
echo $name_val $age_val
$> ./post-parse.sh
name=Serg&age=25
Serg 25
$> 

Vous avez également dit:

L'exercice consiste cependant à créer un tableau à partir d'une chaîne de paires nom / valeur multiples. L'entrée peut provenir de n'importe quel fichier ou autre source d'entrée.

Si nous voulons stocker plusieurs paires clé-valeur, nous pouvons lire ligne par ligne (donc pas besoin d'utiliser sed se débarrasser de \n là-bas), et appliquer le même concept que j'ai montré ci-dessus:

#!/bin/bash
declare -A myArray
while read input_line ; # read input line by line
do
echo $input_line
  IFS='&' read PAIR1 PAIR2 <<< $input_line # split in two with &
  # name_key=${PAIR1%%=*}
  # age_key=${PAIR2%%=*}
  name_val=${PAIR1##*=}
  age_val=${PAIR2##*=}
  myArray[$name_val]=$age_val
done
# print out the array
for key in "${!myArray[@]}" 
do
   echo ${myArray[$key]} is $key
done

Les exemples ci-dessous sont utilisés ici mais ce pourrait être n'importe quoi, même un tuyau. Le point est que la commande de lecture ne se soucie pas de savoir où elle entre.

$> ./post-parse.sh << EOF                                                      
> name=John&age=25                                                             
> name=Jane&age=35                                                             
> EOF
name=John&age=25
name=Jane&age=35
25 is John
35 is Jane

1
2018-03-09 13:55