سؤال

نحن بحاجة إلى برنامج نصي يحاكي المصفوفات الترابطية أو خريطة مثل بنية البيانات لبرمجة Shell، أي شخص؟

هل كانت مفيدة؟

المحلول

لإضافة إلى عرفان للإجابة ، هنا هو نسخة أقصر وأسرع من get() لأنه لا يتطلب التكرار على محتويات خريطة:

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

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

نصائح أخرى

هناك خيار آخر، إذا لم تكن قابلية النقل هي اهتمامك الرئيسي، وهو استخدام المصفوفات الترابطية المضمنة في الصدفة.يجب أن يعمل هذا في bash 4.0 (متوفر الآن في معظم التوزيعات الرئيسية، ولكن ليس على 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, أو في بعض الحالات قد لا يكون ذلك ضروريًا على الإطلاق.

وغير باش آخر 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 للبحث في هناك أيضا. إذا [[$ فار = ~ / بلاه /]]. أو أيا كان.

وأعتقد أنك بحاجة إلى خطوة الى الوراء والتفكير في ما الخريطة، أو مجموعة النقابي، هو في الحقيقة. كل ما هو وسيلة لتخزين قيمة للحصول على مفتاح معين، والحصول على تلك القيمة مرة أخرى بسرعة وكفاءة. قد تحتاج أيضا لتكون قادرة على تكرار عبر مفاتيح لاسترداد كل زوج قيمة المفتاح، أو حذف مفاتيح والقيم المرتبطة بها.

والآن، والتفكير في بنية البيانات التي تستخدم في كل وقت في قذيفة البرمجة، وحتى مجرد في وعاء دون كتابة السيناريو، والتي لديها هذه الخصائص. الحيرة؟ انها نظام الملفات.

وحقا، كل ما عليك أن يكون هناك مجموعة النقابي في البرمجة قذيفة هو الدليل المؤقت. mktemp -d بك النقابي مجموعة المنشئ:

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

إذا كنت لا تشعر مثل استخدام echo وcat، يمكنك دائما كتابة بعض مغلفة قليلا. وعلى غرار هذه منها الخروج من وعرفان، على الرغم من أنها مجرد إخراج القيمة بدلا من وضع المتغيرات العشوائية مثل $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

تعديل : في هذا النهج هو في الواقع لا بأس به أسرع من البحث الخطي باستخدام الحوار الاقتصادي الاستراتيجي الذي اقترحه السائل، وكذلك أكثر قوة (أنه يسمح المفاتيح والقيم لاحتواء -، =، والفضاء، qnd ": SP:"). والحقيقة أنه يستخدم نظام الملفات لا يجعلها بطيئة. في الواقع لم يضمن هذه الملفات المراد كتابتها إلى القرص إلا إذا كنت استدعاء sync. للملفات المؤقتة مثل هذا مع عمر قصير، فمن غير المستبعد أن الكثيرين منهم لن تكون مكتوبة على القرص.

ولقد فعلت ذلك لمؤشرات قليلة من التعليمات البرمجية عرفان، وتعديل جيري رمز عرفان ووقانون بلدي، وذلك باستخدام برنامج التشغيل التالية:

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

    real    0m0.975s
    user    0m0.280s
    sys     0m0.691s

    $ time ./driver.sh brian 10 5

    real    0m0.226s
    user    0m0.057s
    sys     0m0.123s

    $ time ./driver.sh jerry 10 5

    real    0m0.706s
    user    0m0.228s
    sys     0m0.530s

    $ time ./driver.sh irfan 100 5

    real    0m10.633s
    user    0m4.366s
    sys     0m7.127s

    $ time ./driver.sh brian 100 5

    real    0m1.682s
    user    0m0.546s
    sys     0m1.082s

    $ time ./driver.sh jerry 100 5

    real    0m9.315s
    user    0m4.565s
    sys     0m5.446s

    $ time ./driver.sh irfan 10 500

    real    1m46.197s
    user    0m44.869s
    sys     1m12.282s

    $ time ./driver.sh brian 10 500

    real    0m16.003s
    user    0m5.135s
    sys     0m10.396s

    $ time ./driver.sh jerry 10 500

    real    1m24.414s
    user    0m39.696s
    sys     0m54.834s

    $ time ./driver.sh irfan 1000 5

    real    4m25.145s
    user    3m17.286s
    sys     1m21.490s

    $ time ./driver.sh brian 1000 5

    real    0m19.442s
    user    0m5.287s
    sys     0m10.751s

    $ time ./driver.sh jerry 1000 5

    real    5m29.136s
    user    4m48.926s
    sys     0m59.336s

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

وBash4 يدعم هذا أصلا. لا تستخدم grep أو eval، فهي أقبح الخارقة.

لذلك، إجابة مفصلة مطول مع انظر المثال رمز: https://stackoverflow.com/questions/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; }'
}

مثال:

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

الآن الإجابة على هذا السؤال.

