อาร์เรย์ที่เกี่ยวข้องในเชลล์สคริปต์


คำตอบ:


20

หากต้องการเพิ่มคำตอบของ Irfanนี่เป็นเวอร์ชันที่สั้นกว่าและเร็วกว่าget()เนื่องจากไม่ต้องทำซ้ำในเนื้อหาแผนที่:

get() {
    mapName=$1; key=$2

    map=${!mapName}
    value="$(echo $map |sed -e "s/.*--${key}=\([^ ]*\).*/\1/" -e 's/:SP:/ /g' )"
}

16
การแยกส่วนย่อยและ sed นั้นแทบจะไม่เหมาะสม Bash4 รองรับสิ่งนี้โดยกำเนิดและ bash3 มีทางเลือกอื่นที่ดีกว่า
lhunath

149

อีกทางเลือกหนึ่งหากการพกพาไม่ใช่ข้อกังวลหลักของคุณคือการใช้อาร์เรย์เชื่อมโยงที่ติดตั้งในเชลล์ สิ่งนี้ควรใช้งานได้ใน bash 4.0 (พร้อมใช้งานแล้วใน distros หลัก ๆ ส่วนใหญ่แม้ว่าจะไม่ใช่ใน OS X เว้นแต่คุณจะติดตั้งด้วยตัวเอง), ksh และ zsh:

declare -A newmap
newmap[name]="Irfan Zulfiqar"
newmap[designation]=SSE
newmap[company]="My Own Company"

echo ${newmap[company]}
echo ${newmap[name]}

ขึ้นอยู่กับเปลือกคุณอาจต้องทำtypeset -A newmapแทนdeclare -A newmapหรือในบางกรณีอาจไม่จำเป็นเลย


ขอบคุณสำหรับการโพสต์คำตอบฉันคิดว่านั่นเป็นวิธีที่ดีที่สุดสำหรับผู้ชายที่จะใช้ bash 4.0 ขึ้นไป
Irfan Zulfiqar

ฉันจะเพิ่มกากบาทเล็กน้อยเพื่อให้แน่ใจว่าได้ตั้งค่า BASH_VERSION แล้วและ> = 4 และใช่ BASH 4 นั้นเจ๋งจริงๆ!
Tim Post

ฉันใช้อะไรแบบนี้ วิธีใดที่ดีที่สุดในการ "จับ" ข้อผิดพลาดที่ไม่มีดัชนีอาร์เรย์ / ตัวห้อย ตัวอย่างเช่นจะเกิดอะไรขึ้นถ้าฉันใช้ตัวห้อยเป็นตัวเลือกบรรทัดคำสั่งและผู้ใช้พิมพ์ผิดและป้อน "designatio" ฉันได้รับข้อผิดพลาด "ตัวห้อยอาร์เรย์ที่ไม่ถูกต้อง" แต่ไม่สามารถตรวจสอบความถูกต้องของอินพุตในขณะค้นหาอาร์เรย์ได้อย่างไรหากเป็นไปได้
เย

3
@Jer มันค่อนข้างคลุมเครือ แต่ในการตรวจสอบว่าตัวแปรถูกตั้งค่าในเชลล์หรือไม่คุณสามารถใช้test -z ${variable+x}( xไม่สำคัญนั่นอาจเป็นสตริงใดก็ได้) สำหรับอาร์เรย์ที่เชื่อมโยงใน Bash คุณสามารถทำได้เช่นเดียวกัน ใช้test -z ${map[key]+x}.
Brian Campbell

95

อีกวิธีที่ไม่ทุบตี 4

#!/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"

คุณสามารถใส่คำสั่ง if เพื่อค้นหาในนั้นได้เช่นกัน ถ้า [[$ var = ~ / blah /]] หรืออะไรก็ตาม


