วิธีกำหนดตารางแฮชใน Bash


556

อะไรคือสิ่งที่เทียบเท่ากับพจนานุกรม Pythonแต่ใน Bash (ควรทำงานกับ OS X และ Linux)


4
ทุบตีรันสคริปต์ python / perl ... มันยืดหยุ่นมาก!
e2-e4

พิจารณาใช้ xonsh (มันอยู่ใน GitHub)
Oliver

คำตอบ:


938

Bash 4

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

Bash 3

ก่อนทุบตี 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 คุณสามารถใช้อาร์เรย์ที่เชื่อมโยงได้ตั้งแต่แรกเพื่อหลีกเลี่ยงวิธีแก้ปัญหานี้)

สรุป:

  • อัปเกรดเป็น bash 4 และใช้declare -Aสำหรับเชื่อมโยงอาร์เรย์
  • ใช้declareตัวเลือกหากคุณไม่สามารถอัพเกรดได้
  • พิจารณาใช้awkแทนและหลีกเลี่ยงปัญหาโดยสิ้นเชิง

1
@ ริชาร์ด: สมมุติว่าคุณไม่ได้ใช้ทุบตี เป็น hashbang sh ของคุณแทนที่จะทุบตีหรือมิฉะนั้นคุณจะเรียกรหัสของคุณด้วย sh? ลองวางสิทธินี้ก่อนที่จะประกาศคุณ: echo "$ $ BASH_VERSION POSIXLY_CORRECT" ก็ควรเอาท์พุทและไม่ได้4.x y
lhunath

5
ไม่สามารถอัปเกรด: เหตุผลเดียวที่ฉันเขียนสคริปต์ใน Bash สำหรับการพกพา "ทำงานได้ทุกที่" ดังนั้นการพึ่งพาฟีเจอร์ที่ไม่เป็นสากลของ Bash จะใช้วิธีนี้ ซึ่งเป็นความอัปยศเพราะมิฉะนั้นมันจะเป็นทางออกที่ดีสำหรับฉัน!
สตีฟเหยือกน้ำ

3
เป็นเรื่องที่น่าเสียดายที่ OSX จะใช้ค่าเริ่มต้นเป็น Bash 3 เนื่องจากนี่เป็น "ค่าเริ่มต้น" สำหรับผู้คนจำนวนมาก ฉันคิดว่าความกลัวของ ShellShock อาจเป็นการผลักดันที่พวกเขาต้องการ แต่ดูเหมือนจะไม่ใช่
เคน

13
@ken มันเป็นปัญหาสิทธิ์ใช้งาน Bash บน OSX ติดอยู่ที่โครงสร้างลิขสิทธิ์ที่ไม่ใช่ GPLv3 ล่าสุด
lhunath

2
... หรือsudo port install bashสำหรับผู้ที่ (อย่างชาญฉลาด IMHO) ไม่เต็มใจที่จะสร้างไดเรกทอรีใน PATH สำหรับผู้ใช้ทั้งหมดที่สามารถเขียนได้โดยไม่ต้องเพิ่มสิทธิ์ต่อกระบวนการอย่างชัดเจน
Charles Duffy

125

มีการทดแทนพารามิเตอร์ถึงแม้ว่ามันอาจจะไม่ใช่พีซีเช่นกัน ... เหมือนทางอ้อม

#!/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 นั้นดีกว่า แต่ถ้าคุณต้องการแฮ็ก ... แฮ็คเดียวเท่านั้นที่จะทำได้ คุณสามารถค้นหาอาร์เรย์ / แฮชด้วยเทคนิคที่คล้ายกัน


