Bash에서 해시 테이블을 정의하는 방법은 무엇입니까?
Python 사전과 동등하지만 Bash에서는 무엇이 작동합니까(OS X와 Linux에서 작동해야 합니다).
배시 4
Bash 4는 기본적으로 이 기능을 지원합니다.이 스립트해이시방의인지 합니다.#!/usr/bin/env bash또는#!/bin/bash그래서 당신은 사용하지 않을 것입니다.sh직접 하거나, 또는 실행합니다.script와 함께bash script(Bash를 사용하여 Bash 스크립트를 실제로 실행하는 것은 아니며, 매우 혼란스러울 것입니다!)
다음을 수행하여 연관 배열을 선언합니다.
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
배시 3
bash 4 이전에는 연관 배열이 없습니다.을(를) 에뮬레이트하지 마십시오.피하다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함수에 넣을 수 없습니다.의 임의 declarebash 함수 내부에서는 생성되는 변수를 해당 함수의 범위로 로컬로 전환합니다. 즉, 이 함수를 사용하여 글로벌 배열에 액세스하거나 수정할 수 없습니다. (bash 4에서는 사용할 수 있습니다.declare -g글로벌 변수를 선언하지만 bash 4에서는 이러한 해결 방법을 사용하지 않고 연관 배열을 사용할 수 있습니다.)
요약:.
- 하고 bash 4를 사용합니다.
declare -A연관 배열의 경우. - 을 합니다.
declare업그레이드할 수 없는 경우 옵션을 선택합니다. - 사을고려다니합을 사용하는 것을 .
awk대신 문제를 완전히 피하십시오.
매개 변수 대체가 있습니다. 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 방법이 더 좋지만, 해킹이 필요하다면... 해킹만 가능합니다.유사한 기법으로 배열/해시를 검색할 수 있습니다.
이것이 제가 여기서 찾고 있던 것입니다.
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" )
파일 시스템을 사용하면 됩니다. 파일 시스템은 해시 맵으로 사용할 수 있는 트리 구조입니다.해시 테이블은 임시 디렉토리가 되고 키는 파일 이름이 되며 값은 파일 내용이 됩니다.장점은 거대한 해시 맵을 처리할 수 있고 특정 셸이 필요하지 않다는 것입니다.
해시 테이블 생성
hashtable=$(mktemp -d)
요소 추가
echo $value > "$hashtable/$key"
요소 읽기
value=$(< "$hashtable/$key")
성능
물론, 느리지만, 그렇게 느리지는 않습니다.SSD와 btrfs를 사용하여 컴퓨터에서 테스트해 보았는데 초당 약 3000개의 요소 읽기/쓰기를 수행합니다.
다음과 같이 해시 이름을 지정하도록 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'는 수도별로 국가 조회를 수행합니다).하지만, 어느 쪽이든, 여러분은 이 모든 것이 성능 면에서 상당히 끔찍하다는 것을 알게 될 것입니다.
편집: 영숫자가 아닌 키를 지원하는 위의 수정된 버전
hashKey() {
# replace non-alphanumeric characters with underscore to make keys valid BASH identifiers
echo "$1_$2" | sed -E "s/[^a-zA-Z0-9]+/_/g" | sed -E "s/^[^a-zA-Z0-9]+|[^a-zA-Z0-9]+\$//g"
}
hashPut() {
local KEY=`hashKey $1 $2`
eval "$KEY"="$3"
}
hashGet() {
local KEY=`hashKey $1 $2`
echo "${!KEY}"
}
편집 종료
만약 당신이 정말로 빠른 해시 검색을 원한다면, 정말로 잘 작동하는 끔찍하고 끔찍한 해킹이 있습니다.이것입니다. 키/값을 임시 파일에 한 줄에 한 줄씩 쓴 다음 'grep "^$key"를 사용하여 꺼냅니다. 절단, 어크, 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`
다음에 나오는 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
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
저는 연상배열이 Bash 4로 가는 길이라는 @lunath 등의 의견에 동의합니다.Bash 3(OSX, 업데이트할 수 없는 이전 배포자)을 고수하는 경우 expr도 사용할 수 있습니다. expr은 모든 곳에 있어야 하며 문자열 및 정규식입니다.저는 특히 사전이 너무 크지 않을 때가 좋습니다.
- 키 및 값에 사용하지 않을 구분 기호 2개(예: ', ' 및 ':')를 선택합니다.
지도를 문자열로 작성합니다(처음과 끝에도 구분 기호 ',' 참조).
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() 추가
동료가 방금 이 스레드를 언급했습니다.저는 해시 테이블을 bash 내에서 독립적으로 구현했으며 버전 4에 종속되지 않습니다.2010년 3월 제 블로그 게시물(여기에 있는 일부 답변 이전에...)에서 bash의 해시 테이블:
이전에 사용한 적이 있습니다.cksum해시하지만 그 이후로 Java의 문자열 hashCode를 네이티브 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는 빠 위 것 을 른 며 이 것 해 은 복 을 성 가 시 잡 들 거 수 의 않 반 하 지 아 필 입 외 예 니 경 이 다 는 우 한 의 합 러 야 자 용 요 는 사 단 일 회 할 있 수 니 성 다 한 ▁perhaps ▁one ▁bash ▁your hes ▁in ity ▁that ▁things ▁except~/.bashrc그리고 친구들.
두 가지는 커널 2.6에서 /dev/shm(Redhat)을 사용하여 /tmp 대신 메모리를 사용할 수 있다는 점입니다. 다른 배포자는 다를 수 있습니다.또한 다음과 같이 읽기를 사용하여 hget을 다시 구현할 수 있습니다.
function hget {
while read key idx
do
if [ $key = $2 ]
then
echo $idx
return
fi
done < /dev/shm/hashmap.$1
}
또한 모든 키가 고유하다고 가정하면 리턴은 읽기 루프를 단락시키고 모든 항목을 읽을 필요가 없습니다.구현에 중복 키가 있을 수 있는 경우에는 반환을 생략합니다.이것은 그렙과 어색함 모두를 읽고 포크하는 비용을 절약합니다.두 구현 모두에 /dev/shm을 사용하면 마지막 항목을 검색하는 3개의 엔트리 해시에 대한 time 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% 이상 향상된 것을 본 적이 없습니다.이는 모두 포크 오버헤드(forkoverhead)에 기인할 수 있습니다./dev/shm.
bash 4 이전에는 bash에서 연관 배열을 사용하는 좋은 방법이 없습니다.여러분의 최선의 방법은 어색함과 같이 실제로 그러한 것들을 지원하는 해석된 언어를 사용하는 것입니다.반면에, bash 4는 그들을 지원합니다.
bash 3의 덜 좋은 방법에 대해서는 도움이 될 수 있는 것보다 참고 자료가 도움이 될 수 있습니다.
Bash 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
dict의 기존 키에 bash 4.3.11을 추가하면 이미 존재하는 경우 값이 추가된다는 것을 알게 되었습니다.예를 들어, 몇 번 반복한 후에 값의 내용이 "checkKOcheckKOallCheckOK"였는데 이것은 좋지 않았습니다.
bash 4.3.39에서는 기존 키를 여는 것이 이미 존재하는 경우 실제 e 값을 대체하는 것을 의미하는 문제가 없습니다.
이 문제는 cycle 앞에 있는 statusCheck associative array: 상태를 정리/선언하는 것만으로 해결했습니다.
unset statusCheck; declare -A statusCheck
여기 다소 고안되었지만 도움이 되는 해시/맵/사전/어소시에이션 배열 예가 있습니다.문자열 배열이 있는데 각 단어에서 해당 단어가 배열에 표시되는 횟수에 대한 매핑을 만들고 싶다고 가정해 보겠습니다.
물론 파이프 명령을 사용하여 이 작업을 수행하는 방법도 있지만 핵심 맵 작업을 설명하는 것이 중요합니다. 키의 존재 여부를 확인하는 것입니다.-v키-값 매핑 추가, 키에 대한 값 검색, 키에 대한 기존 값 업데이트, 키-값 쌍 인쇄를 위한 전체 맵 루프 등이 있습니다.
#!/usr/bin/bash
set -o pipefail
bash --version | head -1
words=(foo foo bar bar foo baz baz foo bar)
declare -A counter=() # create the map
for word in "${words[@]}"; do
# if the key doesn't yet exist in the map, add it
if [[ ! -v counter[$word] ]]; then
counter[$word]=0
fi
# look up the value of a key, add one, and store back in the map
counter[$word]=$((${counter[$word]} + 1))
done
# iterate the map
for key in "${!counter[@]}"; do
echo "$key ${counter[$key]}"
done
출력:
GNU bash, version 5.1.16(1)-release (x86_64-pc-linux-gnu)
foo 4
bar 3
baz 2
언급URL : https://stackoverflow.com/questions/1494178/how-to-define-hash-tables-in-bash
'programing' 카테고리의 다른 글
| Microsoft의 데이터베이스 명명 규칙? (0) | 2023.05.24 |
|---|---|
| 바이트 배열을 기본 null이 아닌 특정 값으로 초기화하시겠습니까? (0) | 2023.05.24 |
| SqlConnectionStringBuilder를 사용하여 연결 문자열에서 데이터베이스 이름을 가져오는 방법 (0) | 2023.05.24 |
| 큰따옴표로 묶인 문자열에서 개체의 속성을 사용하려면 어떻게 해야 합니까? (0) | 2023.05.24 |
| 그 방법이 Moq에서 호출되지 않았는지 어떻게 확인합니까? (0) | 2023.05.24 |