Domanda

Avevamo bisogno di uno script che simula gli array Associativi o la Mappa come struttura di dati per la Shell Scripting, qualsiasi corpo?

È stato utile?

Soluzione

Per aggiungere a di Irfan risposta , qui è una versione più breve e più veloce di get() in quanto non richiede l'iterazione sui contenuti della mappa:

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

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

Altri suggerimenti

Un'altra opzione, se la portabilità non è la vostra preoccupazione principale, è quello di utilizzare array associativi che sono costruiti dentro al guscio. Questo dovrebbe funzionare in bash 4.0 (disponibile ora sulla maggior parte delle distribuzioni più importanti, anche se non su OS X a meno che non si installa da soli), ksh e zsh:

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

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

A seconda della shell, potrebbe essere necessario fare un typeset -A newmap invece di declare -A newmap, o in qualche Potrebbe non essere necessario a tutti.

Un altro non bash 4 vie.

#!/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"

Si potrebbe gettare un'istruzione if per la ricerca in là pure. se [[$ var = ~ / bla /]]. o qualsiasi altra cosa.

Credo che è necessario fare un passo indietro e pensare a ciò che una mappa, o array associativo, è davvero. Tutto ciò che è è un modo per memorizzare un valore per una data chiave, e ottenere che il valore indietro in modo rapido ed efficiente. Si consiglia inoltre di essere in grado di scandire le chiavi per recuperare ogni coppia chiave-valore, o cancellare le chiavi ei relativi valori associati.

Ora, pensare a una struttura di dati si utilizza tutto il tempo in shell scripting, e anche solo nella shell senza scrivere una sceneggiatura, che ha queste proprietà. Perplesso? E 'il filesystem.

In realtà, tutto è necessario avere un array associativo in programmazione della shell è una directory temp. mktemp -d è il vostro costruttore di array associativo:

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

Se non avete voglia di utilizzare echo e cat, si può sempre scrivere alcuni piccoli involucri; questi sono modellati off di Irfan di, anche se solo in uscita il valore piuttosto che impostare variabili arbitrarie come $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

modifica : Questo approccio è in realtà un po 'più veloce rispetto alla ricerca lineare utilizzando sed suggerito da l'interrogante, così come più robusto (permette chiavi e valori per contenere -, =, lo spazio, qnd ": SP:"). Il fatto che si utilizza il file system non lo rende lento; Questi file sono in realtà mai garantita da scrivere sul disco a meno che non si chiama sync; per i file temporanei come questo con una vita breve, non è improbabile che molti di loro non sarà mai scritta sul disco.

Ho fatto un paio di punti di riferimento di codice di Irfan, modifica del codice di Irfan di Jerry, e il mio codice, utilizzando il seguente programma del driver:

#!/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

I risultati:

    $ 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 supporta in modo nativo. Non utilizzare grep o eval, sono il più brutto di hack.

Per una, risposta dettagliata verbose con codice di esempio, vedere: 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; }'
}

Esempio:

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

Ora per rispondere a questa domanda.

Seguente script simula gli array associativi in script di shell.La sua semplice e molto facile da capire.

La mappa non è altro che una infinita stringa che ha keyValuePair salvato come --name=Irfan --denominazione=SSE --società=la Mia:SP:Proprie:SP:Azienda

gli spazi vengono sostituiti con:SP:' per i valori

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: Appena aggiunto un altro metodo per recuperare tutte le chiavi.

getKeySet() {
    if [ "$#" != 1 ]; 
    then 
        exit 1; 
    fi

    mapName=$1; 

    eval map="\"\$$mapName\""

    keySet=`
           echo $map | 
           sed -e "s/=[^ ]*//g" -e "s/\([ ]*\)--/\1/g"
          `
}

Per Bash 3, c'è un caso particolare che ha una bella e semplice soluzione:

Se non si desidera gestire un sacco di variabili, o le chiavi sono semplicemente non validi gli identificatori delle variabili, e l'array è garantito meno di 256 elementi, è possibile abuso di funzione dei valori di ritorno.Questa soluzione non richiede alcuna subshell come valore è prontamente disponibile come una variabile, né alcuna iterazione in modo che le prestazioni di urla.Inoltre è molto leggibile, quasi come il Bash 4 versione.

Ecco la versione base:

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

Ricordate, usa virgolette singole in case, il resto è soggetto a glob.Davvero utile per static/congelati hash dall'inizio, ma si potrebbe scrivere un indice generatore da un hash_keys=() array.