5
ฉันจะเปลี่ยนเป็นVALUE=${animal#*:}เพื่อปกป้องกรณีที่ARRAY[$x]="caesar:come:see:conquer"
เกล็นแจ็คแมน

2
นอกจากนี้ยังมีประโยชน์ในการใส่เครื่องหมายคำพูดคู่ล้อมรอบ $ {ARRAY [@]} ในกรณีที่มีช่องว่างในคีย์หรือค่าเช่นเดียวกับในfor animal in "${ARRAY[@]}"; do
devguydavid

1
แต่ประสิทธิภาพไม่ได้แย่ใช่มั้ย ฉันกำลังคิดว่า O (n * m) ถ้าคุณต้องการเปรียบเทียบกับรายการของคีย์อื่นแทนที่จะเป็น O (n) ด้วย hashmaps ที่เหมาะสม (การค้นหาเวลาคงที่ O (1) สำหรับคีย์เดียว)
CodeManX

1
แนวคิดนี้เกี่ยวกับประสิทธิภาพน้อยกว่าเพิ่มเติมเกี่ยวกับความเข้าใจ / ความสามารถในการอ่านสำหรับผู้ที่มีพื้นหลังเป็น perl, python หรือ bash 4 ช่วยให้คุณสามารถเขียนในลักษณะเดียวกัน
Bubnoff

1
@CoDEmanX: นี่คือแฮ็คการแก้ปัญหาที่ชาญฉลาดและสง่างาม แต่ยังคงเป็นพื้นฐานเพื่อช่วยให้ดวงวิญญาณที่ยากจนยังคงติดอยู่ในปี 2007 ด้วย Bash 3.x คุณไม่สามารถคาดหวัง "แฮชแมพที่เหมาะสม" หรือการพิจารณาประสิทธิภาพในรหัสง่ายๆ
MestreLion

85

นี่คือสิ่งที่ฉันกำลังมองหาที่นี่:

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" )

2
โปรดทราบว่าค่าอาจไม่มีช่องว่างมิฉะนั้นคุณจะเพิ่มองค์ประกอบเพิ่มเติมในครั้งเดียว
rubo77

6
โหวตขึ้นสำหรับ hashmap ["key"] = "value" ไวยากรณ์ที่ฉันก็พบว่าหายไปจากคำตอบที่ได้รับการยอมรับเป็นอย่างอื่น
thomanski

@ @ rubo77 ไม่สำคัญมันเพิ่มหลายปุ่ม วิธีใดในการแก้ไขปัญหานี้
Xeverous

25

คุณสามารถปรับเปลี่ยนส่วนต่อประสาน 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`

1
ที่ดี! คุณสามารถทำซ้ำได้: สำหรับ i ใน $ (คอมไพเลอร์ - แคปปิตอลตัวแปร) ทำ hget "$ i" "" เสร็จแล้ว
zhaorufei

22

เพียงใช้ระบบไฟล์

ระบบไฟล์เป็นโครงสร้างแบบต้นไม้ที่สามารถใช้เป็นแผนที่แฮช ตารางแฮชของคุณจะเป็นไดเรกทอรีชั่วคราวคีย์ของคุณจะเป็นชื่อไฟล์และค่าของคุณจะเป็นเนื้อหาไฟล์ ข้อดีคือมันสามารถจัดการแฮชแมพขนาดใหญ่และไม่ต้องใช้เปลือกเฉพาะ

การสร้าง Hashtable

hashtable=$(mktemp -d)

เพิ่มองค์ประกอบ

echo $value > $hashtable/$key

อ่านองค์ประกอบ

value=$(< $hashtable/$key)

ประสิทธิภาพ

แน่นอนช้า แต่ไม่ว่าช้า ผมทดสอบในเครื่องของฉันกับ SSD และbtrfsและมันไม่รอบ3000 องค์ประกอบการอ่าน / เขียนต่อวินาที


1
ทุบตีรุ่นใดรองรับmkdir -d? (ไม่ใช่ 4.3 บน Ubuntu 14. ฉันต้องการใช้mkdir /run/shm/fooหรือว่า RAM เต็มmkdir /tmp/foo)
Camille Goudeseune

1
บางทีmktemp -dมีความหมายแทน?
Reid Ellis

2
อยากรู้ว่าอะไรคือความแตกต่างระหว่าง$value=$(< $hashtable/$key)และvalue=$(< $hashtable/$key)? ขอบคุณ!
Helin Wang

1
"ทดสอบบนเครื่องของฉัน" นี่เป็นวิธีที่ยอดเยี่ยมในการเบิร์นผ่าน SSD ของคุณ Linux distros บางตัวใช้ tmpfs โดยค่าเริ่มต้น
kirbyfan64sos

ฉันกำลังประมวลผลแฮชประมาณ 50,000 Perl และ PHP ทำผมภายใต้ 1/2 วินาที โหนดใน 1 วินาทีและบางสิ่งบางอย่าง ตัวเลือก FS ฟังดูช้า อย่างไรก็ตามเราสามารถตรวจสอบให้แน่ใจว่าไฟล์นั้นมีอยู่ใน RAM เท่านั้นหรือไม่?
Rolf

14
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

31
ถอนหายใจดูเหมือนจะดูถูกโดยไม่จำเป็นและมันไม่ถูกต้องอยู่ดี หนึ่งจะไม่ทำให้การตรวจสอบการป้อนข้อมูลหนีหรือเข้ารหัส (ดูฉันรู้จริง) ในความกล้าหาญของตารางแฮช แต่ในเสื้อคลุมและโดยเร็วที่สุดหลังจากการป้อนข้อมูล
DigitalRoss

@DigitalRoss คุณสามารถอธิบายการใช้ #hash ใน eal echo '$ {hash' "$ 1" '# # hash}'ได้อย่างไร สำหรับฉันดูเหมือนว่าฉันเป็นความคิดเห็นไม่มากไปกว่านั้น #hash มีความหมายพิเศษที่นี่หรือไม่
Sanjay

@Sanjay ${var#start}ลบข้อความที่เริ่มต้นจากจุดเริ่มต้นของค่าที่เก็บไว้ในตัวแปรvar
jpaugh

11

พิจารณาวิธีการแก้ปัญหาโดยใช้ 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

2
@ CharlieMartin: read เป็นคุณสมบัติที่ทรงพลังมากและใช้งานไม่ได้โดยโปรแกรมเมอร์ bash จำนวนมาก มันช่วยให้การประมวลผลรายการแบบกระเพื่อมคล้ายรูปแบบกะทัดรัด ตัวอย่างเช่นในตัวอย่างข้างต้นเราสามารถถอดองค์ประกอบแรกออกและเก็บส่วนที่เหลือ (เช่นแนวคิดที่คล้ายคลึงกับลำดับแรกและพักผ่อนในเสียงกระเพื่อม) โดยทำดังนี้:IFS=$'|' read -r first rest <<< "$fields"
AsymLabs

6

ฉันเห็นด้วยกับ @lhunath และคนอื่น ๆ ว่าอาเรย์แบบเชื่อมโยงเป็นวิธีที่จะไปกับ Bash 4 หากคุณติดกับ Bash 3 (OSX, distros เก่าที่คุณไม่สามารถอัปเดตได้) คุณสามารถใช้ expr ได้เช่นกัน และการแสดงออกปกติ ฉันชอบมันมากโดยเฉพาะเมื่อพจนานุกรมไม่ใหญ่เกินไป

  1. เลือกตัวคั่น 2 ตัวที่คุณจะไม่ใช้ในคีย์และค่า (เช่น ',' และ ':')
  2. เขียนแผนที่ของคุณเป็นสตริง (สังเกตตัวคั่น ',' ที่จุดเริ่มต้นและจุดสิ้นสุดด้วย)

    animals=",moo:cow,woof:dog,"
  3. ใช้ regex เพื่อแยกค่า

    get_animal {
        echo "$(expr "$animals" : ".*,$1:\([^,]*\),.*")"
    }
  4. แยกสตริงเพื่อแสดงรายการ

    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

5

ฉันชอบคำตอบของ 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 ()


3

สองสิ่งคุณสามารถใช้หน่วยความจำแทน / 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ทั้งหมดนี้สามารถนำมาประกอบกับส้อมเหนือหัวเนื่องจากการใช้งานของ


3

เพื่อนร่วมงานเพิ่งพูดถึงหัวข้อนี้ ฉันใช้ตารางแฮชอย่างอิสระภายในทุบตีและไม่ได้ขึ้นอยู่กับรุ่น 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และเพื่อน ๆ


ลิงก์ในคำตอบนั้นน่ากลัว! หากคุณคลิกคุณจะติดอยู่ในลูปการเปลี่ยนเส้นทาง โปรดอัปเดต
Rakib

1
@MohammadRakibAmin - ใช่เว็บไซต์ของฉันหยุดทำงานและฉันสงสัยว่าฉันจะทำการซ่อมแซมบล็อกของฉันอีกครั้ง ฉันได้อัปเดตลิงค์ด้านบนเป็นเวอร์ชันที่เก็บถาวรแล้ว ขอบคุณสำหรับความสนใจของคุณ!
Adam Katz

2

ก่อนที่จะทุบตี 4 ไม่มีวิธีที่ดีในการใช้อาเรย์แบบเชื่อมโยงในการทุบตี ทางออกที่ดีที่สุดของคุณคือการใช้ภาษาที่ตีความแล้วซึ่งมีการสนับสนุนสิ่งต่าง ๆ เช่น awk ในทางตรงกันข้ามทุบตี 4 จะสนับสนุนพวกเขา

สำหรับวิธีที่ดีน้อยกว่าใน bash 3 นี่คือข้อมูลอ้างอิงที่อาจช่วยได้: http://mywiki.wooledge.org/BashFAQ/006


2

วิธีการแก้ปัญหา 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[@])

ฉันคิดว่านี่เป็นตัวอย่างที่ค่อนข้างดี มันสามารถใช้การทำความสะอาดเล็กน้อย (ไม่มาก) ในรุ่นของฉันฉันได้เปลี่ยนชื่อเป็น 'key' เป็น 'pair' และทำให้ KEY และ VALUE เป็นตัวพิมพ์เล็ก (เพราะฉันใช้ตัวพิมพ์ใหญ่เมื่อส่งออกตัวแปร) ฉันยังเปลี่ยนชื่อ getHashKey เป็น getHashValue และสร้างทั้งคีย์และค่าในตัวเครื่อง (บางครั้งคุณอาจไม่ต้องการให้เป็น local) ใน getHashKeys ฉันไม่ได้กำหนดค่าใด ๆ ฉันใช้เครื่องหมายอัฒภาคเพื่อแยกเนื่องจากค่าของฉันคือ URL

0

ฉันยังใช้วิธี 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

-1

ฉันสร้าง HashMaps ใน bash 3 โดยใช้ตัวแปรแบบไดนามิก ฉันอธิบายวิธีการทำงานในคำตอบของฉันไปที่:อาร์เรย์ที่เกี่ยวข้องในเชลล์สคริปต์

นอกจากนี้คุณสามารถดูในshell_mapซึ่งเป็นการนำ HashMap มาใช้ใน bash 3

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.