Tableaux associatifs dans les scripts Shell

116

Nous avions besoin d'un script qui simule des tableaux associatifs ou une structure de données de type Map pour Shell Scripting, n'importe quel corps?

Irfan Zulfiqar
la source

Réponses:

20

Pour ajouter à la réponse d' Irfan , voici une version plus courte et plus rapide de get()puisqu'elle 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' )"
}
Jerry Penner
la source
16
bifurquer une sous-coque et un sed n'est guère optimal. Bash4 prend en charge cela de manière native et bash3 a de meilleures alternatives.
lhunath
149

Une autre option, si la portabilité n'est pas votre principale préoccupation, consiste à utiliser des tableaux associatifs intégrés au shell. Cela devrait fonctionner dans bash 4.0 (disponible maintenant sur la plupart des distributions majeures, mais pas sur OS X sauf si vous l'installez 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 à la typeset -A newmapplace de declare -A newmap, ou dans certains, cela peut ne pas être nécessaire du tout.

Brian Campbell
la source
Merci pour votre réponse, je pense que ce serait la meilleure façon de le faire pour les gars qui utiliseraient bash 4.0 ou supérieur.
Irfan Zulfiqar
J'ajouterais un peu de kludge pour m'assurer que BASH_VERSION est défini, et> = 4. Et oui, BASH 4 est vraiment, vraiment cool!
Tim Post
J'utilise quelque chose comme ça. Quelle est la meilleure façon de "détecter" l'erreur là où l'index / indice du tableau n'existe pas? Par exemple, que se passerait-il si je prenais l'indice comme option de ligne de commande et que l'utilisateur faisait une faute de frappe et entrait «designatio»? J'obtiens une erreur "mauvais indice de tableau" mais je ne sais pas comment valider l'entrée au moment de la recherche de tableau, si c'est possible?
Jer
3
@Jer C'est assez obscur, mais pour déterminer si une variable est définie dans le shell, vous pouvez utiliser test -z ${variable+x}(cela xn'a pas d'importance, cela pourrait être n'importe quelle chaîne). Pour un tableau associatif dans Bash, vous pouvez faire la même chose; utiliser test -z ${map[key]+x}.
Brian Campbell
95

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 également lancer une instruction if pour effectuer une recherche. si [[$ var = ~ / blah /]]. ou peu importe.

