Apa yang setara dengan kamus Python tetapi di Bash (harus bekerja di OS X dan Linux).
Apa yang setara dengan kamus Python tetapi di Bash (harus bekerja di OS X dan Linux).
Jawaban:
Bash 4 secara native mendukung fitur ini. Pastikan hashbang skrip Anda #!/usr/bin/env bash
atau #!/bin/bash
Anda tidak menggunakannya sh
. Pastikan Anda mengeksekusi skrip Anda secara langsung, atau mengeksekusi script
dengan bash script
. (Sebenarnya tidak menjalankan skrip Bash dengan Bash memang terjadi, dan akan sangat membingungkan!)
Anda mendeklarasikan array asosiatif dengan melakukan:
declare -A animals
Anda dapat mengisinya dengan elemen menggunakan operator penetapan array normal. Misalnya, jika Anda ingin memiliki peta animal[sound(key)] = animal(value)
:
animals=( ["moo"]="cow" ["woof"]="dog")
Atau gabungkan mereka:
declare -A animals=( ["moo"]="cow" ["woof"]="dog")
Kemudian gunakan mereka seperti array normal. Menggunakan
animals['key']='value'
untuk menetapkan nilai
"${animals[@]}"
untuk memperluas nilai
"${!animals[@]}"
(perhatikan !
) untuk membuka kunci
Jangan lupa mengutipnya:
echo "${animals[moo]}"
for sound in "${!animals[@]}"; do echo "$sound - ${animals[$sound]}"; done
Sebelum bash 4, Anda tidak memiliki array asosiatif. Jangan gunakan eval
untuk meniru mereka . Hindari eval
seperti wabah, karena merupakan wabah shell scripting. Alasan terpenting adalah itueval
memperlakukan data Anda sebagai kode yang dapat dieksekusi (ada banyak alasan lain juga).
Pertama dan terutama : Pertimbangkan untuk meningkatkan ke bash 4. Ini akan membuat seluruh proses lebih mudah bagi Anda.
Jika ada alasan Anda tidak bisa memutakhirkan, itu declare
adalah opsi yang jauh lebih aman. Itu tidak mengevaluasi data seperti kode basheval
tidak, dan dengan demikian tidak memungkinkan injeksi kode arbitrer dengan mudah.
Mari kita siapkan jawabannya dengan memperkenalkan konsep:
Pertama, tipuan.
$ animals_moo=cow; sound=moo; i="animals_$sound"; echo "${!i}"
cow
Kedua, declare
:
$ sound=moo; animal=cow; declare "animals_$sound=$animal"; echo "$animals_moo"
cow
Satukan mereka:
# Set a value:
declare "array_$index=$value"
# Get a value:
arrayGet() {
local array=$1 index=$2
local i="${array}_$index"
printf '%s' "${!i}"
}
Mari kita gunakan:
$ sound=moo
$ animal=cow
$ declare "animals_$sound=$animal"
$ arrayGet animals "$sound"
cow
Catatan: declare
tidak bisa dimasukkan ke dalam fungsi. Setiap penggunaan declare
fungsi bash dalam mengubah variabel yang dibuatnya lokal untuk lingkup fungsi itu, berarti kita tidak bisa akses atau memodifikasi array global dengan itu. (Dalam bash 4 Anda dapat menggunakan menyatakan -g untuk mendeklarasikan variabel global - tetapi dalam bash 4, Anda dapat menggunakan array asosiatif di tempat pertama, menghindari solusi ini.)
Ringkasan:
declare -A
untuk array asosiatif.declare
opsi jika Anda tidak dapat memutakhirkan.awk
dan hindari masalah ini sama sekali.4.x
dan bukan y
.
sudo port install bash
, bagi mereka (secara bijak, IMHO) yang tidak ingin membuat direktori di PATH untuk semua pengguna yang dapat ditulis tanpa eskalasi hak istimewa per-proses yang eksplisit.
Ada substitusi parameter, meskipun mungkin un-PC juga ... seperti tipuan.
#!/bin/bash
# Array pretending to be a Pythonic dictionary
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
printf "%s is an extinct animal which likes to %s\n" "${ARRAY[1]%%:*}" "${ARRAY[1]##*:}"
Cara BASH 4 tentu saja lebih baik, tetapi jika Anda membutuhkan peretasan ... hanya peretasan yang akan dilakukan. Anda dapat mencari array / hash dengan teknik serupa.
VALUE=${animal#*:}
untuk melindungi kasus di manaARRAY[$x]="caesar:come:see:conquer"
for animal in "${ARRAY[@]}"; do
Inilah yang saya cari di sini:
declare -A hashmap
hashmap["key"]="value"
hashmap["key2"]="value2"
echo "${hashmap["key"]}"
for key in ${!hashmap[@]}; do echo $key; done
for value in ${hashmap[@]}; do echo $value; done
echo hashmap has ${#hashmap[@]} elements
Ini tidak berfungsi untuk saya dengan bash 4.1.5:
animals=( ["moo"]="cow" )
Anda selanjutnya dapat memodifikasi antarmuka hput () / hget () sehingga Anda telah menamai hash sebagai berikut:
hput() {
eval "$1""$2"='$3'
}
hget() {
eval echo '${'"$1$2"'#hash}'
}
lalu
hput capitals France Paris
hput capitals Netherlands Amsterdam
hput capitals Spain Madrid
echo `hget capitals France` and `hget capitals Netherlands` and `hget capitals Spain`
Ini memungkinkan Anda menentukan peta lain yang tidak bertentangan (misalnya, 'rcapitals' yang melakukan pencarian negara oleh ibu kota). Tapi, bagaimanapun juga, saya pikir Anda akan menemukan bahwa ini semua cukup mengerikan, dari segi kinerja.
Jika Anda benar-benar ingin pencarian hash cepat, ada peretasan yang mengerikan, yang sebenarnya bekerja sangat baik. Ini adalah ini: tulis kunci / nilai Anda ke file sementara, satu per baris, kemudian gunakan 'grep "^ $ key"' untuk mengeluarkannya, menggunakan pipa dengan cut atau awk atau sed atau apa pun untuk mengambil nilai.
Seperti yang saya katakan, kedengarannya mengerikan, dan kedengarannya seperti itu harus lambat dan melakukan semua jenis IO yang tidak perlu, tetapi dalam praktiknya sangat cepat (cache disk mengagumkan, bukan?), Bahkan untuk hash yang sangat besar meja. Anda harus memaksakan keunikan kunci sendiri, dll. Bahkan jika Anda hanya memiliki beberapa ratus entri, file output / grep combo akan menjadi sedikit lebih cepat - dalam pengalaman saya beberapa kali lebih cepat. Ini juga memakan lebih sedikit memori.
Inilah satu cara untuk melakukannya:
hinit() {
rm -f /tmp/hashmap.$1
}
hput() {
echo "$2 $3" >> /tmp/hashmap.$1
}
hget() {
grep "^$2 " /tmp/hashmap.$1 | awk '{ print $2 };'
}
hinit capitals
hput capitals France Paris
hput capitals Netherlands Amsterdam
hput capitals Spain Madrid
echo `hget capitals France` and `hget capitals Netherlands` and `hget capitals Spain`
Sistem file adalah struktur pohon yang dapat digunakan sebagai peta hash. Tabel hash Anda akan menjadi direktori sementara, kunci Anda akan menjadi nama file, dan nilai Anda akan menjadi isi file. Keuntungannya adalah ia dapat menangani hashmaps besar, dan tidak memerlukan shell spesifik.
hashtable=$(mktemp -d)
echo $value > $hashtable/$key
value=$(< $hashtable/$key)
Tentu saja, ini lambat, tetapi tidak terlalu lambat. Saya mengujinya di mesin saya, dengan SSD dan btrfs , dan itu sekitar 3000 elemen baca / tulis per detik .
mkdir -d
? (Tidak 4.3, di Ubuntu 14. Saya akan menggunakan mkdir /run/shm/foo
, atau jika itu mengisi RAM mkdir /tmp/foo
,.)
mktemp -d
itu yang dimaksudkan?
$value=$(< $hashtable/$key)
dan value=$(< $hashtable/$key)
? Terima kasih!
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
${var#start}
menghapus teks mulai dari awal nilai yang disimpan dalam variabel var .
Pertimbangkan solusi menggunakan bash builtin read seperti yang diilustrasikan dalam cuplikan kode dari skrip firewall ufw yang mengikuti. Pendekatan ini memiliki keuntungan menggunakan sebanyak set bidang terbatas (tidak hanya 2) seperti yang diinginkan. Kami telah menggunakan | pembatas karena penentu rentang 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
IFS=$'|' read -r first rest <<< "$fields"
Saya setuju dengan @lhunath dan lainnya bahwa array asosiatif adalah cara untuk menggunakan Bash 4. Jika Anda terjebak pada Bash 3 (OSX, distro lama yang tidak dapat Anda perbarui) Anda dapat menggunakan expr, yang seharusnya ada di mana-mana, sebuah string dan ekspresi reguler. Saya suka terutama ketika kamusnya tidak terlalu besar.
Tulis peta Anda sebagai string (perhatikan pemisah ',' juga di awal dan akhir)
animals=",moo:cow,woof:dog,"
Gunakan regex untuk mengekstrak nilai
get_animal {
echo "$(expr "$animals" : ".*,$1:\([^,]*\),.*")"
}
Pisahkan string untuk membuat daftar item
get_animal_items {
arr=$(echo "${animals:1:${#animals}-2}" | tr "," "\n")
for i in $arr
do
value="${i##*:}"
key="${i%%:*}"
echo "${value} likes to $key"
done
}
Sekarang Anda dapat menggunakannya:
$ animal = get_animal "moo"
cow
$ get_animal_items
cow likes to moo
dog likes to woof
Saya sangat menyukai jawaban Al P tetapi ingin keunikan ditegakkan dengan murah jadi saya mengambil satu langkah lebih jauh - menggunakan direktori. Ada beberapa batasan yang jelas (batas file direktori, nama file tidak valid) tetapi harus berfungsi untuk sebagian besar kasus.
hinit() {
rm -rf /tmp/hashmap.$1
mkdir -p /tmp/hashmap.$1
}
hput() {
printf "$3" > /tmp/hashmap.$1/$2
}
hget() {
cat /tmp/hashmap.$1/$2
}
hkeys() {
ls -1 /tmp/hashmap.$1
}
hdestroy() {
rm -rf /tmp/hashmap.$1
}
hinit ids
for (( i = 0; i < 10000; i++ )); do
hput ids "key$i" "value$i"
done
for (( i = 0; i < 10000; i++ )); do
printf '%s\n' $(hget ids "key$i") > /dev/null
done
hdestroy ids
Ini juga melakukan sedikit lebih baik dalam pengujian saya.
$ time bash hash.sh
real 0m46.500s
user 0m16.767s
sys 0m51.473s
$ time bash dirhash.sh
real 0m35.875s
user 0m8.002s
sys 0m24.666s
Kupikir aku akan ikut. Bersulang!
Edit: Menambahkan hdestroy ()
Dua hal, Anda dapat menggunakan memori alih-alih / tmp di kernel 2.6 dengan menggunakan / dev / shm (Redhat) distro lain mungkin berbeda. Hget juga dapat diimplementasikan menggunakan baca sebagai berikut:
function hget {
while read key idx
do
if [ $key = $2 ]
then
echo $idx
return
fi
done < /dev/shm/hashmap.$1
}
Selain itu dengan mengasumsikan bahwa semua tombol unik, kembalikan sirkuit pendek loop baca dan mencegah harus membaca semua entri. Jika implementasi Anda dapat memiliki kunci duplikat, cukup tinggalkan kembalinya. Ini menghemat biaya membaca dan forking baik grep dan awk. Menggunakan / dev / shm untuk kedua implementasi menghasilkan berikut menggunakan waktu hget pada hash entri 3 mencari entri terakhir:
Grep / Awk:
hget() {
grep "^$2 " /dev/shm/hashmap.$1 | awk '{ print $2 };'
}
$ time echo $(hget FD oracle)
3
real 0m0.011s
user 0m0.002s
sys 0m0.013s
Baca / gema:
$ time echo $(hget FD oracle)
3
real 0m0.004s
user 0m0.000s
sys 0m0.004s
pada banyak pemanggilan, saya tidak pernah melihat peningkatan yang kurang dari 50%. Ini semua dapat dikaitkan dengan fork over head, karena penggunaan /dev/shm
.
Seorang rekan kerja baru saja menyebutkan utas ini. Saya sudah menerapkan tabel hash secara mandiri dalam bash, dan itu tidak tergantung pada versi 4. Dari posting blog saya pada Maret 2010 (sebelum beberapa jawaban di sini ...) berjudul Tabel hash di bash :
Saya sebelumnya pernah menggunakan cksum
hash tetapi sejak itu menerjemahkan hashCode string Java ke bash / zsh asli.
# Here's the hashing function
ht() {
local h=0 i
for (( i=0; i < ${#1}; i++ )); do
let "h=( (h<<5) - h ) + $(printf %d \'${1:$i:1})"
let "h |= h"
done
printf "$h"
}
# Example:
myhash[`ht foo bar`]="a value"
myhash[`ht baz baf`]="b value"
echo ${myhash[`ht baz baf`]} # "b value"
echo ${myhash[@]} # "a value b value" though perhaps reversed
echo ${#myhash[@]} # "2" - there are two values (note, zsh doesn't count right)
Ini bukan dua arah, dan cara bawaannya jauh lebih baik, tetapi tidak pula seharusnya digunakan. Bash hanya untuk sekali saja, dan hal-hal seperti itu seharusnya jarang melibatkan kompleksitas yang mungkin membutuhkan hash, kecuali mungkin pada Anda ~/.bashrc
dan teman-teman.
Sebelum bash 4 tidak ada cara yang baik untuk menggunakan array asosiatif di bash. Taruhan terbaik Anda adalah menggunakan bahasa yang ditafsirkan yang sebenarnya memiliki dukungan untuk hal-hal seperti itu, seperti awk. Di sisi lain, bash 4 melakukannya mendukung mereka.
Adapun cara-cara yang kurang baik di bash 3, berikut ini adalah referensi yang mungkin bisa membantu: http://mywiki.wooledge.org/BashFAQ/006
Solusi Bash 3:
Dalam membaca beberapa jawaban saya mengumpulkan fungsi kecil cepat saya ingin berkontribusi kembali yang dapat membantu orang lain.
# Define a hash like this
MYHASH=("firstName:Milan"
"lastName:Adamovsky")
# Function to get value by key
getHashKey()
{
declare -a hash=("${!1}")
local key
local lookup=$2
for key in "${hash[@]}" ; do
KEY=${key%%:*}
VALUE=${key#*:}
if [[ $KEY == $lookup ]]
then
echo $VALUE
fi
done
}
# Function to get a list of all keys
getHashKeys()
{
declare -a hash=("${!1}")
local KEY
local VALUE
local key
local lookup=$2
for key in "${hash[@]}" ; do
KEY=${key%%:*}
VALUE=${key#*:}
keys+="${KEY} "
done
echo $keys
}
# Here we want to get the value of 'lastName'
echo $(getHashKey MYHASH[@] "lastName")
# Here we want to get all keys
echo $(getHashKeys MYHASH[@])
Saya juga menggunakan cara bash4 tapi saya menemukan bug yang mengganggu.
Saya perlu memperbarui konten array asosiatif secara dinamis sehingga saya menggunakan cara ini:
for instanceId in $instanceList
do
aws cloudwatch describe-alarms --output json --alarm-name-prefix $instanceId| jq '.["MetricAlarms"][].StateValue'| xargs | grep -E 'ALARM|INSUFFICIENT_DATA'
[ $? -eq 0 ] && statusCheck+=([$instanceId]="checkKO") || statusCheck+=([$instanceId]="allCheckOk"
done
Saya mengetahui bahwa dengan bash 4.3.11 menambahkan ke kunci yang ada di dict menghasilkan menambahkan nilai jika sudah ada. Jadi misalnya setelah beberapa kali pengulangan konten nilainya adalah "checkKOcheckKOallCheckOK" dan ini tidak baik.
Tidak ada masalah dengan bash 4.3.39 di mana appenging kunci yang ada berarti mengganti nilai aktual jika sudah ada.
Saya memecahkan ini hanya membersihkan / menyatakan array asosiatif statusCheck sebelum cicle:
unset statusCheck; declare -A statusCheck
Saya membuat HashMaps di bash 3 menggunakan variabel dinamis. Saya menjelaskan cara kerjanya dalam jawaban saya untuk: Array asosiatif dalam skrip Shell
Anda juga dapat melihat di shell_map , yang merupakan implementasi HashMap dibuat di bash 3.