Kami memerlukan skrip yang mensimulasikan array Asosiatif atau Peta seperti struktur data untuk Skrip Shell, badan apa saja?
Kami memerlukan skrip yang mensimulasikan array Asosiatif atau Peta seperti struktur data untuk Skrip Shell, badan apa saja?
Jawaban:
Untuk menambah jawaban Irfan , berikut adalah versi yang lebih pendek dan lebih cepat get()
karena tidak memerlukan iterasi atas isi peta:
get() {
mapName=$1; key=$2
map=${!mapName}
value="$(echo $map |sed -e "s/.*--${key}=\([^ ]*\).*/\1/" -e 's/:SP:/ /g' )"
}
Pilihan lain, jika portabilitas bukan perhatian utama Anda, adalah menggunakan array asosiatif yang dibangun di dalam shell. Ini seharusnya berfungsi di bash 4.0 (sekarang tersedia di sebagian besar distro utama, meskipun tidak di OS X kecuali Anda menginstalnya sendiri), ksh, dan zsh:
declare -A newmap
newmap[name]="Irfan Zulfiqar"
newmap[designation]=SSE
newmap[company]="My Own Company"
echo ${newmap[company]}
echo ${newmap[name]}
Tergantung pada shell, Anda mungkin perlu melakukan typeset -A newmap
bukan declare -A newmap
, atau dalam beberapa hal mungkin tidak diperlukan sama sekali.
test -z ${variable+x}
( x
tidak masalah, itu bisa berupa string apa pun). Untuk array asosiatif di Bash, Anda bisa melakukan hal serupa; gunakan test -z ${map[key]+x}
.
Cara 4 non-bash lainnya.
#!/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"
Anda juga bisa melempar pernyataan if untuk mencari di sana. jika [[$ var = ~ / blah /]]. atau terserah.
Saya pikir Anda perlu melangkah mundur dan memikirkan tentang apa itu peta, atau array asosiatif, sebenarnya. Semua itu adalah cara untuk menyimpan nilai untuk kunci tertentu, dan mendapatkan kembali nilai itu dengan cepat dan efisien. Anda mungkin juga ingin dapat mengulang kunci untuk mengambil setiap pasangan nilai kunci, atau menghapus kunci dan nilai yang terkait.
Sekarang, pikirkan tentang struktur data yang Anda gunakan sepanjang waktu dalam skrip shell, dan bahkan hanya di shell tanpa menulis skrip, yang memiliki properti ini. Bingung? Itu sistem file.
Sungguh, yang Anda butuhkan untuk memiliki array asosiatif dalam pemrograman shell adalah direktori temp. mktemp -d
adalah konstruktor array asosiatif Anda:
prefix=$(basename -- "$0")
map=$(mktemp -dt ${prefix})
echo >${map}/key somevalue
value=$(cat ${map}/key)
Jika Anda tidak ingin menggunakan echo
dan cat
, Anda selalu dapat menulis bungkus kecil; yang ini dimodelkan dari Irfan, meskipun mereka hanya menampilkan nilai daripada menyetel variabel arbitrer seperti $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
sunting : Pendekatan ini sebenarnya sedikit lebih cepat daripada pencarian linier menggunakan sed yang disarankan oleh penanya, serta lebih kuat (memungkinkan kunci dan nilai untuk memuat -, =, spasi, qnd ": SP:"). Fakta bahwa ia menggunakan sistem berkas tidak membuatnya lambat; file-file ini sebenarnya tidak pernah dijamin akan ditulis ke disk kecuali Anda memanggil sync
; untuk file sementara seperti ini dengan masa hidup yang singkat, bukan tidak mungkin banyak dari mereka tidak akan pernah ditulis ke disk.
Saya melakukan beberapa tolok ukur kode Irfan, modifikasi kode Irfan oleh Jerry, dan kode saya, menggunakan program driver berikut:
#!/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
Hasil:
$ time ./driver.sh irfan 10 5 0m0.975s nyata pengguna 0m0.280s sys 0m0.691s $ time ./driver.sh brian 10 5 0m0.226s nyata pengguna 0m0.057s sys 0m0.123s $ waktu ./driver.sh jerry 10 5 0m0.706s nyata pengguna 0m0.228s sys 0m0.530s $ waktu ./driver.sh irfan 100 5 0m10.633s nyata pengguna 0m4.366s sys 0m7.127s $ time ./driver.sh brian 100 5 0m1.682s nyata pengguna 0m0.546s sys 0m1.082s $ waktu ./driver.sh jerry 100 5 0m9.315 nyata pengguna 0m4.565s sys 0m5.446s $ time ./driver.sh irfan 10 500 nyata 1m46.197s pengguna 0m44.869s sys 1m12.282s $ time ./driver.sh brian 10 500 0m16.003 nyata pengguna 0m5.135s sys 0m10.396s $ waktu ./driver.sh jerry 10 500 nyata 1m24,414s pengguna 0m39.696s sys 0m54.834s $ waktu ./driver.sh irfan 1000 5 4m25.145s nyata pengguna 3m17.286s sys 1m21.490s $ time ./driver.sh brian 1000 5 0m19.442s nyata pengguna 0m5.287s sys 0m10.751s $ waktu ./driver.sh jerry 1000 5 nyata 5m29.136s pengguna 4m48.926s sys 0m59.336s
Bash4 mendukung ini secara native. Jangan gunakan grep
atau eval
, mereka adalah peretasan paling jelek.
Untuk jawaban verbose dan mendetail dengan kode contoh lihat: /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; }'
}
Contoh:
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
Sekarang menjawab pertanyaan ini.
Skrip berikut mensimulasikan array asosiatif dalam skrip shell. Sederhana dan sangat mudah dimengerti.
Peta tidak lain adalah string yang tidak pernah berakhir yang memiliki keyValuePair yang disimpan sebagai --name = Irfan --designation = SSE --company = My: SP: Own: SP: Company
spasi diganti dengan ': SP:' untuk nilai
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: Baru saja menambahkan metode lain untuk mengambil semua kunci.
getKeySet() {
if [ "$#" != 1 ];
then
exit 1;
fi
mapName=$1;
eval map="\"\$$mapName\""
keySet=`
echo $map |
sed -e "s/=[^ ]*//g" -e "s/\([ ]*\)--/\1/g"
`
}
eval
mengumpulkan data seolah-olah itu adalah kode bash, dan terlebih lagi: Anda gagal mengutipnya dengan benar. Keduanya menyebabkan banyak bug dan injeksi kode arbitrer.
Untuk Bash 3, ada kasus tertentu yang memiliki solusi yang bagus dan sederhana:
Jika Anda tidak ingin menangani banyak variabel, atau kunci hanyalah pengenal variabel yang tidak valid, dan array Anda dijamin memiliki kurang dari 256 item , Anda dapat menyalahgunakan nilai pengembalian fungsi. Solusi ini tidak memerlukan subkulit karena nilainya sudah tersedia sebagai variabel, atau iterasi apa pun sehingga performa menjerit. Juga sangat mudah dibaca, hampir seperti versi Bash 4.
Ini versi paling dasar:
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[$?]}
Ingat, gunakan tanda kutip tunggal dalam case
, jika tidak, itu tunduk pada globbing. Sangat berguna untuk hash statis / beku sejak awal, tetapi seseorang dapat menulis generator indeks dari hash_keys=()
array.
Hati-hati, defaultnya adalah yang pertama, jadi Anda mungkin ingin menyisihkan elemen ke nol:
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
Peringatan: panjangnya sekarang salah.
Atau, jika Anda ingin mempertahankan pengindeksan berbasis nol, Anda dapat memesan nilai indeks lain dan menjaga dari kunci yang tidak ada, tetapi kurang terbaca:
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[$?]}
Atau, untuk menjaga panjangnya tetap benar, offset indeks satu per satu:
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))]}
Anda dapat menggunakan nama variabel dinamis dan membiarkan nama variabel berfungsi seperti kunci dari hashmap.
Misalnya, jika Anda memiliki file input dengan dua kolom, nama, kredit, seperti contoh di bawah ini, dan Anda ingin menjumlahkan pendapatan setiap pengguna:
Mary 100
John 200
Mary 50
John 300
Paul 100
Paul 400
David 100
Perintah di bawah ini akan menjumlahkan semuanya, menggunakan variabel dinamis sebagai kunci, dalam bentuk peta _ $ {person} :
while read -r person money; ((map_$person+=$money)); done < <(cat INCOME_REPORT.log)
Untuk membaca hasilnya:
set | grep map
Outputnya adalah:
map_David=100
map_John=500
map_Mary=150
map_Paul=500
Menguraikan teknik ini, saya mengembangkan di GitHub sebuah fungsi yang bekerja seperti Objek HashMap , shell_map .
Untuk membuat " instance HashMap ", fungsi shell_map dapat membuat salinannya sendiri dengan nama yang berbeda. Setiap salinan fungsi baru akan memiliki variabel $ FUNCNAME yang berbeda. $ FUNCNAME kemudian digunakan untuk membuat namespace untuk setiap instance Peta.
Kunci peta adalah variabel global, dalam bentuk $ FUNCNAME_DATA_ $ KEY, di mana $ KEY adalah kunci yang ditambahkan ke Peta. Variabel ini adalah variabel dinamis .
Di bawah ini saya akan memberikan versi yang disederhanakan sehingga Anda dapat menggunakan sebagai contoh.
#!/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
}
Pemakaian:
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!"
Namun cara non-bash-4 lainnya (yaitu, bash 3, kompatibel dengan 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
Cetakan:
A1 => aaa
B2 => bbb
C3 => ccc
D4 => zzz
Fungsi dengan case
tindakan seperti array asosiatif. Sayangnya itu tidak bisa digunakan return
, jadi harus echo
outputnya, tapi ini bukan masalah, kecuali Anda seorang purist yang menghindari forking subkulit.
Sayang sekali saya tidak melihat pertanyaan sebelumnya - saya telah menulis library shell-framework yang berisi antara lain maps (Associative arrays). Versi terakhirnya dapat ditemukan di sini .
Contoh:
#!/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")"
Saya merasa benar, seperti yang telah disebutkan, bahwa metode berkinerja terbaik adalah menulis kunci / vals ke file, dan kemudian menggunakan grep / awk untuk mengambilnya. Kedengarannya seperti semua jenis IO yang tidak perlu, tetapi cache disk masuk dan membuatnya sangat efisien - jauh lebih cepat daripada mencoba menyimpannya dalam memori menggunakan salah satu metode di atas (seperti yang ditunjukkan oleh benchmark).
Inilah metode cepat dan bersih yang saya suka:
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`
Jika Anda ingin menerapkan nilai tunggal per kunci, Anda juga dapat melakukan sedikit tindakan grep / sed di hput ().
Beberapa tahun yang lalu saya menulis pustaka skrip untuk bash yang mendukung array asosiatif di antara fitur-fitur lain (logging, file konfigurasi, dukungan tambahan untuk argumen baris perintah, menghasilkan bantuan, pengujian unit, dll). Pustaka berisi pembungkus untuk array asosiatif dan secara otomatis beralih ke model yang sesuai (internal untuk bash4 dan meniru untuk versi sebelumnya). Itu disebut kerangka-shell dan dihosting di origo.ethz.ch tetapi hari ini sumber daya ditutup. Jika seseorang masih membutuhkannya, saya dapat membagikannya dengan Anda.
Shell tidak memiliki peta bawaan seperti struktur data, saya menggunakan string mentah untuk mendeskripsikan item seperti itu:
ARRAY=(
"item_A|attr1|attr2|attr3"
"item_B|attr1|attr2|attr3"
"..."
)
ketika mengekstrak item dan atributnya:
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
Ini sepertinya tidak pandai dari jawaban orang lain, tetapi mudah dimengerti untuk orang baru untuk dikupas.
Saya memodifikasi solusi Vadim dengan yang berikut:
####################################################################
# 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; }'
}
Perubahannya adalah map_get untuk mencegahnya mengembalikan kesalahan jika Anda meminta kunci yang tidak ada, meskipun efek sampingnya adalah ia juga akan secara diam-diam mengabaikan peta yang hilang, tetapi lebih cocok untuk kasus penggunaan saya karena saya baru saja ingin memeriksa kunci untuk melewatkan item dalam satu lingkaran.
Balas terlambat, tetapi pertimbangkan untuk mengatasi masalah dengan cara ini, menggunakan bash builtin yang dibaca seperti yang diilustrasikan dalam cuplikan kode dari skrip firewall ufw yang mengikuti. Pendekatan ini memiliki keuntungan menggunakan sebanyak mungkin kumpulan bidang yang dibatasi (tidak hanya 2) seperti yang diinginkan. Kami telah menggunakan | pembatas karena penentu kisaran port mungkin memerlukan titik dua, yaitu 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