BASH 연관 배열 인쇄


17

모든 요소를 ​​반복하지 않고 전체 배열 ([key] = value)을 인쇄하는 방법이 있습니까?

일부 요소로 배열을 만들었다 고 가정하십시오.

declare -A array
array=([a1]=1 [a2]=2 ... [b1]=bbb ... [f500]=abcdef)

나는 전체 배열을 다시 인쇄 할 수 있습니다

for i in "${!array[@]}"
do
echo "${i}=${array[$i]}"
done

그러나 bash는 키 ${!array[@]}와 값 모두에서 하나의 "go"로 모든 배열 요소를 얻는 방법을 이미 알고있는 것 같습니다 ${array[@]}.

bash가 루프 없이이 정보를 인쇄하게하는 방법이 있습니까?

편집 :
typeset -p array그렇습니다!
그러나 단일 대체로 접두사와 접미사를 모두 제거 할 수는 없습니다.

a="$(typeset -p array)"
b="${a##*(}"
c="${b%% )*}"

출력의 키 = 값 부분 만 가져 오거나 인쇄하는 더 확실한 방법이 있습니까?

답변:


15

나는 당신이 두 가지 다른 것을 요구하고 있다고 생각합니다.

bash가 루프 없이이 정보를 인쇄하게하는 방법이 있습니까?

예, 그러나 루프를 사용하는 것만 큼 좋지 않습니다.

출력의 키 = 값 부분 만 가져 오거나 인쇄하는 더 확실한 방법이 있습니까?

예, for루프입니다. 외부 프로그램이 필요없고 간단하며 장점없이 정확한 출력 형식을 쉽게 제어 할 수 있다는 장점이 있습니다.


declare -p( typeset -p) 의 출력을 처리하려는 모든 솔루션은 a) 괄호 또는 괄호를 포함하는 변수 자체의 가능성, b) declare -p셸에 대한 출력을 유효하게 입력하기 위해 추가 해야하는 따옴표 를 처리해야합니다.

예를 들어, b="${a##*(}"키 / 값에 여는 괄호가 포함되어 있으면 확장에서 일부 값을 사용합니다. 가장 긴 접두사 ##를 제거하는 을 사용했기 때문 입니다. 동일합니다 . 물론 더 정확하게 인쇄 된 상용구를 일치시킬 수는 있지만 인용 부호를 모두 원하지 않으면 여전히 어려움을 겪을 것입니다.c="${b%% )*}"declare

당신이 그것을 필요로하지 않으면 아주 좋아 보이지 않습니다.

$ declare -A array=([abc]="'foobar'" [def]='"foo bar"')
$ declare -p array
declare -A array='([def]="\"foo bar\"" [abc]="'\''foobar'\''" )'

for루프를 사용하면 원하는대로 출력 형식을 쉽게 선택할 수 있습니다.

# without quoting
$ for x in "${!array[@]}"; do printf "[%s]=%s\n" "$x" "${array[$x]}" ; done
[def]="foo bar"
[abc]='foobar'

# with quoting
$ for x in "${!array[@]}"; do printf "[%q]=%q\n" "$x" "${array[$x]}" ; done
[def]=\"foo\ bar\"
[abc]=\'foobar\'

거기 에서 출력 형식을 변경하는 것도 간단 합니다 (키 주위의 괄호를 제거하고 모든 키 / 값 쌍을 한 줄에 넣으십시오 ...). 쉘 자체가 아닌 다른 것을 인용 해야하는 경우 여전히 직접 작성해야하지만 최소한 원시 데이터가 있어야합니다. 키나 값에 줄 바꿈이 있으면 인용 부호가 필요할 수 있습니다.

현재 Bash (4.4, 생각합니다)를 사용하면 printf "[%s]=%s" "${x@Q}" "${array[$x]@Q}"대신 사용할 수도 있습니다 printf "%q=%q". 다소 좋은 인용 형식을 생성하지만 물론 작성해야 할 작업이 조금 더 있습니다. (그리고 그것은 대소 문자를 인용하지 않는 @배열 키로 %q인용합니다.)