2
วิธีนี้ดีเมื่อคุณไม่มี Bash 4 แน่นอน แต่ฉันคิดว่าบรรทัดที่ดึงค่า VALUE จะปลอดภัยกว่าด้วยวิธีนี้: VALUE = $ {animal # *:} ด้วยอักขระเพียง # ตัวเดียวการจับคู่จะหยุดที่ ":" ตัวแรก ซึ่งอนุญาตให้มีค่า ":" ด้วย
Ced-le-pingouin

@ Ced-le-pingouin ~ นั่นเป็นจุดที่ดีมาก! ฉันฟังไม่ทัน ฉันได้แก้ไขโพสต์ของฉันเพื่อแสดงถึงการปรับปรุงที่คุณแนะนำ
Bubnoff

1
เป็นการจำลองอาร์เรย์ที่เชื่อมโยงกันอย่างแฮ็กโดยใช้การทดแทนพารามิเตอร์ BASH พารามิเตอร์ย่อย "คีย์" จะแทนที่ทุกอย่างก่อนโคลอนและรูปแบบค่าจะแทนที่ทุกอย่างหลังลำไส้ใหญ่ คล้ายกับการจับคู่สัญลักษณ์ตัวแทน regex ดังนั้นไม่ใช่อาร์เรย์เชื่อมโยงที่แท้จริง ไม่แนะนำเว้นแต่คุณต้องการวิธีที่เข้าใจง่ายในการทำฟังก์ชันแฮช / เชื่อมโยงอาร์เรย์เหมือนใน BASH 3 หรือต่ำกว่า มันใช้งานได้! เพิ่มเติมที่นี่: tldp.org/LDP/abs/html/parameter-substitution.html#PSOREX2
Bubnoff

1
สิ่งนี้ไม่ได้ใช้งานอาร์เรย์ที่เชื่อมโยงเนื่องจากไม่มีวิธีการค้นหารายการด้วยคีย์ มีเพียงวิธีค้นหาแต่ละคีย์ (และค่า) จากดัชนีตัวเลข (สามารถค้นหารายการได้โดยใช้คีย์โดยการวนซ้ำผ่านอาร์เรย์ แต่นั่นไม่ใช่สิ่งที่ต้องการสำหรับอาร์เรย์ที่เชื่อมโยงกัน)
Eric Postpischil

@EricPostpischil ที่แท้ทรู. มันเป็นเพียงการแฮ็ก ช่วยให้บุคคลสามารถใช้ไวยากรณ์ที่คุ้นเคยในการตั้งค่าได้ แต่ยังคงต้องทำซ้ำผ่านอาร์เรย์ตามที่คุณพูด ฉันพยายามชัดเจนในความคิดเห็นก่อนหน้านี้ว่าไม่ใช่อาร์เรย์ที่เชื่อมโยงกันอย่างแน่นอนและฉันไม่แนะนำด้วยซ้ำหากคุณมีทางเลือกอื่น ในมุมมองของฉันมีจุดเดียวที่ชอบคือเขียนและใช้งานได้ง่ายสำหรับผู้ที่คุ้นเคยกับภาษาอื่น ๆ เช่น Python หากคุณอยู่ในจุดที่คุณต้องการใช้งานอาร์เรย์เชื่อมโยงใน BASH 3 จริงๆคุณอาจต้องย้อนกลับไปดูขั้นตอนของคุณอีกเล็กน้อย
Bubnoff

34

ฉันคิดว่าคุณต้องย้อนกลับไปและคิดว่าจริงๆแล้วแผนที่หรืออาร์เรย์แบบเชื่อมโยงคืออะไร ทั้งหมดนี้เป็นวิธีการจัดเก็บค่าสำหรับคีย์หนึ่ง ๆ และรับมูลค่านั้นกลับคืนมาอย่างรวดเร็วและมีประสิทธิภาพ คุณอาจต้องการที่จะสามารถทำซ้ำบนคีย์เพื่อดึงทุกคู่ค่าคีย์หรือลบคีย์และค่าที่เกี่ยวข้อง

ตอนนี้ให้นึกถึงโครงสร้างข้อมูลที่คุณใช้ตลอดเวลาในเชลล์สคริปต์และแม้แต่ในเชลล์โดยไม่ต้องเขียนสคริปต์ก็มีคุณสมบัติเหล่านี้ นิ่งงัน? มันคือระบบไฟล์

จริงๆแล้วสิ่งที่คุณต้องมีในอาร์เรย์ที่เชื่อมโยงในการเขียนโปรแกรมเชลล์คือไดเร็กทอรีชั่วคราว mktemp -dเป็นตัวสร้างอาร์เรย์ที่เชื่อมโยงของคุณ:

prefix=$(basename -- "$0")
map=$(mktemp -dt ${prefix})
echo >${map}/key somevalue
value=$(cat ${map}/key)

หากคุณไม่ต้องการใช้echoและcatคุณสามารถเขียนกระดาษห่อเล็ก ๆ ได้ตลอดเวลา สิ่งเหล่านี้จำลองมาจาก Irfan แม้ว่าพวกเขาจะส่งออกค่าแทนที่จะตั้งค่าตัวแปรตามอำเภอใจเช่น$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

แก้ไข : วิธีนี้ค่อนข้างเร็วกว่าการค้นหาเชิงเส้นโดยใช้ sed ที่ผู้ถามแนะนำและมีประสิทธิภาพมากกว่า (ช่วยให้คีย์และค่ามี -, =, space, qnd ": SP:") ความจริงที่ว่ามันใช้ระบบไฟล์ไม่ได้ทำให้มันช้า ไฟล์เหล่านี้ไม่รับประกันว่าจะถูกเขียนลงดิสก์เว้นแต่คุณจะโทรsync; สำหรับไฟล์ชั่วคราวแบบนี้ที่มีอายุการใช้งานสั้นไม่น่าเป็นไปได้ที่ไฟล์เหล่านี้จะไม่ถูกเขียนลงดิสก์

ฉันทำเกณฑ์มาตรฐานของรหัสของ Irfan การปรับเปลี่ยนรหัสของ Irfan และรหัสของ Jerry โดยใช้โปรแกรมไดรเวอร์ต่อไปนี้:

#!/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

ผลลัพธ์:

    $ time ./driver.sh irfan 10 5

    0m0.975s จริง
    ผู้ใช้ 0m0.280s
    sys 0m0.691s

    $ time ./driver.sh ไบรอัน 10 5

    0m0.226s จริง
    ผู้ใช้ 0m0.057s
    sys 0m0.123s

    $ time ./driver.sh เจอร์รี่ 10 5

    0m0.706s จริง
    ผู้ใช้ 0m0.228s
    sys 0m0.530s

    $ time ./driver.sh irfan 100 5

    0m10.633s จริง
    ผู้ใช้ 0m4.366s
    sys 0m7.127s

    $ time ./driver.sh ไบรอัน 100 5

    0m1.682s จริง
    ผู้ใช้ 0m0.546s
    sys 0m1.082s

    $ time ./driver.sh เจอร์รี่ 100 5

    จริง 0m9.315s
    ผู้ใช้ 0m4.565s
    sys 0m5.446s

    $ time ./driver.sh irfan 10500

    1m46.197s จริง
    ผู้ใช้ 0m44.869s
    sys 1m12.282s

    $ time ./driver.sh ไบรอัน 10500

    0m16.003s จริง
    ผู้ใช้ 0m5.135s
    sys 0m10.396s

    $ time ./driver.sh เจอร์รี่ 10500

    1m24.414s จริง
    ผู้ใช้ 0m39.696s
    sys 0m54.834s

    $ time ./driver.sh irfan 1000 5

    4m25.145s จริง
    ผู้ใช้ 3m17.286s
    sys 1m21.490s

    $ time ./driver.sh ไบรอัน 1000 5

    0m19.442s จริง
    ผู้ใช้ 0m5.287s
    sys 0m10.751s

    $ time ./driver.sh เจอร์รี่ 1000 5

    5 นาที 29.136 วินาทีจริง
    ผู้ใช้ 4m48.926s
    sys 0m59.336s


1
ฉันไม่คิดว่าคุณควรใช้ระบบไฟล์สำหรับแผนที่ซึ่งโดยพื้นฐานแล้วจะใช้ IO สำหรับบางสิ่งที่คุณทำได้เร็วพอสมควรในหน่วยความจำ
Irfan Zulfiqar

9
ไฟล์ไม่จำเป็นต้องถูกเขียนลงในดิสก์ เว้นแต่คุณจะเรียกการซิงค์ระบบปฏิบัติการอาจปล่อยไว้ในหน่วยความจำ รหัสของคุณเรียกร้องให้ sed และทำการค้นหาเชิงเส้นหลายครั้งซึ่งทั้งหมดนี้ช้ามาก ฉันทำการวัดประสิทธิภาพอย่างรวดเร็วและเวอร์ชันของฉันเร็วขึ้น 5-35 เท่า
Brian Campbell

ในทางกลับกันอาร์เรย์เนทีฟของ bash4 เป็นแนวทางที่ดีกว่าอย่างมากและใน bash3 คุณยังสามารถเก็บทุกอย่างออกจากดิสก์ได้โดยไม่ต้องใช้การประกาศและทิศทาง
lhunath

7
"fast" และ "shell" ไม่ได้อยู่ด้วยกันจริงๆ: ไม่แน่นอนสำหรับปัญหาด้านความเร็วที่เรากำลังพูดถึงในระดับ "หลีกเลี่ยงย่อส่วน IO" คุณสามารถค้นหาและใช้ / dev / shm เพื่อรับประกันว่าไม่มี IO
jmtd

2
วิธีนี้ทำให้ฉันประหลาดใจและยอดเยี่ยมมาก ยังคงเป็นจริงในปี 2559 มันควรจะเป็นคำตอบที่ยอมรับได้จริงๆ
Gordon

7

Bash4 รองรับสิ่งนี้โดยกำเนิด อย่าใช้grepหรือevalเป็นแฮ็กที่น่าเกลียดที่สุด

สำหรับคำตอบโดยละเอียดพร้อมรหัสตัวอย่างโปรดดู: /programming/3467959


7
####################################################################
# 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; }'
}

