Mảng liên kết trong tập lệnh Shell


116

Chúng tôi yêu cầu một tập lệnh mô phỏng các mảng kết hợp hoặc Bản đồ như cấu trúc dữ liệu cho Shell Scripting, bất kỳ phần thân nào?

Câu trả lời:


20

Để thêm vào câu trả lời của Irfan , đây là phiên bản ngắn hơn và nhanh hơn get()vì nó không yêu cầu lặp lại nội dung bản đồ:

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

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

16
rèn một subshell và sed hầu như không tối ưu. Bash4 hỗ trợ điều này tự nhiên và bash3 có các lựa chọn thay thế tốt hơn.
lhunath

149

Một tùy chọn khác, nếu tính di động không phải là mối quan tâm chính của bạn, là sử dụng các mảng kết hợp được tích hợp vào trình bao. Điều này sẽ hoạt động trong bash 4.0 (hiện có sẵn trên hầu hết các bản phát hành chính, mặc dù không phải trên OS X trừ khi bạn tự cài đặt nó), ksh và zsh:

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

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

Tùy thuộc vào vỏ, bạn có thể cần phải typeset -A newmapthay thế declare -A newmaphoặc trong một số trường hợp có thể không cần thiết chút nào.


Cảm ơn bạn đã đăng câu trả lời, tôi nghĩ rằng đó sẽ là cách tốt nhất để làm điều đó cho những người sẽ sử dụng bash 4.0 trở lên.
Irfan Zulfiqar

Tôi sẽ thêm một ít bùn để đảm bảo BASH_VERSION được đặt và> = 4. Và vâng, BASH 4 thực sự rất tuyệt!
Tim Post

Tôi đang sử dụng một cái gì đó như thế này. Cách tốt nhất để "bắt" lỗi trong đó chỉ số / chỉ mục mảng không tồn tại là gì? Ví dụ, điều gì sẽ xảy ra nếu tôi lấy chỉ mục dưới dạng tùy chọn dòng lệnh và người dùng đã tạo một lỗi đánh máy và nhập "designatio"? Tôi gặp lỗi "đăng ký mảng xấu" nhưng không biết cách xác thực đầu vào tại thời điểm tra cứu mảng, nếu điều đó có thể?
Jer

3
@Jer Nó khá tối nghĩa, nhưng để xác định xem một biến được đặt trong shell, bạn có thể sử dụng test -z ${variable+x}( xkhông quan trọng, đó có thể là bất kỳ chuỗi nào). Đối với một mảng kết hợp trong Bash, bạn có thể làm tương tự; sử dụng test -z ${map[key]+x}.
Brian Campbell

95

Một cách khác không bash 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"

Bạn cũng có thể ném một câu lệnh if để tìm kiếm trong đó. nếu [[$ var = ~ / blah /]]. hay bất cứ cái gì.


