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?

Était-ce utile?

La solution

Pour ajouter réponse de Irfan, voici une version plus courte et plus rapide de get() car il ne nécessite pas d'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' )"
}

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
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top