ตัวอย่าง:

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

4

ตอนนี้กำลังตอบคำถามนี้

สคริปต์ต่อไปนี้จำลองอาร์เรย์ที่เชื่อมโยงในเชลล์สคริปต์ มันง่ายและเข้าใจง่ายมาก

แผนที่ไม่มีอะไรนอกจากสตริงที่ไม่มีวันสิ้นสุดที่มี keyValuePair บันทึกเป็น --name = Irfan --designation = SSE --company = My: SP: Own: SP: Company

ช่องว่างจะถูกแทนที่ด้วย ': SP:' สำหรับค่า

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

แก้ไข:เพิ่งเพิ่มวิธีอื่นในการดึงคีย์ทั้งหมด

getKeySet() {
    if [ "$#" != 1 ]; 
    then 
        exit 1; 
    fi

    mapName=$1; 

    eval map="\"\$$mapName\""

    keySet=`
           echo $map | 
           sed -e "s/=[^ ]*//g" -e "s/\([ ]*\)--/\1/g"
          `
}

1
คุณกำลังรับevalข้อมูลราวกับว่าเป็นรหัสทุบตีและยิ่งไปกว่านั้น: คุณไม่ได้อ้างอย่างถูกต้อง ทั้งสองทำให้เกิดข้อบกพร่องจำนวนมากและการฉีดรหัสโดยพลการ
lhunath