2
Phương pháp này tốt khi bạn không có Bash 4. Nhưng tôi nghĩ rằng dòng tìm nạp GIÁ TRỊ sẽ an toàn hơn theo cách này: VALUE = $ {động vật # *:}. Chỉ với một ký tự #, kết quả khớp sẽ dừng ở ":" đầu tiên. Điều đó cho phép các giá trị chứa ":", quá.
Ced-le-pingouin

@ Ced-le-pingouin ~ Đó là một điểm tuyệt vời! Tôi đã không nắm bắt được điều đó. Tôi đã chỉnh sửa bài viết của mình để phản ánh những cải tiến được đề xuất của bạn.
Bubnoff

1
Nó là một mô phỏng khá hackish của các mảng kết hợp bằng cách sử dụng thay thế tham số BASH. "Key" param-sub thay thế mọi thứ trước dấu hai chấm và mẫu giá trị thay thế mọi thứ sau dấu hai chấm. Tương tự như một trận đấu ký tự đại diện regex. Vì vậy, KHÔNG phải là một mảng kết hợp thực sự. Không được đề xuất trừ khi bạn cần một cách dễ hiểu để thực hiện chức năng giống như mảng băm / liên kết trong BASH 3 trở xuống. Nó hoạt động mặc dù! Xem thêm tại đây: tldp.org/LDP/abs/html/parameter-substlation.html#PSOREX2
Bubnoff

1
Điều này không thực hiện một mảng kết hợp bởi vì nó không cung cấp một cách để tìm kiếm một mục bằng khóa. Nó chỉ cung cấp một cách để tìm từng khóa (và giá trị) từ một chỉ mục số. (Một mục có thể được tìm thấy bằng khóa bằng cách lặp qua mảng, nhưng đó không phải là điều mong muốn cho một mảng kết hợp.)
Eric Postpischil 22/12/18

@EricPostpischil Đúng. Nó chỉ là một hack. Nó cho phép một người sử dụng cú pháp quen thuộc trong thiết lập nhưng vẫn yêu cầu lặp qua mảng như bạn nói. Tôi đã cố gắng để rõ ràng trong nhận xét trước đây của tôi rằng nó chắc chắn không phải là một mảng kết hợp và tôi thậm chí không đề xuất nó nếu bạn có lựa chọn thay thế. Điểm duy nhất có lợi cho nó, theo quan điểm của tôi, là nó dễ viết và sử dụng cho những người quen thuộc với các ngôn ngữ khác như Python. Nếu bạn đang ở một điểm mà bạn thực sự muốn triển khai các mảng kết hợp trong BASH 3 thì bạn có thể cần phải lấy lại các bước của mình một chút.
Bubnoff

34

Tôi nghĩ rằng bạn cần phải lùi lại và suy nghĩ về những gì một bản đồ, hoặc mảng kết hợp, thực sự là gì. Tất cả đó là một cách để lưu trữ một giá trị cho một khóa nhất định và lấy lại giá trị đó một cách nhanh chóng và hiệu quả. Bạn cũng có thể muốn lặp lại các khóa để truy xuất mọi cặp giá trị khóa hoặc xóa các khóa và các giá trị liên quan của chúng.

Bây giờ, hãy nghĩ về một cấu trúc dữ liệu bạn sử dụng toàn bộ thời gian trong kịch bản lệnh shell và thậm chí chỉ trong trình bao mà không viết tập lệnh, có các thuộc tính này. Bướng bỉnh? Đó là hệ thống tập tin.

Thực sự, tất cả những gì bạn cần để có một mảng kết hợp trong lập trình shell là một thư mục tạm thời. mktemp -dlà hàm tạo mảng kết hợp của bạn:

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

Nếu bạn không thích sử dụng echocat, bạn luôn có thể viết một số hàm bao nhỏ; những cái này được mô hình hóa từ Irfan, mặc dù chúng chỉ xuất giá trị chứ không đặt các biến tùy ý như $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

chỉnh sửa : Cách tiếp cận này thực sự nhanh hơn một chút so với tìm kiếm tuyến tính bằng cách sử dụng sed do người hỏi đề xuất, cũng như mạnh mẽ hơn (nó cho phép các khóa và giá trị chứa -, =, dấu cách, qnd ": SP:"). Thực tế là nó sử dụng hệ thống tập tin không làm cho nó chậm; những tệp này thực sự không bao giờ được đảm bảo ghi vào đĩa trừ khi bạn gọi sync; đối với các tệp tạm thời như thế này với thời gian tồn tại ngắn, không có khả năng nhiều tệp sẽ không bao giờ được ghi vào đĩa.

Tôi đã thực hiện một vài điểm chuẩn về mã của Irfan, sửa đổi mã của Irfan và mã của tôi bằng chương trình trình điều khiển sau:

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

Kết quả:

    $ thời gian ./do.sh irfan 10 5

    số 0m0.975 thật
    người dùng 0m0.280
    sys 0m0.691s

    $ thời gian ./do.sh brian 10 5

    số 0m0.226 thực
    người dùng 0m0.057s
    sys 0m0.123s

    $ thời gian ./do.sh jerry 10 5

    0m0.706s thật
    người dùng 0m0.228s
    sys 0m0.530s

    $ thời gian ./do.sh irfan 100 5

    số 0m10.633 thật
    người dùng 0m4.366
    sys 0m7.127s

    $ thời gian ./do.sh brian 100 5

    số 0m1.682 thực
    người dùng 0m0.546s
    sys 0m1.082s

    $ thời gian ./do.sh jerry 100 5

    0m9.315s thật
    người dùng 0m4.565s
    sys 0m5.446s

    $ thời gian ./do.sh irfan 10 500

    1m46.197 thật
    người dùng 0m44.869
    sys 1m12.282

    $ thời gian ./do.sh brian 10 500

    0m16.003s thực
    người dùng 0m5.135
    sys 0m10.396s

    $ thời gian ./do.sh jerry 10 500

    1m24.414s thật
    người dùng 0m39.696
    sys 0m54.834s

    $ thời gian ./do.sh irfan 1000 5

    4m25.145 thực
    người dùng 3m17.286
    sys 1m21.490

    $ thời gian ./do.sh brian 1000 5

    số 0m19.442 thực
    người dùng 0m5.287
    sys 0m10.751s

    $ thời gian ./do.sh jerry 1000 5

    5m29.136 thực
    người dùng 4m48.926s
    sys 0m59.336s


1
Tôi không nghĩ bạn nên sử dụng hệ thống tệp cho bản đồ, về cơ bản là sử dụng IO cho một thứ gì đó mà bạn có thể thực hiện khá nhanh trong bộ nhớ.
Irfan Zulfiqar

9
Các tập tin sẽ không nhất thiết phải được ghi vào đĩa; trừ khi bạn gọi đồng bộ hóa, hệ điều hành có thể chỉ để chúng trong bộ nhớ. Mã của bạn đang gọi ra sed và thực hiện một số tìm kiếm tuyến tính, tất cả đều rất chậm. Tôi đã thực hiện một số điểm chuẩn nhanh và phiên bản của tôi nhanh hơn 5-35 lần.
Brian Campbell

mặt khác, các mảng riêng của bash4 là cách tiếp cận tốt hơn đáng kể và trong bash3, bạn vẫn có thể giữ mọi thứ khỏi đĩa mà không cần sử dụng khai báo và gián tiếp.
lhunath

7
"nhanh" và "vỏ" dù sao cũng không thực sự đi đôi với nhau: chắc chắn không phải là vấn đề tốc độ mà chúng ta đang nói ở cấp độ "tránh IO rất nhỏ". Bạn có thể tìm kiếm và sử dụng / dev / shm để đảm bảo không có IO.
jmtd

2
Giải pháp này làm tôi ngạc nhiên, và thật tuyệt vời. Vẫn đúng trong năm 2016. Nó thực sự nên là câu trả lời được chấp nhận.
Gordon

7

Bash4 hỗ trợ điều này nguyên bản. Không sử dụng grephoặc eval, chúng là xấu nhất của hack.

Đối với một verbose, câu trả lời chi tiết với mã ví dụ, xem: /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; }'
}

