Shell Scripting için İlişkilendirilebilir dizileri veya Harita benzeri veri yapısını simüle eden bir komut dosyası gerekiyor mu?
Shell Scripting için İlişkilendirilebilir dizileri veya Harita benzeri veri yapısını simüle eden bir komut dosyası gerekiyor mu?
Yanıtlar:
İrfan'ın cevabına eklemek için get()
, harita içeriği üzerinde yineleme gerektirmediğinden , burada daha kısa ve daha hızlı bir versiyonu var :
get() {
mapName=$1; key=$2
map=${!mapName}
value="$(echo $map |sed -e "s/.*--${key}=\([^ ]*\).*/\1/" -e 's/:SP:/ /g' )"
}
Diğer bir seçenek, taşınabilirlik ana endişeniz değilse, kabukta yerleşik olan ilişkilendirilebilir dizileri kullanmaktır. Bu, bash 4.0'da çalışmalıdır (şimdi çoğu büyük dağıtımda mevcuttur, ancak OS X'te kendiniz yüklemediğiniz sürece değil), ksh ve zsh:
declare -A newmap
newmap[name]="Irfan Zulfiqar"
newmap[designation]=SSE
newmap[company]="My Own Company"
echo ${newmap[company]}
echo ${newmap[name]}
Kabuğa bağlı olarak, typeset -A newmap
yerine bir tane yapmanız gerekebilir declare -A newmap
veya bazılarında hiç gerekli olmayabilir.
test -z ${variable+x}
( x
önemli değil, bu herhangi bir dize olabilir). Bash'deki ilişkilendirilebilir bir dizi için, benzerini yapabilirsiniz; kullanın test -z ${map[key]+x}
.
Bash olmayan başka bir 4 yol.
#!/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"
Orada arama yapmak için bir if ifadesi de atabilirsiniz. eğer [[$ var = ~ / blah /]]. ya da her neyse.
Sanırım geri adım atıp haritanın veya ilişkisel dizinin gerçekte ne olduğunu düşünmeniz gerekiyor. Tüm bunlar, belirli bir anahtar için bir değeri depolamanın ve bu değeri hızlı ve verimli bir şekilde geri almanın bir yoludur. Ayrıca, her bir anahtar-değer çiftini almak veya anahtarları ve bunların ilişkili değerlerini silmek için anahtarlar üzerinde yineleme yapabilmek isteyebilirsiniz.
Şimdi, kabuk komut dosyası oluşturmada her zaman kullandığınız ve hatta bir komut dosyası yazmadan yalnızca kabukta bu özelliklere sahip olan bir veri yapısını düşünün. Kafası karışmış? Dosya sistemi.
Gerçekten, kabuk programlamada ilişkilendirilebilir bir diziye sahip olmak için ihtiyacınız olan tek şey bir geçici dizindir. mktemp -d
ilişkisel dizi kurucunuz:
prefix=$(basename -- "$0")
map=$(mktemp -dt ${prefix})
echo >${map}/key somevalue
value=$(cat ${map}/key)
Kullandığınız gibi hissediyorum yoksa echo
ve cat
, her zaman bazı küçük sarmalayıcılarını yazabilirsiniz; bunlar, İran'ınkinden modellenmiştir, ancak aşağıdakiler gibi keyfi değişkenler ayarlamak yerine sadece değeri çıkarırlar $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 : Bu yaklaşım, sorgulayıcı tarafından önerilen sed kullanan doğrusal aramadan biraz daha hızlıdır ve daha sağlamdır (anahtarların ve değerlerin -, =, boşluk, qnd ": SP:" içermesine izin verir). Dosya sistemini kullanıyor olması onu yavaşlatmaz; bu dosyaların aslında siz çağırmadığınız sürece diske yazılacağı asla garanti edilmez sync
; Bu gibi kısa ömürlü geçici dosyalar için, çoğunun asla diske yazılmaması olası değildir.
Aşağıdaki sürücü programını kullanarak, İran'ın kodunun, Jerry'nin İran'ın kodunun ve kodumun birkaç karşılaştırmasını yaptım:
#!/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
Sonuçlar:
$ time ./driver.sh irfan 10 5 gerçek 0m0.975s kullanıcı 0m0.280s sys 0d0.691sn $ time ./driver.sh brian 10 5 gerçek 0a0.226s kullanıcı 0m0.057s sys 0d0,123sn $ zaman ./driver.sh jerry 10 5 gerçek 0a0.706s kullanıcı 0m0.228s sys 0d0.530sn $ time ./driver.sh irfan 100 5 gerçek 0m10.633s kullanıcı 0m4.366s sys 0d7.127s $ time ./driver.sh brian 100 5 gerçek 0m1.682s kullanıcı 0m0.546sn sys 0d1.082sn $ time ./driver.sh jerry 100 5 gerçek 0m9.315s kullanıcı 0m4.565s sys 0d5.446sn $ time ./driver.sh irfan 10500 gerçek 1m46.197s kullanıcı 0m44.869s sys 1d12.282s $ zaman ./driver.sh brian 10500 gerçek 0a16.003sn kullanıcı 0d5.135s sys 0d10.396s $ zaman ./driver.sh jerry 10500 gerçek 1a24.414s kullanıcı 0m39.696s sys 0d54.834sn $ time ./driver.sh irfan 1000 5 gerçek 4a25.145s kullanıcı 3m17.286s sys 1a21,490sn $ time ./driver.sh brian 1000 5 gerçek 0a19.442sn kullanıcı 0d5.287s sys 0d10.751s $ zaman ./driver.sh jerry 1000 5 gerçek 5m29.136s kullanıcı 4d48.926s sys 0d59.336sn
Bash4 bunu yerel olarak destekler. Kullanmayın grep
ya da eval
, bunlar en çirkin hackler.
Örnek kodla ayrıntılı, ayrıntılı bir yanıt için bkz .: /programming/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; }'
}
Misal:
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
Şimdi bu soruyu cevaplıyorum.
Aşağıdaki komut dosyaları, kabuk komut dosyalarındaki ilişkili dizileri simüle eder. Basit ve anlaşılması çok kolay.
Harita, keyValuePair'in --name = Irfan --designation = SSE --company = My: SP: Own: SP: Company olarak kaydedildiği hiç bitmeyen bir dizeden başka bir şey değildir
değerler için boşluklar ': SP:' ile değiştirilir
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
düzenleme: Tüm anahtarları almak için başka bir yöntem eklendi.
getKeySet() {
if [ "$#" != 1 ];
then
exit 1;
fi
mapName=$1;
eval map="\"\$$mapName\""
keySet=`
echo $map |
sed -e "s/=[^ ]*//g" -e "s/\([ ]*\)--/\1/g"
`
}
eval
Verileri bash kodu gibi alıyorsunuz ve dahası: doğru şekilde alıntı yapmıyorsunuz . Her ikisi de çok sayıda hataya ve keyfi kod enjeksiyonuna neden olur.
Bash 3 için güzel ve basit bir çözüme sahip özel bir durum var:
Çok fazla değişkeni işlemek istemiyorsanız veya anahtarlar sadece geçersiz değişken tanımlayıcılarıysa ve dizinizin 256 öğeden az olması garantiliyse , işlev dönüş değerlerini kötüye kullanabilirsiniz. Bu çözüm, değer bir değişken olarak hazır olduğundan herhangi bir alt kabuk veya performansın çığlık atması için herhangi bir yineleme gerektirmez. Ayrıca neredeyse Bash 4 sürümü gibi çok okunabilir.
İşte en temel versiyon:
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[$?]}
Unutmayın, içinde tek tırnak kullanın case
, aksi takdirde genellemeye tabidir. Başlangıçtan itibaren statik / donmuş karmalar için gerçekten kullanışlıdır, ancak bir hash_keys=()
diziden bir dizin oluşturucu yazılabilir .
Dikkat edin, varsayılan olarak birinciye ayarlıdır, bu nedenle sıfırıncı öğeyi bir kenara bırakmak isteyebilirsiniz:
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
Uyarı: uzunluk artık yanlış.
Alternatif olarak, sıfır tabanlı indekslemeyi korumak istiyorsanız, başka bir indeks değeri ayırabilir ve var olmayan bir anahtara karşı koruma sağlayabilirsiniz, ancak daha az okunabilir:
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[$?]}
Veya uzunluğu doğru tutmak için dizini birer birer kaydırın:
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))]}
Dinamik değişken adlarını kullanabilir ve değişken adlarının bir karma haritanın anahtarları gibi çalışmasına izin verebilirsiniz.
Örneğin, aşağıdaki örnekte olduğu gibi, iki sütunlu, ad, kredi içeren bir girdi dosyanız varsa ve her kullanıcının gelirini toplamak istiyorsanız:
Mary 100
John 200
Mary 50
John 300
Paul 100
Paul 400
David 100
Aşağıdaki komut, dinamik değişkenleri anahtar olarak kullanarak, _ $ {person} haritası biçiminde her şeyi toplar :
while read -r person money; ((map_$person+=$money)); done < <(cat INCOME_REPORT.log)
Sonuçları okumak için:
set | grep map
Çıktı şu şekilde olacaktır:
map_David=100
map_John=500
map_Mary=150
map_Paul=500
Bu teknikleri detaylandırırken, GitHub'da bir HashMap Nesnesi gibi çalışan bir işlev , shell_map geliştiriyorum .
" HashMap örnekleri " oluşturmak için , shell_map işlevi farklı isimler altında kendi kopyalarını oluşturabilir. Her yeni işlev kopyasının farklı bir $ FUNCNAME değişkeni olacaktır. $ FUNCNAME daha sonra her bir Harita örneği için bir ad alanı oluşturmak için kullanılır.
Harita anahtarları $ FUNCNAME_DATA_ $ KEY biçimindeki global değişkenlerdir, burada $ KEY Haritaya eklenen anahtardır. Bu değişkenler dinamik değişkenlerdir .
Aşağıda örnek olarak kullanabilmeniz için basitleştirilmiş bir versiyonunu koyacağım.
#!/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
}
Kullanımı:
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!"
Yine başka bir bash-4 olmayan (yani bash 3, Mac uyumlu) yol:
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
Baskılar:
A1 => aaa
B2 => bbb
C3 => ccc
D4 => zzz
Fonksiyonlu case
bir ilişkisel dizi gibi davranır. Maalesef kullanamaz return
, bu yüzden echo
çıktısına sahiptir, ancak alt kabukları çatallayan bir pürist değilseniz bu bir problem değildir.
Ne yazık ki daha önce soruyu görmemiştim - diğerlerinin yanı sıra haritaları (İlişkilendirilebilir diziler) içeren kütüphane kabuğu çerçevesi yazdım . Son versiyonu burada bulunabilir .
Misal:
#!/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")"
Daha önce de belirtildiği gibi, en iyi performans gösteren yöntemin bir dosyaya anahtar / vals yazmak ve sonra bunları almak için grep / awk kullanmak olduğunu doğru buldum. Her türlü gereksiz IO gibi görünüyor, ancak disk önbelleği devreye giriyor ve onu son derece verimli hale getiriyor - yukarıdaki yöntemlerden birini kullanarak bunları bellekte saklamaya çalışmaktan çok daha hızlı (kıyaslamaların gösterdiği gibi).
İşte sevdiğim hızlı ve temiz bir yöntem:
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`
Anahtar başına tek değeri zorlamak istiyorsanız, hput () 'da küçük bir grep / sed eylemi de yapabilirsiniz.
birkaç yıl önce bash için, diğer özellikler arasında (günlük kaydı, yapılandırma dosyaları, komut satırı argümanı için genişletilmiş destek, yardım oluşturma, birim testi vb.) destekleyen komut dosyası kitaplığı yazdım. Kitaplık, ilişkili diziler için bir sarmalayıcı içerir ve otomatik olarak uygun modele geçer (bash4 için dahili ve önceki sürümler için öykünme). Kabuk çerçevesi olarak adlandırıldı ve origo.ethz.ch adresinde barındırıldı, ancak bugün kaynak kapalı. Birinin hala ihtiyacı varsa, sizinle paylaşabilirim.
Kabuğun veri yapısı gibi yerleşik bir haritası yok, öğeleri şu şekilde açıklamak için ham dizeyi kullanıyorum:
ARRAY=(
"item_A|attr1|attr2|attr3"
"item_B|attr1|attr2|attr3"
"..."
)
öğeleri ve özniteliklerini çıkarırken:
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
Bu, diğer insanların cevabından daha akıllıca görünmüyor, ancak yeni insanlar için anlaşılması kolay.
Vadim'in çözümünü aşağıdakilerle değiştirdim:
####################################################################
# 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; }'
}
Değişiklik, mevcut olmayan bir anahtar talep ederseniz hataları döndürmesini önlemek için map_get yapmaktır, ancak yan etkisi, eksik haritaları sessizce yok saymasıdır, ancak benim kullanım durumuma daha uygun olmuştur. bir döngüdeki öğeleri atlamak için bir anahtarı kontrol etmek istedi.
Geç yanıt, ancak aşağıdaki ufw güvenlik duvarı betiğinden kod parçacığı içinde gösterildiği gibi okunan bash yerleşikini kullanarak sorunu bu şekilde ele almayı düşünün . Bu yaklaşım, istendiği kadar sınırlandırılmış alan kümesi (sadece 2 değil) kullanma avantajına sahiptir. Kullandık | sınırlayıcı çünkü bağlantı noktası aralığı belirleyicileri iki nokta üst üste gerektirebilir, ör. 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