3

สำหรับ Bash 3 มีบางกรณีที่มีวิธีแก้ปัญหาที่ดีและเรียบง่าย:

หากคุณไม่ต้องการจัดการกับตัวแปรจำนวนมากหรือคีย์เป็นเพียงตัวระบุตัวแปรที่ไม่ถูกต้องและอาร์เรย์ของคุณได้รับการรับรองว่ามีรายการน้อยกว่า 256 รายการคุณสามารถละเมิดฟังก์ชันการส่งคืนค่าได้ โซลูชันนี้ไม่จำเป็นต้องใช้ subshell ใด ๆ เนื่องจากค่าพร้อมใช้งานเป็นตัวแปรหรือการวนซ้ำใด ๆ เพื่อให้ประสิทธิภาพการทำงานดังขึ้น นอกจากนี้ยังสามารถอ่านได้เกือบจะเหมือนกับเวอร์ชัน Bash 4

นี่คือเวอร์ชันพื้นฐานที่สุด:

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[$?]}

โปรดจำไว้ว่าให้ใช้เครื่องหมายคำพูดเดี่ยวในcaseมิฉะนั้นอาจมีการปัดเศษ มีประโยชน์มากสำหรับแฮชแบบคงที่ / แช่แข็งตั้งแต่เริ่มต้น แต่เราสามารถเขียนตัวสร้างดัชนีจากhash_keys=()อาร์เรย์ได้