Thí dụ:

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

Bây giờ trả lời câu hỏi này.

Các kịch bản sau mô phỏng các mảng kết hợp trong các kịch bản shell. Nó đơn giản và rất dễ hiểu.

Bản đồ không có gì ngoài một chuỗi không bao giờ kết thúc có keyValuePair được lưu dưới dạng --name = Irfan --designation = SSE --company = My: SP: own: SP: Company

khoảng trắng được thay thế bằng ': SP:' cho các giá trị

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

chỉnh sửa: Chỉ cần thêm một phương thức khác để tìm nạp tất cả các khóa.

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

    mapName=$1; 

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

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

1
Bạn đang lấy evaldữ liệu như thể đó là mã bash, và hơn thế nữa: bạn không trích dẫn đúng. Cả hai đều gây ra hàng loạt lỗi và tiêm mã tùy ý.
lhunath

3

Đối với Bash 3, có một trường hợp cụ thể có một giải pháp hay và đơn giản:

Nếu bạn không muốn xử lý nhiều biến hoặc các khóa đơn giản là các định danh biến không hợp lệ mảng của bạn được đảm bảo có ít hơn 256 mục , bạn có thể lạm dụng các giá trị trả về của hàm. Giải pháp này không yêu cầu bất kỳ giá trị con nào vì giá trị này có sẵn dưới dạng một biến, cũng không có bất kỳ phép lặp nào để hiệu suất hét lên. Ngoài ra, nó rất dễ đọc, gần giống như phiên bản Bash 4.

Đây là phiên bản cơ bản nhất:

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

Hãy nhớ rằng, sử dụng các trích dẫn duy nhất case, nếu không, nó có thể là toàn cầu. Thực sự hữu ích cho băm tĩnh / đóng băng ngay từ đầu, nhưng người ta có thể viết một trình tạo chỉ mục từ một hash_keys=()mảng.

Xem ra, nó mặc định là cái đầu tiên, vì vậy bạn có thể muốn đặt phần tử zeroth sang một bên:

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

Hãy cẩn thận: chiều dài bây giờ không chính xác.

Ngoài ra, nếu bạn muốn giữ lập chỉ mục dựa trên số không, bạn có thể bảo lưu một giá trị chỉ mục khác và bảo vệ chống lại khóa không tồn tại, nhưng nó ít đọc hơn:

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

Hoặc, để giữ độ dài chính xác, hãy bù chỉ số theo một:

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

Bạn có thể sử dụng tên biến động và để tên biến hoạt động giống như các khóa của hàm băm.

Ví dụ: nếu bạn có một tệp đầu vào có hai cột, tên, tín dụng, làm ví dụ dưới đây và bạn muốn tính tổng thu nhập của mỗi người dùng:

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

Lệnh dưới đây sẽ tổng hợp mọi thứ, sử dụng các biến động làm khóa, dưới dạng bản đồ _ $ {person} :

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