for 루프가 쓰기에 너무 지친 것처럼 보이면 어딘가에 함수를 저장하십시오 (여기 인용하지 않고)

printarr() { declare -n __p="$1"; for k in "${!__p[@]}"; do printf "%s=%s\n" "$k" "${__p[$k]}" ; done ;  }  

그런 다음 사용하십시오.

$ declare -A a=([a]=123 [b]="foo bar" [c]="(blah)")
$ printarr a
a=123
b=foo bar
c=(blah)

인덱스 배열에서도 작동합니다.

$ b=(abba acdc)
$ printarr b
0=abba
1=acdc

printf ...%q...배열에 @% q가 따옴표로 표시되지 않고 키 a=([@]=value)의 구문 오류 인 경우 배열 의 출력은 쉘에 다시 입력하기에 적합 하지 않습니다 bash.
Stéphane Chazelas

@ StéphaneChazelas입니다. "${x@Q}"모든 문자열을 인용하고 멋지게 보이기 때문에 따옴표도 인용합니다. 사용에 대한 메모를 추가했습니다.
ilkkachu

예, mksh에서 복사했습니다. 다른 형태의 다른 연산자는 다른 연산자와 함께 사용할 수 없습니다. 다시 말하지만, 볼 zsh(다시 수십 년간 배쉬의 선행 것을하고있는 당신은 인용 스타일을 선택할 수 있습니다 : $ {(Q) VAR}, $ {(전분기 대비) VAR}를 ...)의 변수 확장 플래그와 함께 더 나은 디자인을 위해. bash는 빈 문자열을 인용하지 않는다는 점에서 mksh와 동일한 문제가 있습니다 (어쨌든 bash는 빈 키를 지원하지 않으므로 여기서는 문제가되지 않습니다). (작은 따옴표가 아닌 다른 인용 스타일을 사용하는 경우 또한, ${var@Q}에 리조트 $'...'몇 가지 값을)는 코드가 같은 장소에 재 투입하는 것이 중요합니다.
Stéphane Chazelas

@ StéphaneChazelas, 빈 문자열이 아닌 설정되지 않은 값을 의미한다고 생각하십니까? ( x=; echo "${x@Q}"주는가 '', unset x; echo "${x@Q}"배쉬의이. 아무것도 제공하지 않음) @Q을 선호하는 것 같다 $'\n'실제로 어떤 상황에서 잘 수있는 문자 줄 바꿈, 이상 (하지만 난 다른 사람이 원하는 것을 말할 수 없다). 물론 선택하는 것은 나쁘지 않을 것입니다.
ilkkachu

아, 미안하지만, 나는 그것을 몰랐다. mksh와의 차이점입니다. $'...'구문은 같은 일에 잠재적 인 문제가 LC_ALL=zh_HK.big5hkscs bash -c 'a=$'\''\n\u3b1'\''; printf "%s\n" "${a@Q}"'출력되는 $'\n<0xa3><0x5c>'0x5c그 따옴표가 다른 로케일로 해석 한 경우 당신은 문제가 있다면 것, 그래서 혼자가 백 슬래시입니다.
Stéphane Chazelas

9
declare -p array
declare -A array='([a2]="2" [a1]="1" [zz]="Hello World" [b1]="bbb" [f50]="abcd" )'

포크 2 개

아마도 이것 :

printf "%s\n" "${!array[@]}"
a2
a1
f50
zz
b1

printf "%s\n" "${array[@]}"
2
1
abcd
Hello World
bbb

printf "%s\n" "${!array[@]}" "${array[@]}" | pr -2t
a2                              2
a1                              1
f50                             abcd
zz                              Hello World
b1                              bbb

포크 3 개

아니면 이거:

paste -d= <(printf "%s\n" "${!array[@]}") <(printf "%s\n" "${array[@]}")
a2=2
a1=1
f50=abcd
zz=Hello World
b1=bbb

포크 없음

비교 될