ระวังมันเป็นค่าเริ่มต้นขององค์ประกอบแรกดังนั้นคุณอาจต้องการแยกองค์ประกอบ zeroth:

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

ข้อแม้: ตอนนี้ความยาวไม่ถูกต้อง

หรือหากคุณต้องการคงการจัดทำดัชนีแบบศูนย์ไว้คุณสามารถสงวนค่าดัชนีอื่นและป้องกันคีย์ที่ไม่มีอยู่จริง แต่อ่านได้น้อยลง:

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[$?]}

หรือเพื่อให้ความยาวถูกต้องให้หักล้างดัชนีทีละรายการ:

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))]}

2

คุณสามารถใช้ชื่อตัวแปรแบบไดนามิกและปล่อยให้ชื่อตัวแปรทำงานเหมือนคีย์ของแฮชแมป

ตัวอย่างเช่นหากคุณมีไฟล์อินพุตที่มีสองคอลัมน์ชื่อเครดิตดังตัวอย่างการร้องและคุณต้องการรวมรายได้ของผู้ใช้แต่ละคน:

Mary 100
John 200
Mary 50
John 300
Paul 100
Paul 400
David 100

การร้องคำสั่งจะรวมทุกอย่างโดยใช้ตัวแปรแบบไดนามิกเป็นคีย์ในรูปแบบของแผนที่ _ $ {person} :

while read -r person money; ((map_$person+=$money)); done < <(cat INCOME_REPORT.log)

หากต้องการอ่านผลลัพธ์:

set | grep map

ผลลัพธ์จะเป็น:

map_David=100
map_John=500
map_Mary=150
map_Paul=500

Elaborating เกี่ยวกับเทคนิคเหล่านี้ผมกำลังพัฒนาบน GitHub ฟังก์ชั่นที่ใช้งานได้เช่นเดียวกับที่HashMap วัตถุ , shell_map

ในการสร้าง " อินสแตนซ์ HashMap " ฟังก์ชัน shell_mapสามารถสร้างสำเนาของตัวเองภายใต้ชื่อที่แตกต่างกัน สำเนาฟังก์ชันใหม่แต่ละรายการจะมีตัวแปร $ FUNCNAME ที่แตกต่างกัน จากนั้น $ FUNCNAME จะใช้เพื่อสร้างเนมสเปซสำหรับแต่ละอินสแตนซ์แผนที่

คีย์แผนที่เป็นตัวแปรส่วนกลางในรูปแบบ $ FUNCNAME_DATA_ $ KEY โดยที่ $ KEY เป็นคีย์ที่เพิ่มลงในแผนที่ ตัวแปรเหล่านี้เป็นตัวแปรแบบไดนามิก

ร้องฉันจะใส่เวอร์ชันที่เรียบง่ายเพื่อให้คุณสามารถใช้เป็นตัวอย่างได้

#!/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
}

การใช้งาน:

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

2

อีกวิธีหนึ่งที่ไม่ใช่ bash-4 (เช่น bash 3, Mac-compatible):

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

พิมพ์:

A1 => aaa
B2 => bbb
C3 => ccc
D4 => zzz

ฟังก์ชันที่มีการcaseกระทำเหมือนอาร์เรย์เชื่อมโยง น่าเสียดายที่ไม่สามารถใช้งานได้returnดังนั้นจึงต้องมีechoเอาต์พุต แต่นี่ไม่ใช่ปัญหาเว้นแต่คุณจะเป็นคนเจ้าระเบียบที่หลีกเลี่ยงการฟอร์ก subshells


1

