tableaux dans les scripts Shell Associatif
-
22-08-2019 - |
Question
Nous avions besoin d'un script qui simule des tableaux ou Associatif carte comme structure de données pour le script Shell, tout organisme?
Autres conseils
Une autre option, si la portabilité est pas votre principale préoccupation, est d'utiliser des tableaux associatifs qui sont intégrés à la coque. Cela devrait fonctionner en bash 4.0 (maintenant disponible sur la plupart des distros majeurs, mais pas sur OS X sauf si vous installez vous-même), ksh, zsh et:
declare -A newmap
newmap[name]="Irfan Zulfiqar"
newmap[designation]=SSE
newmap[company]="My Own Company"
echo ${newmap[company]}
echo ${newmap[name]}
En fonction du shell, vous devrez peut-être faire un typeset -A newmap
au lieu de declare -A newmap
, ou dans certains, il ne serait pas nécessaire du tout.
Une autre façon non bash 4.
#!/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 pouvez lancer une instruction if pour la recherche là-dedans aussi. si [[$ var = ~ / bla /]]. ou autre.
Je pense que vous avez besoin de prendre du recul et réfléchir à ce que une carte ou tableau associatif, est vraiment. Tout ce qu'il est est un moyen de stocker une valeur pour une clé donnée, et d'obtenir cette valeur de retour rapidement et efficacement. Vous pouvez également être en mesure de parcourir les clés pour récupérer toutes les valeurs paire de clés, ou supprimer des clés et leurs valeurs associées.
Maintenant, pensez à une structure de données que vous utilisez tout le temps dans les scripts shell, et même juste dans la coquille sans écrire un script, qui a ces propriétés. Stumped? Il est le système de fichiers.
Vraiment, tout ce que vous devez disposer d'un tableau associatif dans la 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 ne vous sentez pas comme l'utilisation echo
et cat
, vous pouvez toujours écrire quelques petits emballages; ceux-ci sont modélisés hors de Irfan de, mais ils la juste valeur de sortie plutôt que de définir des variables arbitraires comme $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 un peu plus rapide que la recherche linéaire avec sed suggéré par le questionneur, ainsi que plus robuste (il permet des clés et des valeurs de contenir -, =, l'espace, QND ": SP:"). Le fait qu'il utilise le système de fichiers ne permet pas ralentir; ces fichiers sont jamais garantis à écrire sur le disque à moins que vous appelez sync
; pour les fichiers temporaires comme celui-ci avec une courte durée de vie, il est assez probable que beaucoup d'entre eux ne seront jamais écrites sur le disque.
Je l'ai fait quelques points de repère du code de Irfan, la modification de Jerry du code de Irfan, et 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
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
Bash4 supporte nativement. Ne pas utiliser grep
ou eval
, ils sont le plus laid des hacks.
Pour une réponse verbeux, détaillée par exemple voir code: https://stackoverflow.com/questions/3467959
####################################################################
# Bash v3 does not support associative arrays
# and we cannot use ksh since all generic scripts are on bash
# Usage: map_put map_name key value
#
function map_put
{
alias "${1}$2"="$3"
}
# map_get map_name key
# @return value
#
function map_get
{
alias "${1}$2" | awk -F"'" '{ print $2; }'
}
# map_keys map_name
# @return map keys
#
function map_keys
{
alias -p | grep $1 | cut -d'=' -f1 | awk -F"$1" '{print $2; }'
}
Exemple:
mapName=$(basename $0)_map_
map_put $mapName "name" "Irfan Zulfiqar"
map_put $mapName "designation" "SSE"
for key in $(map_keys $mapName)
do
echo "$key = $(map_get $mapName $key)
done
Maintenant répondre à cette question.
Après des scripts simulent des tableaux associatifs dans les scripts shell. Son simple et très facile à comprendre.
La carte est rien, mais une chaîne sans fin qui a KeyValuePair enregistré en tant que --name = Irfan --designation = SSE --company = Mon: SP: propre: SP: Société
espaces sont remplacés par ': SP:' pour les valeurs
put() {
if [ "$#" != 3 ]; then exit 1; fi
mapName=$1; key=$2; value=`echo $3 | sed -e "s/ /:SP:/g"`
eval map="\"\$$mapName\""
map="`echo "$map" | sed -e "s/--$key=[^ ]*//g"` --$key=$value"
eval $mapName="\"$map\""
}
get() {
mapName=$1; key=$2; valueFound="false"
eval map=\$$mapName
for keyValuePair in ${map};
do
case "$keyValuePair" in
--$key=*) value=`echo "$keyValuePair" | sed -e 's/^[^=]*=//'`
valueFound="true"
esac
if [ "$valueFound" == "true" ]; then break; fi
done
value=`echo $value | sed -e "s/:SP:/ /g"`
}
put "newMap" "name" "Irfan Zulfiqar"
put "newMap" "designation" "SSE"
put "newMap" "company" "My Own Company"
get "newMap" "company"
echo $value
get "newMap" "name"
echo $value
modifier Juste ajouté une autre méthode pour récupérer toutes les clés
.getKeySet() {
if [ "$#" != 1 ];
then
exit 1;
fi
mapName=$1;
eval map="\"\$$mapName\""
keySet=`
echo $map |
sed -e "s/=[^ ]*//g" -e "s/\([ ]*\)--/\1/g"
`
}
Pour Bash 3, il est un cas particulier qui a une solution simple et agréable:
Si vous ne voulez pas gérer un grand nombre de variables, ou les clés sont tout simplement pas valide identifiants variables, et votre tableau est garanti d'avoir moins de 256 articles , vous pouvez abuser des valeurs de retour de la fonction. Cette solution ne nécessite pas de sous-shell que la valeur est facilement disponible en tant que variable, ni aucune itération de sorte que la performance des cris. En outre, il est très lisible, presque comme la version 4 Bash.
Voici la version la plus simple:
hash_index() {
case $1 in
'foo') return 0;;
'bar') return 1;;
'baz') return 2;;
esac
}
hash_vals=("foo_val"
"bar_val"
"baz_val");
hash_index "foo"
echo ${hash_vals[$?]}
Rappelez-vous, utilisez des guillemets simples dans case
, sinon il est soumis à englobement. Vraiment utile pour hash statiques / congelés depuis le début, mais on pourrait écrire un générateur d'index à partir d'un tableau de hash_keys=()
.
Attention, il est par défaut à la première, de sorte que vous pouvez définir l'élément côté zeroth:
hash_index() {
case $1 in
'foo') return 1;;
'bar') return 2;;
'baz') return 3;;
esac
}
hash_vals=("", # sort of like returning null/nil for a non existent key
"foo_val"
"bar_val"
"baz_val");
hash_index "foo" || echo ${hash_vals[$?]} # It can't get more readable than this
Caveat:. La longueur est maintenant incorrecte
Par ailleurs, si vous voulez conserver l'indexation de base zéro, vous pouvez réserver une autre valeur de l'indice et se prémunir contre une clé inexistante, mais il est moins lisible:
hash_index() {
case $1 in
'foo') return 0;;
'bar') return 1;;
'baz') return 2;;
*) return 255;;
esac
}
hash_vals=("foo_val"
"bar_val"
"baz_val");
hash_index "foo"
[[ $? -ne 255 ]] && echo ${hash_vals[$?]}
Ou, pour maintenir la bonne longueur, décalage d'index par un:
hash_index() {
case $1 in
'foo') return 1;;
'bar') return 2;;
'baz') return 3;;
esac
}
hash_vals=("foo_val"
"bar_val"
"baz_val");
hash_index "foo" || echo ${hash_vals[$(($? - 1))]}
Vous pouvez utiliser des noms de variables dynamiques et laisser les noms des variables fonctionnent comme les touches d'un hashmap.
Par exemple, si vous avez un fichier d'entrée avec deux colonnes, le nom, le crédit, comme l'exemple ci-dessous, et vous voulez résumer le revenu de chaque utilisateur:
Mary 100
John 200
Mary 50
John 300
Paul 100
Paul 400
David 100
La commande ci-dessous résumera tout, en utilisant des variables dynamiques clés, sous forme de carte _ $ {personne} :
while read -r person money; ((map_$person+=$money)); done < <(cat INCOME_REPORT.log)
Pour lire les résultats:
set | grep map
La sortie sera:
map_David=100
map_John=500
map_Mary=150
map_Paul=500
Élaborant sur ces techniques, je développe sur GitHub une fonction qui fonctionne comme un HashMap Object , shell_map .
Afin de créer " instances de HashMap " fonction shell_map est en mesure de créer des copies de lui-même sous des noms différents. Chaque nouvelle copie de la fonction aura une autre variable FUNCNAME $. FUNCNAME $ est utilisé ensuite pour créer un espace de noms pour chaque instance Carte.
Les touches carte sont des variables globales, sous la forme $ FUNCNAME_DATA_ $ KEY, où $ KEY est la clé ajoutée à la carte. Ces variables sont dynamiques les variables .
Bellow Je vais mettre une version simplifiée de celui-ci de sorte que vous pouvez utiliser comme exemple.
#!/bin/bash
shell_map () {
local METHOD="$1"
case $METHOD in
new)
local NEW_MAP="$2"
# loads shell_map function declaration
test -n "$(declare -f shell_map)" || return
# declares in the Global Scope a copy of shell_map, under a new name.
eval "${_/shell_map/$2}"
;;
put)
local KEY="$2"
local VALUE="$3"
# declares a variable in the global scope
eval ${FUNCNAME}_DATA_${KEY}='$VALUE'
;;
get)
local KEY="$2"
local VALUE="${FUNCNAME}_DATA_${KEY}"
echo "${!VALUE}"
;;
keys)
declare | grep -Po "(?<=${FUNCNAME}_DATA_)\w+((?=\=))"
;;
name)
echo $FUNCNAME
;;
contains_key)
local KEY="$2"
compgen -v ${FUNCNAME}_DATA_${KEY} > /dev/null && return 0 || return 1
;;
clear_all)
while read var; do
unset $var
done < <(compgen -v ${FUNCNAME}_DATA_)
;;
remove)
local KEY="$2"
unset ${FUNCNAME}_DATA_${KEY}
;;
size)
compgen -v ${FUNCNAME}_DATA_${KEY} | wc -l
;;
*)
echo "unsupported operation '$1'."
return 1
;;
esac
}
Utilisation:
shell_map new credit
credit put Mary 100
credit put John 200
for customer in `credit keys`; do
value=`credit get $customer`
echo "customer $customer has $value"
done
credit contains_key "Mary" && echo "Mary has credit!"
Quel dommage que je ne vois pas la question avant - je l'ai écrit bibliothèque shell-cadre qui contient entre autres les cartes (tableaux) Associatif. La dernière version de ce se trouve .
Exemple:
#!/bin/bash
#include map library
shF_PATH_TO_LIB="/usr/lib/shell-framework"
source "${shF_PATH_TO_LIB}/map"
#simple example get/put
putMapValue "mapName" "mapKey1" "map Value 2"
echo "mapName[mapKey1]: $(getMapValue "mapName" "mapKey1")"
#redefine old value to new
putMapValue "mapName" "mapKey1" "map Value 1"
echo "after change mapName[mapKey1]: $(getMapValue "mapName" "mapKey1")"
#add two new pairs key/values and print all keys
putMapValue "mapName" "mapKey2" "map Value 2"
putMapValue "mapName" "mapKey3" "map Value 3"
echo -e "mapName keys are \n$(getMapKeys "mapName")"
#create new map
putMapValue "subMapName" "subMapKey1" "sub map Value 1"
putMapValue "subMapName" "subMapKey2" "sub map Value 2"
#and put it in mapName under key "mapKey4"
putMapValue "mapName" "mapKey4" "subMapName"
#check if under two key were placed maps
echo "is map mapName[mapKey3]? - $(if isMap "$(getMapValue "mapName" "mapKey3")" ; then echo Yes; else echo No; fi)"
echo "is map mapName[mapKey4]? - $(if isMap "$(getMapValue "mapName" "mapKey4")" ; then echo Yes; else echo No; fi)"
#print map with sub maps
printf "%s\n" "$(mapToString "mapName")"
Je l'ai trouvé vrai, comme déjà mentionné, que la meilleure méthode d'interprétation est d'écrire / clés vals dans un fichier, puis utiliser grep / awk pour les récupérer. Cela ressemble à toutes sortes de IO inutiles, mais le cache disque entre en jeu et le rend extrêmement efficace -. Beaucoup plus rapide que d'essayer de les stocker dans la mémoire en utilisant l'une des méthodes ci-dessus (comme les benchmarks montrent)
Voici une méthode rapide, propre, j'aime:
hinit() {
rm -f /tmp/hashmap.$1
}
hput() {
echo "$2 $3" >> /tmp/hashmap.$1
}
hget() {
grep "^$2 " /tmp/hashmap.$1 | awk '{ print $2 };'
}
hinit capitols
hput capitols France Paris
hput capitols Netherlands Amsterdam
hput capitols Spain Madrid
echo `hget capitols France` and `hget capitols Netherlands` and `hget capitols Spain`
Si vous voulez appliquer la valeur unique par clé, vous pouvez aussi faire un peu grep / l'action sed dans hput ().
Bibliothèque je l'ai écrit il y a plusieurs années de script pour bash qui a soutenu des réseaux associatifs entre autres fonctions (journalisation, fichiers de configuration, support étendu pour l'argument de ligne de commande, générer de l'aide, les tests unitaires, etc.). La bibliothèque contient une enveloppe pour les tableaux associatifs et passe automatiquement au modèle approprié (interne pour bash4 et émule pour les versions précédentes). Il a été appelé-cadre coquille et hébergé chez origo.ethz.ch mais aujourd'hui la ressource est fermée. Si quelqu'un a besoin encore je peux partager avec vous.
Shell ont aucune carte intégrée comme la structure de données, j'utiliser la chaîne brute pour décrire des éléments tels que:
ARRAY=(
"item_A|attr1|attr2|attr3"
"item_B|attr1|attr2|attr3"
"..."
)
lorsque des éléments d'extrait et de ses attributs:
for item in "${ARRAY[@]}"
do
item_name=$(echo "${item}"|awk -F "|" '{print $1}')
item_attr1=$(echo "${item}"|awk -F "|" '{print $2}')
item_attr2=$(echo "${item}"|awk -F "|" '{print $3}')
echo "${item_name}"
echo "${item_attr1}"
echo "${item_attr2}"
done
Cela semble pas intelligent que d'autres la réponse des gens, mais facile à comprendre pour de nouvelles personnes à Shell.
Ajout d'une autre option, si JQ est disponible:
export NAMES="{
\"Mary\":\"100\",
\"John\":\"200\",
\"Mary\":\"50\",
\"John\":\"300\",
\"Paul\":\"100\",
\"Paul\":\"400\",
\"David\":\"100\"
}"
export NAME=David
echo $NAMES | jq --arg v "$NAME" '.[$v]' | tr -d '"'
Je modifié la solution de Vadim ce qui suit:
####################################################################
# Bash v3 does not support associative arrays
# and we cannot use ksh since all generic scripts are on bash
# Usage: map_put map_name key value
#
function map_put
{
alias "${1}$2"="$3"
}
# map_get map_name key
# @return value
#
function map_get {
if type -p "${1}$2"
then
alias "${1}$2" | awk -F "'" '{ print $2; }';
fi
}
# map_keys map_name
# @return map keys
#
function map_keys
{
alias -p | grep $1 | cut -d'=' -f1 | awk -F"$1" '{print $2; }'
}
Le changement est de map_get afin de l'empêcher de revenir erreurs si vous demandez une clé qui n'existe pas, bien que l'effet secondaire est qu'il ignorera aussi silencieusement des cartes manquantes, mais il convenait à mon cas d'utilisation mieux puisque je voulais juste vérifier une clé afin de sauter les éléments dans une boucle.
Réponse tardive, mais traiter de la problématique de cette façon, en utilisant la commande intégrée Bash Lire comme illustré dans le fragment de code à partir d'un script de pare-feu ufw qui suit. Cette approche a l'avantage d'utiliser autant de jeux de champs délimités (pas seulement 2) sont souhaitées. Nous avons utilisé le | delimiter car spécificateurs gamme de ports peut nécessiter deux points, soit 6001:. 6010
#!/usr/bin/env bash
readonly connections=(
'192.168.1.4/24|tcp|22'
'192.168.1.4/24|tcp|53'
'192.168.1.4/24|tcp|80'
'192.168.1.4/24|tcp|139'
'192.168.1.4/24|tcp|443'
'192.168.1.4/24|tcp|445'
'192.168.1.4/24|tcp|631'
'192.168.1.4/24|tcp|5901'
'192.168.1.4/24|tcp|6566'
)
function set_connections(){
local range proto port
for fields in ${connections[@]}
do
IFS=$'|' read -r range proto port <<< "$fields"
ufw allow from "$range" proto "$proto" to any port "$port"
done
}
set_connections