البرامج النصية التالية تحاكي المصفوفات الترابطية في نصوص shell.انها بسيطة وسهلة للغاية لفهم.

الخريطة ليست سوى سلسلة لا تنتهي أبدًا والتي تحتوي على keyvaluepair المحفوظة كـ -name = irfan -التصميم = sse -company = my: sp: own: sp: شركة

يتم استبدال المسافات بـ ':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"
          `
}

لباش 3، هناك قضية معينة لديه حل لطيف وبسيط:

إذا كنت لا تريد التعامل مع الكثير من المتغيرات، أو مفاتيح هي ببساطة غير صالحة معرفات متغيرة، <م> و مكفول مجموعة لديك <م> أقل من 256 البنود ، يمكنك إساءة القيم عودة وظيفة. لا يتطلب هذا الحل أي المستوى الفرعي كقيمة في متناول الجميع كمتغير، ولا أي تكرار لذلك يصرخ أن الأداء. كما انها قابلة للقراءة جدا، تقريبا مثل النسخة باش 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، إلا أنها تخضع لglobbing. من المفيد حقا للتجزئات المجمدة / ثابت منذ البداية، ولكن يمكن للمرء أن كتابة مولد مؤشر من مجموعة hash_keys=().

واحترس، لأنه تخلف عن أول واحد، لذلك قد ترغب في تعيين العنصر الصفري جانبا:

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

ويمكنك استخدام أسماء المتغيرات الحيوية والسماح للمتغيرات أسماء تعمل مثل مفاتيح لhashmap.

وعلى سبيل المثال، إذا كان لديك ملف إدخال مع عمودين، اسم والائتمان، كما في المثال رفع الصوت عاليا، وأنت تريد جمع دخل لكل مستخدم:

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

ورفع الصوت عاليا الأمر سيتم تلخيص كل شيء، وذلك باستخدام المتغيرات الديناميكية كمفاتيح، في شكل <م> خريطة _ $ {شخص} :

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

وتعليقا على هذه التقنيات، وأنا على تطوير جيثب وظيفة يعمل تماما مثل كائن HashMap ، <وأ href = "https://github.com/bnegrao/shell_map" يختلط = "نوفولو noreferrer"> 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!"

يا للأسف أنني لم أر السؤال من قبل - لقد كتبت المكتبة إطار القشرة الذي يحتوي على الخرائط (المصفوفات الترابطية) من بين أشياء أخرى.يمكن العثور على النسخة الأخيرة منه هنا.

مثال:

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

ولقد وجدت هذا صحيح، كما سبق ذكره، أن أفضل طريقة أداء غير لكتابة مفتاح / فال إلى ملف، ومن ثم استخدام البقرى / 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`

إذا كنت تريد فرض قيمة واحدة لكل مفتاح، هل يمكن أيضا القيام البقرى / إجراءات تذكر الحوار الاقتصادي الاستراتيجي في hput ().

وقبل عدة سنوات كتبت مكتبة النصي لسحق التي تدعم المصفوفات الترابطية بين الميزات الأخرى (قطع الأشجار، وملفات التكوين، الدعم المقدم للسيطة سطر الأوامر، وتوليد مساعدة، وحدة الاختبار، وما إلى ذلك). تحتوي المكتبة على مجمع لصفائف النقابي وتتحول إلى نموذج مناسب (الداخلي bash4 وتقليدها للإصدارات السابقة) تلقائيا. كان يطلق عليه قذيفة إطار واستضافت في origo.ethz.ch ولكن اليوم تم إغلاق الموارد. اذا كان هناك من لا يزال يحتاج إليها يمكن أن أشارك معك.

وشل ليس لديهم خريطة المدمج في مثل هيكل البيانات، وأنا استخدم سلسلة الخام لوصف العناصر مثل ما يلي:

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

وهذا يبدو وكأنه لا ذكية من إجابة والآخرين، ولكن من السهل أن نفهم لأشخاص جدد لقذيفة.

وإضافة خيار آخر، إذا 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 '"' 

وأنا المعدلة حل فاديم مع ما يلي:

####################################################################
# 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 من أجل منعه من العودة الأخطاء إذا كنت طلب المفتاح الذي لا وجود له، على الرغم من أن الآثار الجانبية هو أنه سيتم أيضا تجاهل بصمت الخرائط في عداد المفقودين، لكنها تناسب استخدامي لحالة أفضل منذ أردت فقط أن تحقق من وجود مفتاح من أجل تخطي العناصر في الحلقة.

الرد متأخر، لكن فكر في معالجة المشكلة بهذه الطريقة باستخدام bash المدمج يقرأ كما هو موضح في مقتطف التعليمات البرمجية من البرنامج النصي لجدار الحماية ufw التالي.يتمتع هذا الأسلوب بميزة استخدام أكبر عدد ممكن من مجموعات الحقول المحددة (وليس مجموعتين فقط) حسب الرغبة.لقد استخدمنا | محدد لأن محددات نطاق المنفذ قد تتطلب نقطتين، على سبيل المثال 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
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top