쉘 스크립트의 연관 배열
우리는 셸 스크립팅을 위해 연관 배열 또는 Map like 데이터 구조를 시뮬레이션하는 스크립트가 필요합니까?
Irfan의 답변에 추가하려면 다음은 get()
맵 내용을 반복 할 필요가 없기 때문에 짧고 빠른 버전입니다 .
get() {
mapName=$1; key=$2
map=${!mapName}
value="$(echo $map |sed -e "s/.*--${key}=\([^ ]*\).*/\1/" -e 's/:SP:/ /g' )"
}
이식성이 주요 관심사가 아닌 경우 다른 옵션은 쉘에 내장 된 연관 배열을 사용하는 것입니다. 이것은 bash 4.0 (지금은 대부분의 주요 배포판에서 사용 가능하지만 직접 설치하지 않으면 OS X에서는 불가능), ksh 및 zsh에서 작동합니다.
declare -A newmap
newmap[name]="Irfan Zulfiqar"
newmap[designation]=SSE
newmap[company]="My Own Company"
echo ${newmap[company]}
echo ${newmap[name]}
쉘에 따라 typeset -A newmap
대신에 작업을 수행해야 할 수도 declare -A newmap
있고 전혀 필요하지 않을 수도 있습니다.
비 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"
거기에서 검색하기 위해 if 문을 던질 수 있습니다. [[$ var = ~ / blah /]] 인 경우 또는 무엇이든.
나는 당신이 뒤로 물러서서 맵이나 연관 배열이 실제로 무엇인지 생각해야한다고 생각합니다. 모든 것은 주어진 키에 대한 값을 저장하고 그 값을 빠르고 효율적으로 되 돌리는 방법입니다. 키를 반복하여 모든 키 값 쌍을 검색하거나 키 및 관련 값을 삭제할 수도 있습니다.
이제는 항상 셸 스크립팅 및 스크립트를 작성하지 않은 셸에서 이러한 속성을 가진 데이터 구조를 사용하십시오. 그만? 파일 시스템입니다.
실제로, 쉘 프로그래밍에서 연관 배열을 갖기 위해 필요한 것은 임시 디렉토리입니다. mktemp -d
연관 배열 생성자입니다.
prefix=$(basename -- "$0")
map=$(mktemp -dt ${prefix})
echo >${map}/key somevalue
value=$(cat ${map}/key)
echo
and를 사용 하고 싶지 않다면 언제든지 cat
작은 래퍼를 작성할 수 있습니다. 이러한 것들은 다음과 같은 임의의 변수를 설정하는 대신 값을 출력하지만 Irfan에서 모델링되었습니다 $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
편집 :이 접근법은 실제로 질문자가 제안한 sed를 사용하는 선형 검색보다 훨씬 빠르며 더 강력합니다 (키와 값에-, =, space, qnd ": SP :"를 포함 할 수 있음). 파일 시스템을 사용한다고해서 속도가 느려지지는 않습니다. 이 파일들은 실제로 전화하지 않는 한 디스크에 기록되는 것을 보장하지 않습니다 sync
. 수명이 짧은 이와 같은 임시 파일의 경우 많은 파일이 디스크에 쓰여지지 않을 가능성은 거의 없습니다.
다음 드라이버 프로그램을 사용하여 Irfan의 코드, Jerry의 Irfan 코드의 수정 및 코드의 벤치 마크를 수행했습니다.
#!/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
결과 :
$ 시간 ./driver.sh irfan 10 5 실제 0m0.975s 사용자 0m0.280s 시스 0m0.691s $ 시간 ./driver.sh brian 10 5 실제 0m0.226s 사용자 0m0.057s 시스 0m0.123s $ 시간 ./driver.sh jerry 10 5 실제 0m0.706s 사용자 0m0.228s 시스 0m0.530s $ 시간 ./driver.sh irfan 100 5 실제 0m10.633s 사용자 0m4.366s 시스 0m7.127s $ 시간 ./driver.sh brian 100 5 실제 0m1.682s 사용자 0m0.546s 시스 0m1.082s $ 시간 ./driver.sh jerry 100 5 진짜 0m9.315s 사용자 0m4.565s 시스 0m5.446s $ 시간 ./driver.sh irfan 10 500 실제 1m46.197s 사용자 0m44.869s 시스 1m12.282s $ 시간 ./driver.sh brian 10 500 실제 0m16.003s 사용자 0m5.135s 시스 0m10.396s $ 시간 ./driver.sh jerry 10 500 진짜 1m24.414s 사용자 0m39.696s 시스 0m54.834s $ 시간 ./driver.sh irfan 1000 5 실제 4m 사용자 3 분 17 시스 1m21.490s $ 시간 ./driver.sh brian 1000 5 실제 0m19.442s 사용자 0m5.287s 시스 0m10.751s $ 시간 ./driver.sh jerry 1000 5 진짜 5m29.136s 사용자 4m48.926s 시스 0m59.336s
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
Bash4는이를 기본적으로 지원합니다. 사용하지 마십시오 grep
또는 eval
, 그들은 해킹의 추악한입니다.
예제 코드가 포함 된 자세한 상세 답변은 https://stackoverflow.com/questions/3467959를 참조하십시오.
####################################################################
# 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; }'
}
예:
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
이제이 질문에 대답하십시오.
다음 스크립트는 쉘 스크립트에서 연관 배열을 시뮬레이션합니다. 간단하고 이해하기 쉽습니다.
map은 keyValuePair가 --name = Irfan --designation = SSE --company = My : SP : Own : SP : Company로 저장된 끝없는 문자열 일뿐입니다.
값은 공백이 ': SP :'로 바뀝니다.
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
편집 : 모든 키를 가져 오는 다른 방법을 추가했습니다.
getKeySet() {
if [ "$#" != 1 ];
then
exit 1;
fi
mapName=$1;
eval map="\"\$$mapName\""
keySet=`
echo $map |
sed -e "s/=[^ ]*//g" -e "s/\([ ]*\)--/\1/g"
`
}
Bash 3에는 훌륭하고 간단한 해결책이있는 특별한 경우가 있습니다.
당신은 많은 변수를 처리하지 않으려는 경우, 또는 키를 단순히 잘못된 변수 식별자, 및 당신의 배열이 보장 256 개 항목을 당신이 함수의 반환 값을 악용 할 수 있습니다. 이 솔루션은 값을 변수로 쉽게 사용할 수 있거나 성능이 비명을 지르는 반복으로 하위 쉘이 필요하지 않습니다. 또한 Bash 4 버전과 거의 비슷하게 읽을 수 있습니다.
가장 기본적인 버전은 다음과 같습니다.
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[$?]}
에 작은 따옴표를 사용하십시오. case
그렇지 않으면 글 로빙 될 수 있습니다. 처음부터 정적 / 고정 해시에 유용하지만 hash_keys=()
배열 에서 색인 생성기를 작성할 수 있습니다.
조심하십시오, 기본값은 첫 번째 요소이므로 0 번째 요소를 따로 설정할 수 있습니다.
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
경고 : 길이가 잘못되었습니다.
또는 0부터 시작하여 인덱싱을 유지하려는 경우 다른 인덱스 값을 예약하고 존재하지 않는 키로부터 보호 할 수 있지만 읽기는 쉽지 않습니다.
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[$?]}
또는 길이를 올바르게 유지하려면 인덱스를 1 씩 오프셋하십시오.
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))]}
동적 변수 이름을 사용하고 변수 이름이 해시 맵의 키처럼 작동하게 할 수 있습니다.
예를 들어, 다음과 같이 이름, credit이라는 두 개의 열이있는 입력 파일이 있고 각 사용자의 수입을 합산하려는 경우 :
Mary 100
John 200
Mary 50
John 300
Paul 100
Paul 400
David 100
다음 명령은 동적 변수를 키로 사용하여 map _ $ {person} 형식으로 모든 것을 합산합니다 .
while read -r person money; ((map_$person+=$money)); done < <(cat INCOME_REPORT.log)
결과를 읽으려면 :
set | grep map
출력은 다음과 같습니다.
map_David=100
map_John=500
map_Mary=150
map_Paul=500
이러한 기술을 자세히 설명하기 위해 GitHub에서 HashMap Object , shell_map 과 같은 기능을 개발 중 입니다.
" HashMap 인스턴스 " 를 작성하기 위해 shell_map 함수 는 다른 이름으로 자체 사본을 작성할 수 있습니다. 각각의 새로운 함수 사본에는 다른 $ FUNCNAME 변수가 있습니다. 그런 다음 $ FUNCNAME을 사용하여 각 Map 인스턴스의 네임 스페이스를 만듭니다.
맵 키는 $ FUNCNAME_DATA_ $ KEY 형식의 전역 변수입니다. 여기서 $ KEY는 맵에 추가 된 키입니다. 이러한 변수는 동적 변수 입니다.
아래 예제로 사용할 수 있도록 간단한 버전을 소개하겠습니다.
#!/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
}
용법:
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!"
나는 이전에 질문을 보지 못한 것이 얼마나 유감 스러웠습니까? 지도 (Associative arrays)를 포함하는 라이브러리 쉘 프레임 워크 를 작성했습니다. 그것의 마지막 버전은 여기 에서 찾을 수 있습니다 .
예:
#!/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")"
이미 언급했듯이 가장 성능이 좋은 방법은 키 / val을 파일에 쓴 다음 grep / awk를 사용하여 검색하는 것입니다. 모든 종류의 불필요한 IO처럼 들리지만 디스크 캐시가 작동하여 위의 방법 중 하나를 사용하여 메모리에 저장하는 것보다 훨씬 빠릅니다.
내가 좋아하는 빠르고 깨끗한 방법은 다음과 같습니다.
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`
키당 단일 값을 적용하려면 hput ()에서 약간의 grep / sed 작업을 수행 할 수도 있습니다.
몇 년 전에 나는 다른 기능 (로깅, 구성 파일, 명령 줄 인수에 대한 확장 지원, 도움말 생성, 단위 테스트 등) 중에서 연관 배열을 지원하는 bash 용 스크립트 라이브러리를 작성했습니다. 라이브러리에는 연관 배열을위한 랩퍼가 포함되어 있으며 적절한 모델 (bash4의 내부 및 이전 버전의 에뮬레이션)로 자동 전환됩니다. 쉘 프레임 워크라고하며 origo.ethz.ch에서 호스팅되었지만 오늘날 리소스는 닫힙니다. 누군가가 여전히 필요하면 공유 할 수 있습니다.
셸에는 데이터 구조와 같은 기본 제공 맵이 없으므로 원시 문자열을 사용하여 다음과 같은 항목을 설명합니다.
ARRAY=(
"item_A|attr1|attr2|attr3"
"item_B|attr1|attr2|attr3"
"..."
)
항목 및 해당 속성을 추출 할 때 :
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
이것은 다른 사람들의 대답보다 영리하지 않은 것처럼 보이지만 새로운 사람들이 이해하기 쉽습니다.
jq가 사용 가능한 경우 다른 옵션 추가 :
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 '"'
Vadim의 솔루션을 다음과 같이 수정했습니다.
####################################################################
# 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; }'
}
존재하지 않는 키를 요청하면 오류가 반환되지 않도록 map_get을 변경하는 것이 좋지만 부작용은 누락 된 맵을 자동으로 무시하지만 방금 후 유스 케이스에 더 적합합니다. 루프에서 항목을 건너 뛰기 위해 키를 확인하려고했습니다.
늦은 답변이지만 다음과 같은 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
참고 URL : https://stackoverflow.com/questions/688849/associative-arrays-in-shell-scripts
'program tip' 카테고리의 다른 글
쉘을 사용하여 PostgreSQL에 데이터베이스가 있는지 확인하십시오. (0) | 2020.07.26 |
---|---|
Rails에서 다른 형식의 일부를 어떻게 렌더링합니까? (0) | 2020.07.26 |
setuptools / distribute에 패키지 데이터를 포함시키는 방법은 무엇입니까? (0) | 2020.07.26 |
페이드 효과가있는 요소 추가 [jQuery] (0) | 2020.07.26 |
문자열 리소스의 HTML? (0) | 2020.07.26 |