อะไรคือสิ่งที่เทียบเท่ากับพจนานุกรม Pythonแต่ใน Bash (ควรทำงานกับ OS X และ Linux)
อะไรคือสิ่งที่เทียบเท่ากับพจนานุกรม Pythonแต่ใน Bash (ควรทำงานกับ OS X และ Linux)
คำตอบ:
Bash 4 สนับสนุนคุณสมบัตินี้อย่างเป็นธรรมชาติ ตรวจสอบให้แน่ใจ hashbang สคริปต์ของคุณเป็น#!/usr/bin/env bash
หรือเพื่อให้คุณไม่สิ้นสุดการใช้#!/bin/bash
sh
ให้แน่ใจว่าคุณกำลังดำเนินการอย่างใดอย่างหนึ่งสคริปต์ของคุณโดยตรงหรือดำเนินการกับscript
bash script
(ไม่จริงรันสคริปต์ทุบตีกับทุบตีไม่เกิดขึ้นและจะได้รับจริงๆสับสน!)
คุณประกาศอาเรย์แบบเชื่อมโยงโดยทำ:
declare -A animals
คุณสามารถเติมองค์ประกอบให้เต็มโดยใช้ตัวดำเนินการกำหนดอาร์เรย์ปกติ ตัวอย่างเช่นหากคุณต้องการแผนที่animal[sound(key)] = animal(value)
:
animals=( ["moo"]="cow" ["woof"]="dog")
หรือรวมพวกเขา:
declare -A animals=( ["moo"]="cow" ["woof"]="dog")
จากนั้นใช้พวกเขาเช่นเดียวกับอาร์เรย์ปกติ ใช้
animals['key']='value'
เพื่อตั้งคา
"${animals[@]}"
เพื่อขยายค่า
"${!animals[@]}"
(สังเกตเห็น!
) เพื่อขยายคีย์
อย่าลืมพูดพวกเขา:
echo "${animals[moo]}"
for sound in "${!animals[@]}"; do echo "$sound - ${animals[$sound]}"; done
ก่อนทุบตี 4 คุณไม่มีอาร์เรย์เชื่อมโยง อย่าใช้eval
เพื่อเลียนแบบพวกเขา หลีกเลี่ยงeval
เช่นโรคระบาดเพราะเป็นภัยจากการเขียนสคริปต์เชลล์ เหตุผลที่สำคัญที่สุดคือการeval
ปฏิบัติต่อข้อมูลของคุณเป็นรหัสที่ปฏิบัติการได้
สิ่งแรกและสำคัญที่สุด : พิจารณาอัปเกรดเป็น bash 4 ซึ่งจะทำให้กระบวนการทั้งหมดง่ายขึ้นสำหรับคุณ
หากมีเหตุผลที่คุณไม่สามารถอัปเกรดdeclare
เป็นตัวเลือกที่ปลอดภัยกว่า มันไม่ได้ประเมินข้อมูลว่าเป็นรหัสทุบตีอย่างที่eval
ทำและไม่อนุญาตให้ทำการฉีดรหัสโดยอำเภอใจได้อย่างง่ายดาย
มาเตรียมคำตอบกันโดยแนะนำแนวคิด:
ก่อนทางอ้อม
$ animals_moo=cow; sound=moo; i="animals_$sound"; echo "${!i}"
cow
ประการที่สองdeclare
:
$ sound=moo; animal=cow; declare "animals_$sound=$animal"; echo "$animals_moo"
cow
นำมารวมกัน:
# Set a value:
declare "array_$index=$value"
# Get a value:
arrayGet() {
local array=$1 index=$2
local i="${array}_$index"
printf '%s' "${!i}"
}
มาใช้กัน:
$ sound=moo
$ animal=cow
$ declare "animals_$sound=$animal"
$ arrayGet animals "$sound"
cow
หมายเหตุ: declare
ไม่สามารถใส่ในฟังก์ชั่น การใช้declare
ฟังก์ชั่น bash ภายในจะเปลี่ยนตัวแปรที่สร้างภายในขอบเขตของฟังก์ชั่นนั้นซึ่งหมายความว่าเราไม่สามารถเข้าถึงหรือแก้ไขอาร์เรย์ระดับโลกได้ (ใน bash 4 คุณสามารถใช้การประกาศ -g เพื่อประกาศตัวแปรโกลบอล - แต่ใน bash 4 คุณสามารถใช้อาร์เรย์ที่เชื่อมโยงได้ตั้งแต่แรกเพื่อหลีกเลี่ยงวิธีแก้ปัญหานี้)
สรุป:
declare -A
สำหรับเชื่อมโยงอาร์เรย์declare
ตัวเลือกหากคุณไม่สามารถอัพเกรดได้awk
แทนและหลีกเลี่ยงปัญหาโดยสิ้นเชิง4.x
y
sudo port install bash
สำหรับผู้ที่ (อย่างชาญฉลาด IMHO) ไม่เต็มใจที่จะสร้างไดเรกทอรีใน PATH สำหรับผู้ใช้ทั้งหมดที่สามารถเขียนได้โดยไม่ต้องเพิ่มสิทธิ์ต่อกระบวนการอย่างชัดเจน
มีการทดแทนพารามิเตอร์ถึงแม้ว่ามันอาจจะไม่ใช่พีซีเช่นกัน ... เหมือนทางอ้อม
#!/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]##*:}"
แน่นอนว่า BASH 4 นั้นดีกว่า แต่ถ้าคุณต้องการแฮ็ก ... แฮ็คเดียวเท่านั้นที่จะทำได้ คุณสามารถค้นหาอาร์เรย์ / แฮชด้วยเทคนิคที่คล้ายกัน
VALUE=${animal#*:}
เพื่อปกป้องกรณีที่ARRAY[$x]="caesar:come:see:conquer"
for animal in "${ARRAY[@]}"; do
นี่คือสิ่งที่ฉันกำลังมองหาที่นี่:
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
สิ่งนี้ใช้ไม่ได้กับฉันด้วย bash 4.1.5:
animals=( ["moo"]="cow" )
คุณสามารถปรับเปลี่ยนส่วนต่อประสาน hput () / hget () เพื่อให้คุณตั้งชื่อแฮชได้ดังนี้
hput() {
eval "$1""$2"='$3'
}
hget() {
eval echo '${'"$1$2"'#hash}'
}
แล้ว
hput capitals France Paris
hput capitals Netherlands Amsterdam
hput capitals Spain Madrid
echo `hget capitals France` and `hget capitals Netherlands` and `hget capitals Spain`
สิ่งนี้ช่วยให้คุณกำหนดแผนที่อื่น ๆ ที่ไม่ขัดแย้ง (เช่น 'rcapitals' ซึ่งค้นหาประเทศตามเมืองหลวง) แต่อย่างใดฉันคิดว่าคุณจะพบว่านี่เป็นสิ่งที่แย่มากและฉลาดในการทำงาน
หากคุณต้องการค้นหาการแฮ็ชอย่างรวดเร็วจริง ๆ มีแฮ็คที่แย่และแย่ซึ่งใช้งานได้ดีจริง ๆ นี่คือ: เขียนคีย์ / ค่าของคุณออกไปที่ไฟล์ชั่วคราวหนึ่งต่อบรรทัดจากนั้นใช้ 'grep "^ $ key"' เพื่อนำพวกมันออกมาโดยใช้ไพพ์ที่มีการตัดหรือ awk หรือ sed หรืออะไรก็ตามเพื่อดึงค่า
อย่างที่ฉันบอกว่ามันฟังดูแย่และดูเหมือนว่ามันควรจะช้าและทำทุกประเภทของ IO ที่ไม่จำเป็น แต่ในทางปฏิบัติมันเร็วมาก (แคชของดิสก์น่ากลัวใช่หรือไม่) แม้กระทั่งแฮชที่มีขนาดใหญ่มาก ตาราง คุณต้องบังคับให้มีความเป็นเอกลักษณ์ของตัวคุณเองคีย์ ฯลฯ แม้ว่าคุณจะมีเพียงไม่กี่ร้อยรายการเท่านั้นไฟล์เอาต์พุต / คำสั่งผสม grep ก็จะเร็วขึ้นอีกเล็กน้อย - จากประสบการณ์ของฉันเร็วขึ้นหลายเท่า นอกจากนี้ยังกินหน่วยความจำน้อยลง
นี่เป็นวิธีหนึ่งในการทำ:
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`
ระบบไฟล์เป็นโครงสร้างแบบต้นไม้ที่สามารถใช้เป็นแผนที่แฮช ตารางแฮชของคุณจะเป็นไดเรกทอรีชั่วคราวคีย์ของคุณจะเป็นชื่อไฟล์และค่าของคุณจะเป็นเนื้อหาไฟล์ ข้อดีคือมันสามารถจัดการแฮชแมพขนาดใหญ่และไม่ต้องใช้เปลือกเฉพาะ
hashtable=$(mktemp -d)
echo $value > $hashtable/$key
value=$(< $hashtable/$key)
แน่นอนช้า แต่ไม่ว่าช้า ผมทดสอบในเครื่องของฉันกับ SSD และbtrfsและมันไม่รอบ3000 องค์ประกอบการอ่าน / เขียนต่อวินาที
mkdir -d
? (ไม่ใช่ 4.3 บน Ubuntu 14. ฉันต้องการใช้mkdir /run/shm/foo
หรือว่า RAM เต็มmkdir /tmp/foo
)
mktemp -d
มีความหมายแทน?
$value=$(< $hashtable/$key)
และvalue=$(< $hashtable/$key)
? ขอบคุณ!
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}
ลบข้อความที่เริ่มต้นจากจุดเริ่มต้นของค่าที่เก็บไว้ในตัวแปรvar
พิจารณาวิธีการแก้ปัญหาโดยใช้ bash builtin อ่านตามตัวอย่างในโค้ดขนาดสั้นจากสคริปต์ไฟร์วอลล์ ufw ที่ตามมา วิธีการนี้มีข้อได้เปรียบของการใช้ชุดเขตข้อมูลที่คั่นด้วยจำนวนมาก (ไม่ใช่แค่ 2) ตามที่ต้องการ เราได้ใช้| คั่นเพราะ specifiers ช่วงพอร์ตอาจต้องลำไส้ใหญ่เช่น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"
ฉันเห็นด้วยกับ @lhunath และคนอื่น ๆ ว่าอาเรย์แบบเชื่อมโยงเป็นวิธีที่จะไปกับ Bash 4 หากคุณติดกับ Bash 3 (OSX, distros เก่าที่คุณไม่สามารถอัปเดตได้) คุณสามารถใช้ expr ได้เช่นกัน และการแสดงออกปกติ ฉันชอบมันมากโดยเฉพาะเมื่อพจนานุกรมไม่ใหญ่เกินไป
เขียนแผนที่ของคุณเป็นสตริง (สังเกตตัวคั่น ',' ที่จุดเริ่มต้นและจุดสิ้นสุดด้วย)
animals=",moo:cow,woof:dog,"
ใช้ regex เพื่อแยกค่า
get_animal {
echo "$(expr "$animals" : ".*,$1:\([^,]*\),.*")"
}
แยกสตริงเพื่อแสดงรายการ
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
}
ตอนนี้คุณสามารถใช้มัน:
$ animal = get_animal "moo"
cow
$ get_animal_items
cow likes to moo
dog likes to woof
ฉันชอบคำตอบของ Al P จริงๆ แต่ต้องการความเป็นเอกลักษณ์ที่บังคับใช้อย่างถูกดังนั้นฉันจึงใช้ขั้นตอนต่อไปอีกขั้น - ใช้ไดเรกทอรี มีข้อ จำกัด บางอย่างที่ชัดเจน (ข้อ จำกัด ของไฟล์ไดเรกทอรีชื่อไฟล์ที่ไม่ถูกต้อง) แต่ควรใช้ในกรณีส่วนใหญ่
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
มันยังทำงานได้ดีขึ้นเล็กน้อยในการทดสอบของฉัน
$ 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
แค่คิดว่าฉันจะขว้างเชียร์!
แก้ไข: การเพิ่ม hdestroy ()
สองสิ่งคุณสามารถใช้หน่วยความจำแทน / tmp ในเคอร์เนล 2.6 โดยใช้ / dev / shm (Redhat) distros อื่น ๆ อาจแตกต่างกันไป นอกจากนี้ hget สามารถนำไปใช้ใหม่ได้โดยใช้การอ่านดังนี้:
function hget {
while read key idx
do
if [ $key = $2 ]
then
echo $idx
return
fi
done < /dev/shm/hashmap.$1
}
นอกจากนี้โดยสมมติว่าคีย์ทั้งหมดเป็นค่าเฉพาะการส่งคืนวงจรลัดสำหรับการอ่านลูปและป้องกันไม่ให้อ่านรายการทั้งหมด หากการใช้งานของคุณสามารถมีคีย์ที่ซ้ำกันได้เพียงแค่ปล่อยให้ผลตอบแทน สิ่งนี้ช่วยประหยัดค่าใช้จ่ายในการอ่านและการฟอร์กทั้ง grep และ awk การใช้ / dev / shm สำหรับการปรับใช้ทั้งสองให้ผลดังต่อไปนี้โดยใช้ time hget บนแฮช 3 รายการเพื่อค้นหารายการสุดท้าย:
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
อ่าน / echo:
$ time echo $(hget FD oracle)
3
real 0m0.004s
user 0m0.000s
sys 0m0.004s
ในการขอร้องหลายครั้งฉันไม่เคยเห็นน้อยลงเลยว่าจะมีการปรับปรุง 50% /dev/shm
ทั้งหมดนี้สามารถนำมาประกอบกับส้อมเหนือหัวเนื่องจากการใช้งานของ
เพื่อนร่วมงานเพิ่งพูดถึงหัวข้อนี้ ฉันใช้ตารางแฮชอย่างอิสระภายในทุบตีและไม่ได้ขึ้นอยู่กับรุ่น 4 จากโพสต์บล็อกของฉันในเดือนมีนาคม 2010 (ก่อนคำตอบบางส่วนที่นี่ ... ) ได้รับสิทธิ์ตารางแฮชในทุบตี :
ฉันก่อนหน้านี้ใช้cksum
เพื่อกัญชา แต่มีตั้งแต่การแปลของ Java hashCode สตริงพื้นเมืองทุบตี / zsh
# 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)
มันไม่ใช่แบบสองทิศทางและวิธีในตัวดีกว่ามาก แต่ก็ไม่ควรใช้จริงๆ การทุบตีทำได้รวดเร็วเพียงครั้งเดียวและสิ่งต่าง ๆ เหล่านี้มักจะเกี่ยวข้องกับความซับซ้อนที่อาจต้องใช้แฮชยกเว้นในของคุณ~/.bashrc
และเพื่อน ๆ
ก่อนที่จะทุบตี 4 ไม่มีวิธีที่ดีในการใช้อาเรย์แบบเชื่อมโยงในการทุบตี ทางออกที่ดีที่สุดของคุณคือการใช้ภาษาที่ตีความแล้วซึ่งมีการสนับสนุนสิ่งต่าง ๆ เช่น awk ในทางตรงกันข้ามทุบตี 4 จะสนับสนุนพวกเขา
สำหรับวิธีที่ดีน้อยกว่าใน bash 3 นี่คือข้อมูลอ้างอิงที่อาจช่วยได้: http://mywiki.wooledge.org/BashFAQ/006
วิธีการแก้ปัญหา Bash 3:
ในการอ่านคำตอบบางข้อฉันได้รวบรวมฟังก์ชั่นเล็ก ๆ น้อย ๆ ไว้ด้วยกันฉันต้องการมีส่วนร่วมช่วยเหลือซึ่งอาจช่วยผู้อื่น
# 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[@])
ฉันยังใช้วิธี bash4 แต่ฉันพบและข้อผิดพลาดที่น่ารำคาญ
ฉันต้องการอัปเดตเนื้อหาอาเรย์แบบไดนามิกดังนั้นฉันจึงใช้วิธีนี้:
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
ฉันพบว่าด้วยการทุบตี 4.3.11 ต่อท้ายคีย์ที่มีอยู่ใน dict ส่งผลให้ผนวกค่าถ้ามีอยู่แล้ว ตัวอย่างเช่นหลังจากการทำซ้ำบางครั้งเนื้อหาของค่าคือ "checkKOcheckKOallCheckOK" และสิ่งนี้ไม่ดี
ไม่มีปัญหากับการทุบตี 4.3.39 เมื่อการใช้คีย์ที่มีอยู่หมายถึงการหยุดค่า actuale ให้คงอยู่หากมีอยู่แล้ว
ฉันแก้ไขนี้เพียงแค่ทำความสะอาด / ประกาศอาเรย์การเชื่อมโยง statusCheck ก่อน cicle:
unset statusCheck; declare -A statusCheck
ฉันสร้าง HashMaps ใน bash 3 โดยใช้ตัวแปรแบบไดนามิก ฉันอธิบายวิธีการทำงานในคำตอบของฉันไปที่:อาร์เรย์ที่เกี่ยวข้องในเชลล์สคริปต์
นอกจากนี้คุณสามารถดูในshell_mapซึ่งเป็นการนำ HashMap มาใช้ใน bash 3