สิ่งที่น่าเสียดายที่ฉันไม่เห็นคำถามมาก่อน - ฉันได้เขียนเชลล์เฟรมเวิร์กของไลบรารีซึ่งมีแผนที่ (อาร์เรย์ที่เกี่ยวข้อง) รุ่นสุดท้ายของมันสามารถพบได้ที่นี่

ตัวอย่าง:

#!/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")"

1

การเพิ่มตัวเลือกอื่นถ้า jq พร้อมใช้งาน:

export NAMES="{
  \"Mary\":\"100\",
  \"John\":\"200\",
  \"Mary\":\"50\",
  \"John\":\"300\",
  \"Paul\":\"100\",
  \"Paul\":\"400\",
  \"David\":\"100\"
}"
export NAME=David
echo $NAMES | jq --arg v "$NAME" '.[$v]' | tr -d '"' 

0

ฉันพบว่ามันเป็นความจริงดังที่ได้กล่าวไปแล้วว่าวิธีที่มีประสิทธิภาพดีที่สุดคือการเขียนคีย์ / vals ลงในไฟล์จากนั้นใช้ grep / awk เพื่อดึงข้อมูล ดูเหมือนว่า IO ที่ไม่จำเป็นทุกประเภท แต่แคชของดิสก์จะเริ่มทำงานและทำให้มีประสิทธิภาพสูง - เร็วกว่าการพยายามจัดเก็บไว้ในหน่วยความจำโดยใช้วิธีใดวิธีหนึ่งข้างต้น (ดังที่แสดงในเกณฑ์มาตรฐาน)

นี่เป็นวิธีที่รวดเร็วและสะอาดที่ฉันชอบ:

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`

หากคุณต้องการบังคับใช้ค่าเดียวต่อคีย์คุณสามารถดำเนินการ grep / sed เล็กน้อยใน hput () ได้


0

หลายปีก่อนฉันเขียนไลบรารีสคริปต์สำหรับ bash ซึ่งสนับสนุนอาร์เรย์ที่เชื่อมโยงระหว่างคุณสมบัติอื่น ๆ (การบันทึกไฟล์การกำหนดค่าการสนับสนุนเพิ่มเติมสำหรับอาร์กิวเมนต์บรรทัดคำสั่งสร้างวิธีใช้การทดสอบหน่วย ฯลฯ ) ไลบรารีมีตัวห่อสำหรับอาร์เรย์ที่เชื่อมโยงและเปลี่ยนเป็นโมเดลที่เหมาะสมโดยอัตโนมัติ (ภายในสำหรับ bash4 และจำลองสำหรับเวอร์ชันก่อนหน้า) ถูกเรียกว่าเชลล์เฟรมเวิร์กและโฮสต์ที่ origo.ethz.ch แต่วันนี้ทรัพยากรถูกปิด หากยังมีคนต้องการฉันสามารถแบ่งปันกับคุณได้


อาจจะคุ้มค่าที่จะติดบน github
Mark K Cowan

0

เชลล์ไม่มีแผนที่ในตัวเช่นโครงสร้างข้อมูลฉันใช้สตริงดิบเพื่ออธิบายรายการเช่นนั้น:

ARRAY=(
    "item_A|attr1|attr2|attr3"
    "item_B|attr1|attr2|attr3"
    "..."
)

เมื่อแยกไอเท็มและคุณสมบัติ:

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

ดูเหมือนจะไม่ฉลาดไปกว่าคำตอบของคนอื่น แต่เข้าใจง่ายสำหรับคนใหม่ ๆ


-1

ฉันแก้ไขโซลูชันของ Vadim ด้วยสิ่งต่อไปนี้:

####################################################################
# 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; }'
}

การเปลี่ยนแปลงคือ map_get เพื่อป้องกันไม่ให้ส่งคืนข้อผิดพลาดหากคุณขอคีย์ที่ไม่มีอยู่แม้ว่าผลข้างเคียงคือมันจะเพิกเฉยต่อแผนที่ที่หายไป แต่มันก็เหมาะกับกรณีการใช้งานของฉันดีกว่าเนื่องจากฉันเพิ่ง ต้องการตรวจสอบคีย์เพื่อข้ามรายการแบบวนซ้ำ


-1

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