Bubnoff
la source
2
Cette méthode est bonne lorsque vous n'avez pas vraiment Bash 4. Mais je pense que la ligne qui récupère la valeur VALUE serait plus sûre de cette façon: VALUE = $ {animal # *:}. Avec un seul caractère #, la correspondance s'arrêtera sur le premier ":". Cela permet également aux valeurs de contenir ":".
Ced-le-pingouin
@ Ced-le-pingouin ~ C'est un excellent point! Je n'ai pas compris ça. J'ai modifié mon message pour refléter les améliorations suggérées.
Bubnoff
1
C'est une émulation assez hackish de tableaux associatifs utilisant la substitution de paramètres BASH. Le sous-paramètre "clé" remplace tout avant les deux-points et le modèle de valeur remplace tout après les deux points. Similaire à une correspondance générique regex. Donc PAS un vrai tableau associatif. Non recommandé, sauf si vous avez besoin d'un moyen facile à comprendre de faire des fonctionnalités de hachage / tableau associatif dans BASH 3 ou inférieur. Cela fonctionne cependant! Plus d'informations ici: tldp.org/LDP/abs/html/parameter-substitution.html#PSOREX2
Bubnoff
1
Cela n'implémente pas de tableau associatif car il ne fournit pas un moyen de rechercher un élément par la clé. Il fournit uniquement un moyen de trouver chaque clé (et valeur) à partir d'un index numérique. (Un élément peut être trouvé par clé en itérant dans le tableau, mais ce n'est pas ce qui est souhaité pour un tableau associatif.)
Eric Postpischil
@EricPostpischil Vrai. Ce n'est qu'un hack. Cela permet à une personne d'utiliser une syntaxe familière dans la configuration mais nécessite toujours une itération dans le tableau comme vous le dites. J'ai essayé d'être clair dans mon commentaire précédent qu'il ne s'agissait certainement pas d'un tableau associatif et je ne le recommande même pas si vous avez des alternatives. Le seul point en sa faveur, à mon avis, est qu'il est facile à écrire et à utiliser pour ceux qui connaissent d'autres langages comme Python. Si vous êtes à un point où vous souhaitez réellement implémenter des tableaux associatifs dans BASH 3, vous devrez peut-être revenir un peu sur vos étapes.
Bubnoff
34

Je pense que vous devez prendre du recul et réfléchir à ce qu'est vraiment 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 souhaiterez peut-être également pouvoir parcourir les clés pour récupérer chaque paire clé / valeur 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 le shell sans écrire un script, qui possède ces propriétés. Perplexe? C'est le système de fichiers.

En réalité, tout ce dont vous avez besoin pour avoir un tableau associatif dans la programmation shell est un répertoire temporaire. mktemp -dest 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 echoet cat, vous pouvez toujours écrire de petits wrappers; ceux-ci sont modélisés à partir de ceux d'Irfan, bien qu'ils ne produisent que la valeur 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

edit : Cette approche est en fait un peu plus rapide que la recherche linéaire utilisant sed suggérée par le questionneur, ainsi que plus robuste (elle permet aux clés et valeurs de contenir -, =, espace, qnd ": SP:"). Le fait qu'il utilise le système de fichiers ne le ralentit pas; l'écriture sur le disque de ces fichiers n'est en fait jamais garantie à moins que vous n'appeliez sync; pour des fichiers temporaires comme celui-ci avec une durée de vie 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 par Jerry du code d'Irfan 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 resultats:

    $ time ./driver.sh irfan 10 5

    réel 0m0.975s
    utilisateur 0m0.280s
    sys 0m0.691s

    $ time ./driver.sh brian 10 5

    réel 0m0.226s
    utilisateur 0m0.057s
    sys 0m0.123s

    $ time ./driver.sh jerry 10 5

    réel 0m0.706s
    utilisateur 0m0.228s
    sys 0m0.530s

    $ time ./driver.sh irfan 100 5

    réel 0m10.633s
    utilisateur 0m4.366s
    sys 0m7.127s

    $ time ./driver.sh brian 100 5

    réel 0m1.682s
    utilisateur 0m0.546s
    sys 0m1.082s

    $ time ./driver.sh jerry 100 5

    réel 0m9.315s
    utilisateur 0m4.565s
    sys 0m5.446s

    $ time ./driver.sh irfan 10500

    réel 1m46.197s
    utilisateur 0m44.869s
    sys 1m12.282s

    $ time ./driver.sh brian 10500

    réel 0m16.003s
    utilisateur 0m5.135s
    sys 0m10.396s

    $ time ./driver.sh jerry 10500

    réel 1m24.414s
    utilisateur 0m39.696s
    sys 0m54.834s

    $ time ./driver.sh irfan 1000 5

    réel 4m25.145s
    utilisateur 3m17.286s
    sys 1m21.490s

    $ time ./driver.sh brian 1000 5

    réel 0m19.442s
    utilisateur 0m5.287s
    sys 0m10.751s

    $ time ./driver.sh jerry 1000 5

    réel 5m29.136s
    utilisateur 4m48.926s
    sys 0m59.336s

Brian Campbell
la source
1
Je ne pense pas que vous devriez utiliser un système de fichiers pour les cartes, qui utilise essentiellement IO pour quelque chose que vous pouvez faire assez rapidement en mémoire.
Irfan Zulfiqar
9
Les fichiers ne seront jamais nécessairement écrits sur le disque; sauf si vous appelez sync, le système d'exploitation peut simplement les laisser en mémoire. Votre code appelle 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.
Brian Campbell
D'un autre côté, les tableaux natifs de bash4 sont une approche nettement meilleure et dans bash3, vous pouvez toujours tout garder hors du disque sans bifurquer en utilisant declare et indirection.
lhunath
7
"rapide" et "shell" ne vont pas vraiment ensemble de toute façon: certainement pas pour le genre de problèmes de vitesse dont nous parlons au niveau "éviter les minuscules IO". Vous pouvez rechercher et utiliser / dev / shm pour garantir l'absence d'E / S.
jmtd
2
Cette solution m'a étonné et est tout simplement géniale. C'est toujours vrai en 2016. Cela devrait vraiment être la réponse acceptée.
Gordon
7

Bash4 prend en charge cela de manière native. N'utilisez pas grepou eval, ce sont les plus laids des hacks.

Pour une réponse détaillée et détaillée avec un exemple de code, voir: /programming/3467959

lhunath
la source
7
####################################################################
# 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
Vadim
la source
4

Répondant maintenant à cette question.

Les scripts suivants simulent des tableaux associatifs dans des scripts shell. C'est simple et très facile à comprendre.

La carte n'est rien d'autre qu'une chaîne sans fin qui a keyValuePair enregistré sous --name = Irfan --designation = SSE --company = My: SP: Own: SP: Company

les 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

edit: vient d'ajouter 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"
          `
}
Irfan Zulfiqar
la source
1
Vous donnez evaldes données comme si c'était du code bash, et en plus: vous ne les citez pas correctement. Les deux provoquent une masse de bogues et une injection de code arbitraire.
lhunath
3

Pour Bash 3, il existe un cas particulier qui a une solution simple et agréable:

Si vous ne voulez pas gérer beaucoup de variables, ou si les clés sont simplement des identificateurs de variables non valides et que votre tableau est garanti d'avoir moins de 256 éléments , vous pouvez abuser des valeurs de retour de fonction. Cette solution ne nécessite aucun sous-shell car la valeur est facilement disponible en tant que variable, ni aucune itération pour que les performances crient. De plus, il est très lisible, presque comme la version Bash 4.

Voici la version la plus basique:

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[$?]}

N'oubliez pas, utilisez des guillemets simples dans case, sinon il est sujet à un globbing. Vraiment utile pour les hachages statiques / gelés dès le début, mais on pourrait écrire un générateur d'index à partir d'un hash_keys=()tableau.

Attention, il est par défaut le premier, donc vous voudrez peut-être mettre de côté le zéro élément:

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

Attention: la longueur est désormais incorrecte.

Sinon, si vous souhaitez conserver l'indexation de base zéro, vous pouvez réserver une autre valeur d'index et vous protéger contre une clé inexistante, mais elle 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 garder la longueur correcte, offset de 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))]}
Lloeki
la source
2

Vous pouvez utiliser des noms de variables dynamiques et laisser les noms de variables fonctionner comme les clés d'un hashmap.

Par exemple, si vous avez un fichier d'entrée avec deux colonnes, nom, crédit, comme dans l'exemple ci-dessous, et que vous souhaitez additionner les revenus 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 comme clés, sous la forme de map _ $ {person} :

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

En développant ces techniques, je développe sur GitHub une fonction qui fonctionne exactement comme un objet HashMap , shell_map .

Afin de créer des " instances HashMap ", la fonction shell_map est capable de créer des copies d'elle-même sous différents noms. Chaque nouvelle copie de fonction aura une variable $ FUNCNAME différente. $ FUNCNAME est alors utilisé pour créer un espace de noms pour chaque instance de Map.

Les clés de la carte sont des variables globales, sous la forme $ FUNCNAME_DATA_ $ KEY, où $ KEY est la clé ajoutée à la carte. Ces variables sont des variables dynamiques .

Ci-dessous, je vais mettre une version simplifiée de celui-ci afin que vous puissiez l'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
}

Usage:

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!"
Bruno Negrão Zica
la source
2

Encore une autre manière non-bash-4 (c'est-à-dire, bash 3, compatible Mac):

val_of_key() {
    case $1 in
        'A1') echo 'aaa';;
        'B2') echo 'bbb';;
        'C3') echo 'ccc';;
        *) echo 'zzz';;
    esac
}

for x in 'A1' 'B2' 'C3' 'D4'; do
    y=$(val_of_key "$x")
    echo "$x => $y"
done

Impressions:

A1 => aaa
B2 => bbb
C3 => ccc
D4 => zzz

La fonction avec les caseagit comme un tableau associatif. Malheureusement, il ne peut pas utiliser return, donc il doit à echosa sortie, mais ce n'est pas un problème, à moins que vous ne soyez un puriste qui évite de forger des sous-shell.

Walter Tross
la source
1

Quel dommage que je n'ai pas vu la question avant - j'ai écrit une bibliothèque shell-framework qui contient entre autres les cartes (tableaux associatifs). La dernière version de celui-ci peut être trouvée ici .

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")"
Mendiant
la source
1

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 '"' 
critique
la source
0

J'ai trouvé vrai, comme déjà mentionné, que la méthode la plus performante consiste à écrire des clés / vals dans un fichier, puis à utiliser grep / awk pour les récupérer. Cela ressemble à toutes sortes d'E / S inutiles, mais le cache disque intervient et le rend extrêmement efficace - beaucoup plus rapide que d'essayer de les stocker en mémoire en utilisant l'une des méthodes ci-dessus (comme le montrent les benchmarks).

Voici une méthode rapide et propre que 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 souhaitez appliquer une valeur unique par clé, vous pouvez également faire une petite action grep / sed dans hput ().

Al P.
la source
0

il y a plusieurs années, j'ai écrit une bibliothèque de scripts pour bash qui supportait les tableaux associatifs entre autres fonctionnalités (journalisation, fichiers de configuration, prise en charge étendue de l'argument de ligne de commande, génération d'aide, tests unitaires, etc.). La bibliothèque contient un wrapper pour les tableaux associatifs et bascule automatiquement vers le modèle approprié (interne pour bash4 et émuler pour les versions précédentes). Il s'appelait shell-framework et était hébergé sur origo.ethz.ch mais aujourd'hui, la ressource est fermée. Si quelqu'un en a encore besoin, je peux le partager avec vous.

Mendiant
la source
Ça vaudrait peut-être la peine de le coller sur github
Mark K Cowan
0

Shell n'a pas de carte intégrée comme la structure de données, j'utilise une chaîne brute pour décrire des éléments comme ça:

ARRAY=(
    "item_A|attr1|attr2|attr3"
    "item_B|attr1|attr2|attr3"
    "..."
)

lors de l'extraction d'éléments 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 ne semble pas intelligent que la réponse des autres, mais facile à comprendre pour les nouvelles personnes.

coanor
la source
-1

J'ai modifié la solution de Vadim avec 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 renvoyer des erreurs si vous demandez une clé qui n'existe pas, bien que l'effet secondaire soit qu'il ignorera également silencieusement les cartes manquantes, mais cela convenait mieux à mon cas d'utilisation puisque je viens de voulait vérifier une clé afin de sauter des éléments dans une boucle.

Haravikk
la source
-1

Réponse tardive, mais envisagez de résoudre le problème de cette manière, en utilisant la lecture intégrée bash comme illustré dans l'extrait de code d'un script de pare-feu ufw qui suit. Cette approche a l'avantage d'utiliser autant d'ensembles de champs délimités (pas seulement 2) que vous le souhaitez. Nous avons utilisé le | délimiteur car les spécificateurs de plage de ports peuvent nécessiter deux points, c'est -à- dire 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
AsymLabs
la source