Guardare fuori, di default è il primo, quindi si consiglia di mettere da parte zeroth elemento:

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 lunghezza è ora corretto.

In alternativa, se si desidera mantenere indicizzazione in base zero, è possibile prenotare un altro valore di indice e la guardia contro un inesistente chiave, ma è meno leggibile:

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

O, per mantenere la lunghezza corretta, offset indice per uno:

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))]}

È possibile utilizzare i nomi delle variabili dinamiche e lasciare che le variabili nomi funzionano come i tasti di un HashMap.

Ad esempio, se si dispone di un file di input con due colonne, il nome, di credito, come ad esempio il muggito, e si desidera sommare il reddito di ogni utente:

Mary 100
John 200
Mary 50
John 300
Paul 100
Paul 400
David 100

Il soffietto comando riassumere tutto, utilizzando le variabili dinamiche come chiavi, sotto forma di mappa _ $ {} persona :

while read -r person money; ((map_$person+=$money)); done < <(cat INCOME_REPORT.log)

Per leggere i risultati:

set | grep map

L'output sarà:

map_David=100
map_John=500
map_Mary=150
map_Paul=500

Elaborando queste tecniche, sto sviluppando su GitHub una funzione che funziona proprio come un HashMap oggetto , shell_map .

Al fine di creare " casi HashMap " funzione shell_map è in grado di creare copie di se stesso con nomi diversi. Ogni nuova copia funzione avrà una variabile $ FUNCNAME diverso. $ FUNCNAME poi viene utilizzato per creare uno spazio dei nomi per ogni istanza Map.

La mappa chiavi sono variabili globali, nella forma $ FUNCNAME_DATA_ $ KEY, dove $ KEY è la chiave aggiunto alla mappa. Queste variabili sono dinamica variabili .

Bellow Metterò una versione semplificata di esso in modo da poter utilizzare come esempio.

#!/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
}

Utilizzo:

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!"

ho trovato vero, come già accennato, che il metodo migliore è l'esecuzione di scrivere chiave / Vals in un file, e quindi utilizzare grep / awk per recuperarli. Sembra che tutti i tipi di inutili IO, ma cache del disco calci e lo rende estremamente efficiente -. Molto più veloce che cercare di conservare in memoria utilizzando uno dei metodi di cui sopra (come parametri di riferimento dimostrano)

Ecco un rapido, metodo pulito Mi piace:

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`

Se si voleva far rispettare a valore singolo per tasto, si potrebbe anche fare un po 'di grep / sed azione in hput ().

diversi anni fa ho scritto libreria di script bash che ha sostenuto gli array associativi tra le altre caratteristiche (la registrazione, file di configurazione, supporto esteso per argomento da riga di comando, generare aiuto, unit testing, ecc). La biblioteca contiene un avvolgitore per gli array associativi e passa automaticamente al modello appropriato (interno per bash4 e emulare per le versioni precedenti). E 'stato chiamato shell-quadro e ospitata presso origo.ethz.ch ma oggi la risorsa è chiuso. Se qualcuno ancora ha bisogno posso condividere con voi.

Shell non hanno alcuna mappa built-in come la struttura dei dati, utilizzare stringa grezzo per descrivere gli elementi del genere:

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

quando gli elementi estratto ed i suoi attributi:

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

Questo mi sembra non intelligente di risposta di altre persone, ma facile da capire per nuove persone a Shell.

L'aggiunta di un'altra opzione, se JQ è disponibile:

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 '"' 

Ho modificato la soluzione di Vadim con il seguente:

####################################################################
# 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; }'
}

Il cambiamento è quello di map_get al fine di evitare di tornare errori se si richiede una chiave che non esiste, anche se l'effetto collaterale è che sarà anche silenziosamente ignorare mappe mancanti, ma è adatto il mio caso d'uso migliore poiché volevo solo verificare la presenza di un tasto per saltare gli elementi in un ciclo.

replica tardi, ma prendere in considerazione per affrontare il problema in questo modo, utilizzando la bash incorporato lettura , come illustrato nel frammento di codice da uno script firewall ufw che segue. Questo approccio ha il vantaggio di utilizzare come molti insiemi campo delimitato (non solo 2) come si desidera. Abbiamo usato il | delimitatore perché prescrittori intervallo di porte possono richiedere due punti, vale a 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
scroll top