Gli array associativi in script di Shell
-
22-08-2019 - |
Domanda
Avevamo bisogno di uno script che simula gli array Associativi o la Mappa come struttura di dati per la Shell Scripting, qualsiasi corpo?
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!"
Che peccato non ho visto la domanda prima - ho scritto shell-quadro , che contiene tra l'altro le mappe (array associativi). L'ultima versione di esso può essere trovata rel="nofollow"> .
Esempio:
#!/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")"
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