답변:
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 4 이전에는 연관 배열이 없습니다. 그것들을 모방하기 위해 사용하지 마십시오eval
. 피 eval
가 있기 때문에, 전염병처럼 입니다 쉘 스크립트의 전염병. 가장 중요한 이유는 eval
데이터를 실행 가능한 코드로 취급하기 때문입니다 (다른 많은 이유도 있음).
가장 먼저 : bash 4로 업그레이드하는 것이 좋습니다. 이렇게하면 전체 프로세스가 훨씬 쉬워집니다.
업그레이드 할 수없는 이유 declare
가 있다면 훨씬 안전한 옵션입니다. 데이터를 bash 코드로 평가 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에서는 우선이 해결 방법을 피하면서 연관 배열을 사용할 수 있습니다.
요약:
declare -A
연관 배열에 사용하십시오.declare
업그레이드 할 수없는 경우이 옵션을 사용하십시오 .awk
대신 사용 하고 문제를 피하십시오.4.x
하지 않아야 y
합니다.
sudo port install bash
, (현명하게 IMHO) 모든 프로세스에 대해 명시적인 프로세스 별 권한 에스컬레이션없이 쓰기 가능한 모든 사용자의 PATH 디렉토리를 만들지 않으려는 경우.
매개 변수 대체가 있지만 PC와 같지 않을 수도 있지만 간접적입니다.
#!/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 방법은 물론 더 좋지만, 해킹이 필요한 경우 ... 해킹 만 가능합니다. 비슷한 기술로 배열 / 해시를 검색 할 수 있습니다.
VALUE=${animal#*:}
경우 보호하기 위해ARRAY[$x]="caesar:come:see:conquer"
for animal in "${ARRAY[@]}"; do
이것이 내가 찾고 있던 것입니다.
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" )
다음과 같이 해시 이름을 지정하도록 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`
파일 시스템은 해시 맵으로 사용할 수있는 트리 구조입니다. 해시 테이블은 임시 디렉토리가되고 키는 파일 이름이되고 값은 파일 내용이됩니다. 장점은 거대한 해시 맵을 처리 할 수 있으며 특정 쉘이 필요하지 않다는 것입니다.
hashtable=$(mktemp -d)
echo $value > $hashtable/$key
value=$(< $hashtable/$key)
물론 느리지 만 그렇게 느리지는 않습니다 . SSD와 btrfs를 사용하여 내 컴퓨터에서 테스트했으며 초당 약 3000 개의 요소 읽기 / 쓰기를 수행 합니다.
mkdir -d
합니까? (Ubuntu 14에서는 4.3이 아닙니다. mkdir /run/shm/foo
또는 RAM이 가득 찬 경우에 의지합니다 mkdir /tmp/foo
.)
mktemp -d
그 대신에 의미가 있었습니까?
$value=$(< $hashtable/$key)
과 value=$(< $hashtable/$key)
? 감사!
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}
는 변수 var에 저장된 값의 시작 부분에서 시작 텍스트를 제거합니다 .
다음 ufw 방화벽 스크립트의 코드 스 니펫에 설명 된대로 bash 내장 읽기 를 사용하는 솔루션을 고려하십시오 . 이 접근 방식은 원하는만큼 많은 구분 된 필드 세트 (2 개가 아닌)를 사용하는 이점이 있습니다. 우리는 | 포트 범위 지정자에는 콜론 (예 : 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"
나는 @lhunath와 다른 사람들에게 연관 배열이 Bash 4와 함께 갈 수있는 방법이라는 것에 동의합니다. Bash 3 (OSX, 업데이트 할 수없는 오래된 배포판)에 붙어 있다면 expr을 사용할 수 있습니다. 정규식. 사전이 너무 크지 않을 때 특히 마음에 듭니다.
지도를 문자열로 작성하십시오 (구두 및 시작 부분에도 구분 기호 ','에 유의하십시오)
animals=",moo:cow,woof:dog,"
정규식을 사용하여 값을 추출하십시오.
get_animal {
echo "$(expr "$animals" : ".*,$1:\([^,]*\),.*")"
}
문자열을 분할하여 항목을 나열하십시오.
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
나는 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 () 추가
두 가지로, 커널 2.6에서 / dev / shm (Redhat)을 사용하여 / tmp 대신 메모리를 사용할 수 있습니다. 다른 배포판은 다를 수 있습니다. 또한 다음과 같이 read를 사용하여 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을 사용하면 마지막 항목을 검색하는 3 개의 항목 해시에서 시간 hget을 사용하여 다음을 얻을 수 있습니다.
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
읽기 / 에코 :
$ time echo $(hget FD oracle)
3
real 0m0.004s
user 0m0.000s
sys 0m0.004s
여러 번의 호출에서 나는 50 % 이하의 개선을 보지 못했습니다. 이는을 (를) 사용 함으로 인해 오버 헤드로 인해 발생했을 수 있습니다 /dev/shm
.
동료가 방금이 스레드를 언급했습니다. bash 내에서 해시 테이블을 독립적으로 구현했으며 버전 4에 의존하지 않습니다. 2010 년 3 월 블로그 게시물에서 (해답 중 일부 전에 ...) bash의 Hash table 이라는 제목이 있습니다 .
나는 이전에 사용 된 cksum
해시하지만 이후 번역 한 자바의 문자열의 해시 코드 기본 bash는 / 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)
양방향이 아니며 내장 방식이 훨씬 나아지지만 실제로는 사용되지 않아야합니다. Bash는 빠른 일회성 기능을위한 것이며, 이러한 일에는 아마도 여러분 ~/.bashrc
과 친구를 제외하고 해시가 필요할 수있는 복잡성이 거의 포함되지 않아야합니다 .
bash 4 이전에는 bash에서 연관 배열을 사용하는 좋은 방법이 없습니다. 가장 좋은 방법은 실제로 awk와 같은 것들을 지원하는 해석 언어를 사용하는 것입니다. 반면에, bash 4 는 그것들을 지원합니다.
에 관해서는 덜 bash는 3 좋은 가지 방법, 여기에 힘 도움말보다 기준은 다음과 같습니다 http://mywiki.wooledge.org/BashFAQ/006
배쉬 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[@])
나는 또한 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
bash 4.3.11을 사용하여 dict의 기존 키에 추가하면 이미 존재하는 경우 값이 추가됩니다. 예를 들어 일부 반복 후 값의 내용은 "checkKOcheckKOallCheckOK"이고 이는 좋지 않습니다.
존재하는 키를 할당하는 것이 실제 값을 이미 만족시키는 것을 의미하는 bash 4.3.39에는 아무런 문제가 없습니다.
cicle 전에 statusCheck 연관 배열을 청소 / 선언하는 것으로 해결했습니다.
unset statusCheck; declare -A statusCheck
동적 변수를 사용하여 bash 3에서 HashMaps를 만듭니다. 셸 스크립트의 연관 배열에 대한 대답에서 어떻게 작동하는지 설명했습니다.
또한 bash 3에서 만든 HashMap 구현 인 shell_map을 살펴볼 수 있습니다 .