for i in "${!array[@]}";do printf "%s=%s\n" "$i" "${array[$i]}";done
a2=2
a1=1
f50=abcd
zz=Hello World
b1=bbb

실행 시간 비교

마지막 구문은 포크를 사용하지 않기 때문에 더 빠를 수 있습니다.

time printf "%s\n" "${!array[@]}" "${array[@]}" | pr -2t | wc
      5      11      76
real    0m0.005s
user    0m0.000s
sys     0m0.000s

time paste -d= <(printf "%s\n" "${!array[@]}") <(printf "%s\n" "${array[@]}") | wc
      5       6      41
real    0m0.008s
user    0m0.000s
sys     0m0.000s

time for i in "${!array[@]}";do printf "%s=%s\n" "$i" "${array[$i]}";done | wc
      5       6      41
real    0m0.002s
user    0m0.000s
sys     0m0.001s

그러나 배열이 커지면이 확인은 사실이 아닙니다. 작은 프로세스에 포크를 줄이는 것이 효율적인 경우에는 전용 도구를 사용하는 것이 더 큰 프로세스에 더 효율적입니다.

for i in {a..z}{a..z}{a..z};do array[$i]=$RANDOM;done


time printf "%s\n" "${!array[@]}" "${array[@]}" | pr -2t | wc
  17581   35163  292941
real    0m0.150s
user    0m0.124s
sys     0m0.036s

time paste -d= <(printf "%s\n" "${!array[@]}") <(printf "%s\n" "${array[@]}") | wc
  17581   17582  169875
real    0m0.140s
user    0m0.000s
sys     0m0.004s

time for i in "${!array[@]}";do printf "%s=%s\n" "$i" "${array[$i]}";done | wc
  17581   17582  169875
real    0m0.312s
user    0m0.268s
sys     0m0.076s

두 가지 ( 갈래의 ) 솔루션 모두 정렬 을 사용하기 때문에 변수에 개행 문자 가 포함되어 있으면 어느 것도 작동하지 않습니다 . 이 경우 유일한 방법은 for루프입니다.


영리 해 보이지만 두 가지 방법 모두 for. 정말 부끄러운 일입니다.
Satō Katsura

@SatoKatsura 동의하지만 속도가 느리면 구문 사용 pr이 짧습니다 ... pr큰 배열에서도 구문이 느리게 유지 되는지 잘 모르겠습니다 !
F. Hauri

2
@MiniMax 올바른 결과를 생성하지 않기 때문에 (같은 요소, 잘못된 순서). 배열을 압축 ${!array[@]}하고 ${array[@]}먼저 작동하도록해야합니다.
Satō Katsura

1
마지막 스 니펫 paste은 한 줄에 작성된 질문 의 루프 보다 길지만 두 개의 서브 쉘과 외부 프로그램이 필요합니다. 얼마나 깔끔한가요? 출력을 페이지 매김하려고 시도하기 때문에 많은 요소가 있으면 솔루션 도 중단됩니다. 간단한 루프와 비교할 때 기억하기 시작한 것과 비슷한 것을 사용해야 하며, 그보다 더 길다. forfor i in "${!array[@]}"; do echo "$i=${array[$i]}" ; donepr| pr -2t -l"${#array[@]}"
ilkkachu

1
에서는 bash, cmd1 | cmd2수단 (2 개) 의 포크, CMD1 또는 CMD2 또는 모두 내장하더라도.
Stéphane Chazelas

2

더 나은 연관 배열 지원을 가진 쉘을 찾고 있다면를 시도하십시오 zsh.

에서 zsh(연관 배열이 떠들썩한 파티를위한 경우 ksh93 2009 년 1993 년에 비해 1998 년에 추가 된 경우), $var또는 ${(v)var}제 (비어)로 확장 해시의, ${(k)var}(같은 순서로) (비어) 키, 그리고 ${(kv)var}키와 값 모두.

배열처럼 빈 값을 유지하려면 @플래그 를 인용하고 사용해야합니다 .