Để đọc kết quả:

set | grep map

Đầu ra sẽ là:

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

Xây dựng các kỹ thuật này, tôi đang phát triển trên GitHub một chức năng hoạt động giống như Đối tượng HashMap , shell_map .

Để tạo " phiên bản HashMap ", hàm shell_map có thể tự tạo các bản sao dưới các tên khác nhau. Mỗi bản sao chức năng mới sẽ có một biến $ FUNCNAME khác nhau. $ FUNCNAME sau đó được sử dụng để tạo một không gian tên cho mỗi phiên bản Map.

Các khóa bản đồ là các biến toàn cục, ở dạng $ FUNCNAME_DATA_ $ KEY, trong đó $ KEY là khóa được thêm vào Bản đồ. Các biến này là các biến động .

Dưới đây tôi sẽ đặt một phiên bản đơn giản hóa của nó để bạn có thể sử dụng làm ví dụ.

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

Sử dụng:

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

Một cách khác không phải bash-4 (tức là bash 3, tương thích với Mac):

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

Bản in:

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

Các chức năng với các casehành vi như một mảng kết hợp. Thật không may, nó không thể sử dụng return, vì vậy nó phải echoxuất ra nó, nhưng đây không phải là vấn đề, trừ khi bạn là một người theo chủ nghĩa thuần túy trốn tránh các subshells.


1

Thật đáng tiếc tôi đã không nhìn thấy câu hỏi trước đây - Tôi đã viết khung vỏ thư viện chứa các bản đồ khác (mảng kết hợp). Phiên bản cuối cùng của nó có thể được tìm thấy ở đây .

Thí dụ:

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

Thêm tùy chọn khác, nếu jq có sẵn:

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

Tôi đã thấy đúng, như đã đề cập, phương pháp hoạt động tốt nhất là viết ra key / vals vào một tệp, sau đó sử dụng grep / awk để lấy chúng. Nghe có vẻ như tất cả các loại IO không cần thiết, nhưng bộ đệm đĩa khởi động và làm cho nó cực kỳ hiệu quả - nhanh hơn nhiều so với việc cố gắng lưu trữ chúng trong bộ nhớ bằng một trong các phương pháp trên (như các điểm chuẩn hiển thị).

Đây là một phương pháp nhanh chóng, gọn gàng mà tôi thích:

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`

Nếu bạn muốn thực thi một giá trị đơn cho mỗi khóa, bạn cũng có thể thực hiện một hành động grep / sed nhỏ trong hput ().


0

Cách đây vài năm, tôi đã viết thư viện script cho bash hỗ trợ các mảng kết hợp giữa các tính năng khác (ghi nhật ký, tệp cấu hình, hỗ trợ mở rộng cho đối số dòng lệnh, tạo trợ giúp, kiểm tra đơn vị, v.v.). Thư viện chứa một trình bao bọc cho các mảng kết hợp và tự động chuyển sang mô hình thích hợp (nội bộ cho bash4 và mô phỏng cho các phiên bản trước). Nó được gọi là shell-framework và được lưu trữ tại origo.ethz.ch nhưng ngày nay tài nguyên đã bị đóng. Nếu ai đó vẫn cần nó tôi có thể chia sẻ nó với bạn.


Có thể đáng để gắn nó lên github
Mark K Cowan

0

Shell không có bản đồ tích hợp như cấu trúc dữ liệu, tôi sử dụng chuỗi thô để mô tả các mục như thế:

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

khi trích xuất các mục và thuộc tính của nó:

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

Điều này có vẻ như không thông minh hơn câu trả lời của người khác, nhưng dễ hiểu đối với người mới.


-1

Tôi đã sửa đổi giải pháp của Vadim bằng cách sau:

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

Thay đổi là map_get để ngăn nó trả lại lỗi nếu bạn yêu cầu khóa không tồn tại, mặc dù tác dụng phụ là nó cũng sẽ âm thầm bỏ qua các bản đồ bị thiếu, nhưng nó phù hợp với trường hợp sử dụng của tôi hơn vì tôi chỉ muốn kiểm tra khóa để bỏ qua các mục trong vòng lặp.


-1

Trả lời trễ, nhưng xem xét giải quyết vấn đề theo cách này, sử dụng bash dựng sẵn đọc như minh họa trong đoạn mã từ tập lệnh tường lửa ufw theo sau. Cách tiếp cận này có lợi thế là sử dụng nhiều bộ trường được phân tách (không chỉ 2) như mong muốn. Chúng tôi đã sử dụng | dấu phân cách vì các bộ xác định phạm vi cổng có thể yêu cầu dấu hai chấm, tức là 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
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.