Tương đương với từ điển Python nhưng trong Bash (nên hoạt động trên OS X và Linux).
Tương đương với từ điển Python nhưng trong Bash (nên hoạt động trên OS X và Linux).
Câu trả lời:
Bash 4 vốn hỗ trợ tính năng này. Hãy chắc chắn rằng hashbang của tập lệnh của bạn là #!/usr/bin/env bash
hoặc #!/bin/bash
do đó bạn không sử dụng sh
. Hãy chắc chắn rằng bạn đang thực thi trực tiếp tập lệnh của mình hoặc thực thi script
với bash script
. (Không thực sự thực hiện một kịch bản Bash với Bash không xảy ra, và sẽ được thực sự khó hiểu!)
Bạn khai báo một mảng kết hợp bằng cách làm:
declare -A animals
Bạn có thể điền nó với các phần tử bằng cách sử dụng toán tử gán mảng bình thường. Ví dụ: nếu bạn muốn có một bản đồ animal[sound(key)] = animal(value)
:
animals=( ["moo"]="cow" ["woof"]="dog")
Hoặc hợp nhất chúng:
declare -A animals=( ["moo"]="cow" ["woof"]="dog")
Sau đó sử dụng chúng giống như các mảng bình thường. Sử dụng
animals['key']='value'
để đặt giá trị
"${animals[@]}"
để mở rộng các giá trị
"${!animals[@]}"
(chú ý !
) để mở rộng các phím
Đừng quên trích dẫn chúng:
echo "${animals[moo]}"
for sound in "${!animals[@]}"; do echo "$sound - ${animals[$sound]}"; done
Trước bash 4, bạn không có mảng kết hợp. Không sử dụng eval
để mô phỏng chúng . Tránh eval
như bệnh dịch, vì đó là bệnh dịch của kịch bản shell. Lý do quan trọng nhất là eval
coi dữ liệu của bạn là mã thực thi (có nhiều lý do khác nữa).
Đầu tiên và quan trọng nhất : Xem xét nâng cấp lên bash 4. Điều này sẽ làm cho toàn bộ quá trình dễ dàng hơn cho bạn.
Nếu có một lý do bạn không thể nâng cấp, declare
là một lựa chọn an toàn hơn nhiều. Nó không đánh giá dữ liệu như mã bash eval
, và như vậy không cho phép tiêm mã tùy ý khá dễ dàng.
Hãy chuẩn bị câu trả lời bằng cách giới thiệu các khái niệm:
Đầu tiên, vô cảm.
$ animals_moo=cow; sound=moo; i="animals_$sound"; echo "${!i}"
cow
Thứ hai , declare
:
$ sound=moo; animal=cow; declare "animals_$sound=$animal"; echo "$animals_moo"
cow
Mang chúng lại với nhau:
# Set a value:
declare "array_$index=$value"
# Get a value:
arrayGet() {
local array=$1 index=$2
local i="${array}_$index"
printf '%s' "${!i}"
}
Hãy sử dụng nó:
$ sound=moo
$ animal=cow
$ declare "animals_$sound=$animal"
$ arrayGet animals "$sound"
cow
Lưu ý: declare
không thể được đặt trong một chức năng. Bất kỳ việc sử dụng nào declare
bên trong hàm bash đều biến biến mà nó tạo cục bộ thành phạm vi của hàm đó, nghĩa là chúng ta không thể truy cập hoặc sửa đổi các mảng toàn cục với nó. (Trong bash 4, bạn có thể sử dụng khai báo -g để khai báo các biến toàn cục - nhưng trong bash 4, bạn có thể sử dụng các mảng kết hợp ở vị trí đầu tiên, tránh cách giải quyết này.)
Tóm lược:
declare -A
cho các mảng kết hợp.declare
tùy chọn nếu bạn không thể nâng cấp.awk
thay thế và tránh vấn đề hoàn toàn.4.x
và không y
.
sudo port install bash
, đối với những người (một cách khôn ngoan, IMHO) không muốn tạo các thư mục trong PATH cho tất cả người dùng có thể ghi mà không có sự leo thang đặc quyền theo quy trình rõ ràng.
Có sự thay thế tham số, mặc dù nó có thể không phải là PC cũng như ... không xác định.
#!/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]##*:}"
Tất nhiên, cách BASH 4 tốt hơn, nhưng nếu bạn cần hack ... chỉ có hack mới làm được. Bạn có thể tìm kiếm mảng / băm với các kỹ thuật tương tự.
VALUE=${animal#*:}
bảo vệ trường hợpARRAY[$x]="caesar:come:see:conquer"
for animal in "${ARRAY[@]}"; do
Đây là những gì tôi đang tìm kiếm ở đây:
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
Điều này không làm việc với tôi với bash 4.1.5:
animals=( ["moo"]="cow" )
Bạn có thể sửa đổi thêm giao diện hput () / hget () để bạn có tên băm như sau:
hput() {
eval "$1""$2"='$3'
}
hget() {
eval echo '${'"$1$2"'#hash}'
}
và sau đó
hput capitals France Paris
hput capitals Netherlands Amsterdam
hput capitals Spain Madrid
echo `hget capitals France` and `hget capitals Netherlands` and `hget capitals Spain`
Điều này cho phép bạn xác định các bản đồ khác không xung đột (ví dụ: 'RCapitals' mà quốc gia tìm kiếm theo thành phố thủ đô). Nhưng, dù sao đi nữa, tôi nghĩ bạn sẽ thấy rằng điều này khá kinh khủng, hiệu quả.
Nếu bạn thực sự muốn tra cứu băm nhanh, có một hack khủng khiếp, khủng khiếp thực sự hoạt động rất tốt. Đây là: viết khóa / giá trị của bạn ra một tệp tạm thời, một dòng trên mỗi dòng, sau đó sử dụng 'grep "^ $ key"' để lấy chúng ra, sử dụng các đường ống có cắt hoặc awk hoặc sed hoặc bất cứ thứ gì để lấy các giá trị.
Như tôi đã nói, nghe có vẻ khủng khiếp và nghe có vẻ như nó phải chậm và làm tất cả các loại IO không cần thiết, nhưng trong thực tế thì nó rất nhanh (bộ nhớ cache của đĩa rất tuyệt, phải không?), Ngay cả đối với hàm băm rất lớn những cái bàn. Bạn phải tự thực thi tính duy nhất của khóa, v.v. Ngay cả khi bạn chỉ có vài trăm mục nhập, kết hợp tệp / grep đầu ra sẽ nhanh hơn một chút - theo kinh nghiệm của tôi nhanh hơn nhiều lần. Nó cũng ăn ít bộ nhớ hơn.
Đây là một cách để làm điều đó:
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`
Hệ thống tệp là một cấu trúc cây có thể được sử dụng làm bản đồ băm. Bảng băm của bạn sẽ là một thư mục tạm thời, các khóa của bạn sẽ là tên tệp và giá trị của bạn sẽ là nội dung tệp. Ưu điểm là nó có thể xử lý các hashtag lớn và không yêu cầu shell cụ thể.
hashtable=$(mktemp -d)
echo $value > $hashtable/$key
value=$(< $hashtable/$key)
Tất nhiên, nó chậm, nhưng không phải là chậm. Tôi đã thử nghiệm nó trên máy của mình, với ổ SSD và btrfs , và nó có khoảng 3000 phần tử đọc / ghi mỗi giây .
mkdir -d
? (Không phải 4.3, trên Ubuntu 14. Tôi sẽ dùng đến mkdir /run/shm/foo
, hoặc nếu nó đã đầy RAM , mkdir /tmp/foo
.)
mktemp -d
có nghĩa là thay thế?
$value=$(< $hashtable/$key)
và là value=$(< $hashtable/$key)
gì? Cảm ơn!
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}
xóa văn bản bắt đầu từ đầu giá trị được lưu trong biến var .
Hãy xem xét một giải pháp bằng cách sử dụng hàm bash được đọc như được 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
IFS=$'|' read -r first rest <<< "$fields"
Tôi đồng ý với @lhunath và những người khác rằng mảng kết hợp là cách phù hợp với Bash 4. Nếu bạn bị mắc kẹt với Bash 3 (OSX, các bản phát hành cũ mà bạn không thể cập nhật), bạn cũng có thể sử dụng expr, ở mọi nơi, một chuỗi và biểu thức chính quy. Tôi thích nó đặc biệt là khi từ điển không quá lớn.
Viết bản đồ của bạn dưới dạng một chuỗi (lưu ý dấu phân cách ',' cũng ở đầu và cuối)
animals=",moo:cow,woof:dog,"
Sử dụng biểu thức chính quy để trích xuất các giá trị
get_animal {
echo "$(expr "$animals" : ".*,$1:\([^,]*\),.*")"
}
Tách chuỗi để liệt kê các mục
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
}
Bây giờ bạn có thể sử dụng nó:
$ animal = get_animal "moo"
cow
$ get_animal_items
cow likes to moo
dog likes to woof
Tôi thực sự thích câu trả lời của Al P nhưng muốn sự độc đáo được thi hành với giá rẻ nên tôi đã tiến thêm một bước - sử dụng một thư mục. Có một số hạn chế rõ ràng (giới hạn tệp thư mục, tên tệp không hợp lệ) nhưng nó sẽ hoạt động trong hầu hết các trường hợ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
Nó cũng thực hiện tốt hơn một chút trong các thử nghiệm của tôi.
$ 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
Chỉ cần nghĩ rằng tôi sẽ tham gia. Chúc mừng!
Chỉnh sửa: Thêm hdestroy ()
Hai điều, bạn có thể sử dụng bộ nhớ thay vì / tmp trong bất kỳ kernel 2.6 nào bằng cách sử dụng / dev / shm (Redhat) các bản phát hành khác có thể khác nhau. Ngoài ra hget có thể được thực hiện lại bằng cách đọc như sau:
function hget {
while read key idx
do
if [ $key = $2 ]
then
echo $idx
return
fi
done < /dev/shm/hashmap.$1
}
Ngoài ra, bằng cách giả sử rằng tất cả các khóa là duy nhất, trả về ngắn mạch vòng lặp đọc và ngăn không phải đọc qua tất cả các mục. Nếu việc triển khai của bạn có thể có các khóa trùng lặp, thì bạn chỉ cần bỏ qua phần trả về. Điều này tiết kiệm chi phí đọc và bỏ cả grep và awk. Sử dụng / dev / shm cho cả hai triển khai mang lại kết quả như sau bằng cách sử dụng thời gian trên hàm băm 3 mục tìm kiếm cho mục cuối cùng:
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
Đọc / lặp lại:
$ time echo $(hget FD oracle)
3
real 0m0.004s
user 0m0.000s
sys 0m0.004s
trên nhiều yêu cầu, tôi chưa bao giờ thấy ít hơn một sự cải thiện 50%. Tất cả điều này có thể được quy cho ngã ba trên đầu, do sử dụng /dev/shm
.
Một đồng nghiệp chỉ đề cập đến chủ đề này. Tôi đã triển khai độc lập các bảng băm trong bash và nó không phụ thuộc vào phiên bản 4. Từ một bài đăng trên blog của tôi vào tháng 3 năm 2010 (trước một số câu trả lời ở đây ...) có tiêu đề Bảng bash trong bash :
Trước đây tôi đã sử dụng cksum
để băm nhưng từ đó đã dịch chuỗi hashCode của Java sang bash / zsh gốc.
# 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)
Nó không phải là hai chiều, và cách tích hợp tốt hơn rất nhiều, nhưng dù sao cũng không nên sử dụng. Bash là một lần nhanh chóng, và những thứ như vậy hiếm khi liên quan đến sự phức tạp có thể yêu cầu băm, ngoại trừ có lẽ trong bạn ~/.bashrc
và bạn bè của bạn.
Trước bash 4, không có cách nào tốt để sử dụng mảng kết hợp trong bash. Đặt cược tốt nhất của bạn là sử dụng một ngôn ngữ diễn giải thực sự có hỗ trợ cho những thứ đó, như awk. Mặt khác, bash 4 không hỗ trợ họ.
Đối với những cách ít tốt hơn trong bash 3, đây là một tài liệu tham khảo có thể giúp ích: http://mywiki.wooledge.org/BashFAQ/006
Giải pháp Bash 3:
Khi đọc một số câu trả lời tôi đưa ra một chức năng nhỏ nhanh chóng, tôi muốn đóng góp lại có thể giúp đỡ người khác.
# 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[@])
Tôi cũng đã sử dụng cách bash4 nhưng tôi thấy và lỗi khó chịu.
Tôi cần cập nhật động nội dung mảng kết hợp vì vậy tôi đã sử dụng cách này:
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
Tôi phát hiện ra rằng với bash 4.3.11 gắn vào khóa hiện có trong dict dẫn đến việc nối thêm giá trị nếu đã có. Vì vậy, ví dụ sau một số lần lặp lại, nội dung của giá trị là "checkKOcheckKOallCheckOK" và điều này không tốt.
Không có vấn đề gì với bash 4.3.39 trong đó việc gắn một khóa tồn tại có nghĩa là thay thế giá trị Actuale nếu đã có.
Tôi đã giải quyết điều này chỉ bằng cách làm sạch / khai báo mảng kết hợp statusCheck trước cicle:
unset statusCheck; declare -A statusCheck
Tôi tạo HashMaps trong bash 3 bằng các biến động. Tôi đã giải thích cách thức hoạt động trong câu trả lời của tôi đối với: Mảng liên kết trong tập lệnh Shell
Ngoài ra, bạn có thể xem shell_map , đây là một triển khai HashMap được thực hiện trong bash 3.