155 votes

Tableaux associatifs dans les scripts du shell

Nous avons besoin d'un script qui simule des tableaux associatifs ou une structure de données de type carte pour les scripts Shell, quelqu'un ?

2 votes

202voto

Brian Campbell Points 101107

Une autre option, si la portabilité n'est pas votre principale préoccupation, est d'utiliser des tableaux associatifs qui sont intégrés au shell. Cela devrait fonctionner dans bash 4.0 (disponible maintenant sur la plupart des distros majeures, mais pas sur OS X à moins que vous ne l'installiez vous-même), ksh et zsh :

declare -A newmap
newmap[name]="Irfan Zulfiqar"
newmap[designation]=SSE
newmap[company]="My Own Company"

echo ${newmap[company]}
echo ${newmap[name]}

Selon le shell, vous devrez peut-être faire un typeset -A newmap au lieu de declare -A newmap Dans certains cas, cela n'est pas nécessaire du tout.

0 votes

Merci pour votre réponse, je pense que c'est la meilleure façon de faire pour ceux qui utilisent bash 4.0 ou plus.

0 votes

J'ajouterais un petit truc pour m'assurer que BASH_VERSION est défini, et >= 4. Et oui, BASH 4 est vraiment, vraiment cool !

0 votes

J'utilise quelque chose comme ça. Quelle est la meilleure façon de "rattraper" l'erreur lorsque l'index/le sous-index du tableau n'existe pas ? Par exemple, que se passe-t-il si je prends l'indice comme option de la ligne de commande, et que l'utilisateur fait une faute de frappe et entre "designatio" ? Je reçois une erreur "mauvais indice de tableau" mais je ne sais pas comment valider l'entrée au moment de la consultation du tableau, si c'est possible ?

120voto

Bubnoff Points 1236

Un autre 4 voies sans casse.

#!/bin/bash

# A pretend Python dictionary with bash 3 
ARRAY=( "cow:moo"
        "dinosaur:roar"
        "bird:chirp"
        "bash:rock" )

for animal in "${ARRAY[@]}" ; do
    KEY=${animal%%:*}
    VALUE=${animal#*:}
    printf "%s likes to %s.\n" "$KEY" "$VALUE"
done

echo -e "${ARRAY[1]%%:*} is an extinct animal which likes to ${ARRAY[1]#*:}\n"

Vous pourriez y ajouter une instruction if pour la recherche. if [[ $var =~ /blah/ ]]. ou autre.

3 votes

Cette méthode est bonne lorsque vous ne disposez pas de Bash 4 en effet. Mais je pense que la ligne qui récupère la VALUE serait plus sûre de cette façon : VALUE=${animal#*:}. Avec un seul caractère #, la correspondance s'arrête au premier " :". Cela permet aux valeurs de contenir des " :" également.

0 votes

@Ced-le-pingouin ~ C'est un excellent point ! Je ne l'avais pas compris. J'ai modifié mon message pour tenir compte des améliorations que vous avez suggérées.

1 votes

C'est une émulation assez bricolée des tableaux associatifs utilisant la substitution de paramètres BASH. Le param-sub "key" substitue tout avant le deux-points et le modèle de valeur substitue tout après le colon. C'est similaire à une correspondance joker regex. Donc PAS un vrai tableau associatif. Ce n'est pas recommandé, sauf si vous avez besoin d'un moyen facile à comprendre pour faire des fonctionnalités de type hachage/tableau associatif dans BASH 3 ou moins. Mais ça marche ! Plus d'informations ici : tldp.org/LDP/abs/html/parameter-substitution.html#PSOREX2

38voto

Brian Campbell Points 101107

Je pense que vous devez prendre du recul et réfléchir à ce qu'est réellement une carte, ou un tableau associatif. Il s'agit simplement d'un moyen de stocker une valeur pour une clé donnée, et de récupérer cette valeur rapidement et efficacement. Vous pouvez également vouloir être capable d'itérer sur les clés pour récupérer chaque paire clé-valeur, ou supprimer les clés et leurs valeurs associées.

Maintenant, pensez à une structure de données que vous utilisez tout le temps en script shell, et même juste dans le shell sans écrire un script, qui a ces propriétés. Vous ne savez pas ? C'est le système de fichiers.

En réalité, tout ce dont vous avez besoin pour avoir un tableau associatif en programmation shell est un répertoire temporaire. mktemp -d est votre constructeur de tableau associatif :

prefix=$(basename -- "$0")
map=$(mktemp -dt ${prefix})
echo >${map}/key somevalue
value=$(cat ${map}/key)

Si vous n'avez pas envie d'utiliser echo y cat vous pouvez toujours écrire de petits wrappers ; ceux-ci sont calqués sur ceux d'Irfan, bien qu'ils se contentent d'afficher la valeur au lieu de définir des variables arbitraires, comme par exemple $value :

#!/bin/sh

prefix=$(basename -- "$0")
mapdir=$(mktemp -dt ${prefix})
trap 'rm -r ${mapdir}' EXIT

put() {
  [ "$#" != 3 ] && exit 1
  mapname=$1; key=$2; value=$3
  [ -d "${mapdir}/${mapname}" ] || mkdir "${mapdir}/${mapname}"
  echo $value >"${mapdir}/${mapname}/${key}"
}

get() {
  [ "$#" != 2 ] && exit 1
  mapname=$1; key=$2
  cat "${mapdir}/${mapname}/${key}"
}

put "newMap" "name" "Irfan Zulfiqar"
put "newMap" "designation" "SSE"
put "newMap" "company" "My Own Company"

value=$(get "newMap" "company")
echo $value

value=$(get "newMap" "name")
echo $value

modifier : Cette approche est en fait assez rapide que la recherche linéaire utilisant sed suggérée par le questionneur, ainsi que plus robuste (elle permet aux clés et aux valeurs de contenir -, =, espace, qnd ":SP :"). Le fait qu'elle utilise le système de fichiers ne la rend pas lente ; ces fichiers ne sont en fait jamais garantis d'être écrits sur le disque à moins que vous n'appeliez sync ; pour les fichiers temporaires de ce type dont la durée de vie est courte, il n'est pas improbable que beaucoup d'entre eux ne soient jamais écrits sur le disque.

J'ai fait quelques benchmarks du code d'Irfan, de la modification du code d'Irfan par Jerry, et de mon code, en utilisant le programme pilote suivant :

#!/bin/sh

mapimpl=$1
numkeys=$2
numvals=$3

. ./${mapimpl}.sh    #/ <- fix broken stack overflow syntax highlighting

for (( i = 0 ; $i < $numkeys ; i += 1 ))
do
    for (( j = 0 ; $j < $numvals ; j += 1 ))
    do
        put "newMap" "key$i" "value$j"
        get "newMap" "key$i"
    done
done

Les résultats :

    $ time ./driver.sh irfan 10 5

    real    0m0.975s
    user    0m0.280s
    sys     0m0.691s

    $ time ./driver.sh brian 10 5

    real    0m0.226s
    user    0m0.057s
    sys     0m0.123s

    $ time ./driver.sh jerry 10 5

    real    0m0.706s
    user    0m0.228s
    sys     0m0.530s

    $ time ./driver.sh irfan 100 5

    real    0m10.633s
    user    0m4.366s
    sys     0m7.127s

    $ time ./driver.sh brian 100 5

    real    0m1.682s
    user    0m0.546s
    sys     0m1.082s

    $ time ./driver.sh jerry 100 5

    real    0m9.315s
    user    0m4.565s
    sys     0m5.446s

    $ time ./driver.sh irfan 10 500

    real    1m46.197s
    user    0m44.869s
    sys     1m12.282s

    $ time ./driver.sh brian 10 500

    real    0m16.003s
    user    0m5.135s
    sys     0m10.396s

    $ time ./driver.sh jerry 10 500

    real    1m24.414s
    user    0m39.696s
    sys     0m54.834s

    $ time ./driver.sh irfan 1000 5

    real    4m25.145s
    user    3m17.286s
    sys     1m21.490s

    $ time ./driver.sh brian 1000 5

    real    0m19.442s
    user    0m5.287s
    sys     0m10.751s

    $ time ./driver.sh jerry 1000 5

    real    5m29.136s
    user    4m48.926s
    sys     0m59.336s

3 votes

Je ne pense pas que vous devriez utiliser le système de fichiers pour les cartes, cela revient à utiliser l'IO pour quelque chose que vous pouvez faire assez rapidement en mémoire.

10 votes

Les fichiers ne seront pas nécessairement écrits sur le disque ; à moins que vous n'appeliez sync, le système d'exploitation peut simplement les laisser en mémoire. Votre code fait appel à sed et effectue plusieurs recherches linéaires, qui sont toutes très lentes. J'ai fait quelques tests rapides, et ma version est 5 à 35 fois plus rapide.

0 votes

D'un autre côté, les tableaux natifs de bash4 sont une meilleure approche et dans bash3, vous pouvez toujours tout garder hors du disque sans bifurquer en utilisant declare et indirection.

20voto

Jerry Penner Points 583

À ajouter à Réponse d'Irfan voici une version plus courte et plus rapide de get() puisqu'il ne nécessite aucune itération sur le contenu de la carte :

get() {
    mapName=$1; key=$2

    map=${!mapName}
    value="$(echo $map |sed -e "s/.*--${key}=\([^ ]*\).*/\1/" -e 's/:SP:/ /g' )"
}

18 votes

Forker un sous-shell et sed n'est pas optimal. Bash4 supporte cela nativement et bash3 a de meilleures alternatives.

15voto

DigitalRoss Points 80400
hput () {
  eval hash"$1"='$2'
}

hget () {
  eval echo '${hash'"$1"'#hash}'
}
hput France Paris
hput Netherlands Amsterdam
hput Spain Madrid
echo `hget France` and `hget Netherlands` and `hget Spain`

$ sh hash.sh
Paris and Amsterdam and Madrid

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X