따라서 키와 값을 인쇄하려면

printf '%s => %s\n' "${(@kv)var}"

비어있는 해시를 설명하지만 다음을 수행해야합니다.

(($#var)) &&  printf '%s => %s\n' "${(@kv)var}"

또한 zsh을보다 더 분별 유용한 배열 정의 문법을 사용 유의 ksh93의 (복사로 bash) :

typeset -A var
var=(k1 v1 k2 v2 '' empty '*' star)

연관 배열을 복사하거나 병합하기가 훨씬 쉽습니다.

var2=("${(@kv)var1}")
var3+=("${(@kv)var2}")
var4=("${@kv)var4}" "${(@kv)var5}")

(당신은 쉽게와 루프없이 해시를 복사 할 수 없습니다 bash하고 있습니다 bashNUL은 바이트로 현재 비어 키 또는 키 / 값을 지원하지 않습니다).

zsh일반적으로 연관 배열로 작업해야하는 배열 압축 기능 도 참조하십시오 .

keys=($(<keys.txt)) values=($(<values.txt))
hash=(${keys:^values})

1

이후 조판은 단지 출력을 편집하지 왜 당신이 원하는 무엇입니까?

typeset -p array | sed s/^.*\(// | tr -d ")\'\""  | tr "[" "\n" | sed s/]=/' = '/

준다

a2 = 2  
a1 = 1  
b1 = bbb 

어디

array='([a2]="2" [a1]="1" [b1]="bbb" )'

장황하지만 형식이 어떻게 작동하는지 쉽게 알 수 있습니다 . sedtr 명령 을 점점 더 많이 사용하여 파이프 라인을 실행하십시오 . 예쁜 인쇄 취향에 맞게 수정하십시오.


이러한 종류의 파이프 라인은 배열의 일부 키 또는 값에 괄호, 괄호 또는 따옴표와 같이 바꿀 문자가 포함되는 순간 실패하게됩니다. 그리고의 파이프 라인 seds와 tr의는보다 훨씬 간단하지 않습니다 for와 루프 printf.
ilkkachu

또한 tr문자별로 번역 한다는 것을 알고 문자열과 일치하지 않습니까? tr "]=" " =""]"는 공간 및로 변경 =내지 An =체위에 관계없이. 따라서 세 가지를 모두 하나로 결합 할 수 tr있습니다.
ilkkachu

영숫자가 아닌 문자 중 일부에 대해 매우 사실입니다. 그러나 그것들을 다루어야 할 것은 데이터 피드에 그것들을 가져야 할 정말로 좋은 이유가없고 그것이 우리가 여기에 오기 전에 걸러 졌다고 가정하는 질문에 언급 되어 있지 않으면 더 복잡하고 읽기 쉽지 않습니다 . 항상 당신의 명시적인주의가 있어야합니다. 이 파이프 라인은 완벽하게 작동하거나 얼굴을 날려 버리는 printf glob보다 예를 들어 디버깅 목적으로 더 간단합니다. 여기서 요소 당 하나의 간단한 변경을 수행하고 테스트 한 다음 1을 더 추가하십시오.
Nadreck

내 잘못이야! 내 _tr_s와 _sed_s가 완전히 섞여 있습니다! 최신 편집에서 수정되었습니다.
Nadreck

1

또 다른 옵션은 원하는 변수와 grep을 모두 나열하는 것입니다.

set | grep -e '^aa='

나는 이것을 디버깅에 사용합니다. 모든 변수를 나열하기 때문에 성능이 매우 뛰어납니다.

이 작업을 자주 수행하는 경우 다음과 같은 기능을 수행 할 수 있습니다.

aap() { set | grep -e "^$1="; }

불행히도 시간을 사용하여 성능을 확인할 때 :

$ time aap aa aa=([0]="abc") . real 0m0.014s user 0m0.003s sys 0m0.006s

따라서이 작업을 매우 자주 수행하는 경우 @ F.Hauri의 NO FORKS 버전이 훨씬 빠르기 때문에 원합니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.