Programing

Bash에서 해시 테이블을 정의하는 방법은 무엇입니까?

lottogame 2020. 2. 12. 07:58
반응형

Bash에서 해시 테이블을 정의하는 방법은 무엇입니까?


파이썬 사전 과 동일 하지만 Bash 는 무엇입니까 (OS X 및 Linux에서 작동해야 함).


배쉬 4

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

배쉬 3

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기능에 넣을 수 없습니다. declarebash 함수 내부를 사용 하면 해당 함수의 범위에 로컬 변수를 생성 하여 전역 배열에 액세스하거나 수정할 수 없습니다. bash 4에서는 선언 -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" )

다음과 같이 해시 이름을 지정하도록 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 개의 요소 읽기 / 쓰기를 수행 합니다.


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

다음 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

나는 @lhunath와 다른 사람들에게 연관 배열이 Bash 4와 함께 갈 수있는 방법이라는 것에 동의합니다. Bash 3 (OSX, 업데이트 할 수없는 오래된 배포판)에 붙어 있다면 expr을 사용할 수 있습니다. 정규식. 사전이 너무 크지 않을 때 특히 마음에 듭니다.

  1. 키와 값에 사용하지 않을 구분 기호를 두 개 선택하십시오 (예 : ','및 ':')
  2. 지도를 문자열로 작성하십시오 (시작과 끝에서도 구분 기호 ','에주의하십시오)

    animals=",moo:cow,woof:dog,"
    
  3. 정규식을 사용하여 값을 추출하십시오.

    get_animal {
        echo "$(expr "$animals" : ".*,$1:\([^,]*\),.*")"
    }
    
  4. 문자열을 분할하여 항목을 나열하십시오.

    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 이전에는 bash에서 연관 배열을 사용하는 좋은 방법이 없습니다. 가장 좋은 방법은 실제로 awk와 같은 것들을 지원하는 해석 언어를 사용하는 것입니다. 반면에, bash 4 그것들을 지원합니다.

에 관해서는 bash는 3 좋은 가지 방법, 여기에 힘 도움말보다 기준은 다음과 같습니다 http://mywiki.wooledge.org/BashFAQ/006


두 가지로, 커널 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.


배쉬 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[@])

동료가 방금이 스레드를 언급했습니다. 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과 친구를 제외하고 해시가 필요할 수있는 복잡성이 거의 포함되어서는 안됩니다 .


나는 또한 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을 살펴볼 수 있습니다 .

참고 URL : https://stackoverflow.com/questions/1494178/how-to-define-hash-tables-in-bash



반응형