`set -u`로 빈 배열 확장을 배시


103

나는 bash 스크립트를 작성 중이며 set -u빈 배열 확장에 문제가 있습니다. bash는 확장 중에 빈 배열을 설정되지 않은 변수로 취급하는 것으로 보입니다.

$ set -u
$ arr=()
$ echo "foo: '${arr[@]}'"
bash: arr[@]: unbound variable

( declare -a arr도 도움이되지 않습니다.)

이에 대한 일반적인 해결책은 ${arr[@]-}대신 사용 하여 ( "정의되지 않은") 빈 배열 대신 빈 문자열로 대체하는 것입니다. 그러나 이것은 좋은 해결책이 아닙니다. 지금은 하나의 빈 문자열이있는 배열과 빈 배열을 구별 할 수 없기 때문입니다. (@ -expansion는 확장, bash는 특별한 "${arr[@]}""${arr[0]}" "${arr[1]}" …그 명령 라인을 구축하기위한 완벽한 도구 만드는.)

$ countArgs() { echo $#; }
$ countArgs a b c
3
$ countArgs
0
$ countArgs ""
1
$ brr=("")
$ countArgs "${brr[@]}"
1
$ countArgs "${arr[@]-}"
1
$ countArgs "${arr[@]}"
bash: arr[@]: unbound variable
$ set +u
$ countArgs "${arr[@]}"
0

그렇다면 배열의 길이를 확인하거나 if(아래 코드 샘플 참조) -u짧은 부분에 대한 설정을 끄는 것 외에 그 문제를 해결할 수있는 방법이 있습니까?

if [ "${#arr[@]}" = 0 ]; then
   veryLongCommandLine
else
   veryLongCommandLine "${arr[@]}"
fi

업데이트 :bugs ikegami의 설명으로 인해 태그가 제거 되었습니다.

답변:


17

유일한 안전 관용구이다${arr[@]+"${arr[@]}"}

이것은 이미 ikegami의 답변 에서 권장 사항 이지만이 스레드에는 많은 잘못된 정보와 추측이 있습니다. 같은 다른 패턴, ${arr[@]-}또는 ${arr[@]:0}이다 하지 배쉬의 모든 주요 버전에서 안전합니다.

아래 표에서 볼 수 있듯이 모든 최신 Bash 버전에서 신뢰할 수있는 유일한 확장은 ${arr[@]+"${arr[@]}"}(열 +")입니다. 참고로, Bash 4.2에서는 (불행히도) 짧은 ${arr[@]:0}관용구를 포함하여 몇 가지 다른 확장이 실패하며 , 이는 잘못된 결과를 생성 할뿐만 아니라 실제로 실패합니다. 4.4 이전 버전, 특히 4.2 버전을 지원해야하는 경우 이것이 유일하게 작동하는 관용구입니다.

버전 별 다양한 관용구 스크린 샷

안타깝게도 +한눈에 똑같이 보이는 다른 확장팩은 실제로 다른 동작을 내 보냅니다. :+확장은 안전 하지 않습니다. :-expansion 은 하나의 빈 요소 ( (''))가 있는 배열을 "null"로 취급하므로 동일한 결과로 (일관되게) 확장되지 않기 때문입니다.

"${arr[@]+${arr[@]}}"대략적으로 동일 할 것으로 예상했던 중첩 배열 ( ) 대신 전체 확장을 인용하는 것은 4.2에서 비슷하게 안전하지 않습니다.

이 요점 에서 몇 가지 추가 버전의 bash에 대한 결과와 함께이 데이터를 생성 한 코드를 볼 수 있습니다 .


1
나는 당신이 시험하는 것을 보지 않는다 "${arr[@]}". 내가 뭔가를 놓치고 있습니까? 내가 볼 수있는 것에서 적어도 5.x.
x-yuri

1
@ x-yuri 예, Bash 4.4가 상황을 수정했습니다. 스크립트가 4.4 이상에서만 실행된다는 것을 알고 있다면이 패턴을 사용할 필요가 없지만 많은 시스템이 여전히 이전 버전에 있습니다.
dimo414

물론. 멋지게
보임

81

문서에 따르면

첨자에 값이 할당 된 경우 배열 변수가 설정된 것으로 간주됩니다. 널 문자열은 유효한 값입니다.

값이 할당 된 첨자가 없으므로 배열이 설정되지 않습니다.

그러나 문서는 여기에 오류가 적절하다고 제안하지만 4.4 이후 에는 더 이상 그렇지 않습니다 .

$ bash --version | head -n 1
GNU bash, version 4.4.19(1)-release (x86_64-pc-linux-gnu)

$ set -u

$ arr=()

$ echo "foo: '${arr[@]}'"
foo: ''

이전 버전에서 원하는 것을 얻기 위해 인라인을 사용할 수있는 조건이 있습니다 . ${arr[@]+"${arr[@]}"}대신 "${arr[@]}".

$ function args { perl -E'say 0+@ARGV; say "$_: $ARGV[$_]" for 0..$#ARGV' -- "$@" ; }

$ set -u

$ arr=()

$ args "${arr[@]}"
-bash: arr[@]: unbound variable

$ args ${arr[@]+"${arr[@]}"}
0

$ arr=("")

$ args ${arr[@]+"${arr[@]}"}
1
0: 

$ arr=(a b c)

$ args ${arr[@]+"${arr[@]}"}
3
0: a
1: b
2: c

bash 4.2.25 및 4.3.11로 테스트되었습니다.


4
누구든지 이것이 작동하는 방법과 이유를 설명 할 수 있습니까? [@]+실제로 수행하는 작업과 두 번째 ${arr[@]}가 바인딩되지 않은 오류를 일으키지 않는 이유 에 대해 혼란 스럽습니다 .
Martin von Wittich

2
${parameter+word}이 설정되지 않은 word경우 에만 확장 됩니다 parameter.
ikegami 2011

2
${arr+"${arr[@]}"}더 짧고 잘 작동하는 것 같습니다.
Per Cederberg

3
@ Per Cerderberg, 작동하지 않습니다. unset arr, arr[1]=a, args ${arr+"${arr[@]}"}args ${arr[@]+"${arr[@]}"}
이케 가미

1
정확히 말하면 +확장이 발생하지 않는 경우 (즉, 빈 배열) 확장은 nothing 으로 대체되며 , 이는 정확히 빈 배열이 확장되는 것입니다. :+단일 요소 ('')배열을 설정되지 않은 것으로 취급 하고 유사하게 아무것도 확장하지 않고 값을 잃기 때문에 안전하지 않습니다.
dimo414

23

@ikegami의 수락 된 대답은 미묘하게 잘못되었습니다! 올바른 주문은 ${arr[@]+"${arr[@]}"}다음과 같습니다.

$ countArgs () { echo "$#"; }
$ arr=('')
$ countArgs "${arr[@]:+${arr[@]}}"
0   # WRONG
$ countArgs ${arr[@]+"${arr[@]}"}
1   # RIGHT
$ arr=()
$ countArgs ${arr[@]+"${arr[@]}"}
0   # Let's make sure it still works for the other case...

더 이상 차이를 만들지 않습니다. bash-4.4.23: arr=('') && countArgs "${arr[@]:+${arr[@]}}"생성합니다 1. 그러나 ${arr[@]+"${arr[@]}"}형식은 콜론을 추가하거나 추가하지 않음으로써 비어 있거나 비어 있지 않은 값을 구별 할 수 있습니다.
x-yuri

arr=('') && countArgs ${arr[@]:+"${arr[@]}"}-> 0, arr=('') && countArgs ${arr[@]+"${arr[@]}"}-> 1.
x-yuri

1
이것은 오래 전에 내 대답에서 수정되었습니다. (사실, 나는 그 효과에 대한이 답변에 대해 이전에 코멘트를 남긴 것이 확실합니까?!)
ikegami

16

최근 릴리스 (2016/09/16) bash 4.4 (예 : Debian stretch에서 사용 가능)에서 배열 처리가 변경된 것으로 나타났습니다.

$ bash --version | head -n1
bash --version | head -n1
GNU bash, version 4.4.0(1)-release (x86_64-pc-linux-gnu)

이제 빈 배열 확장이 경고를 표시하지 않습니다.

$ set -u
$ arr=()
$ echo "${arr[@]}"

$ # everything is fine

bash-4.4.12 "${arr[@]}"충분히 확인할 수 있습니다 .
x-yuri

14

이것은 arr [@] 중복을 선호하지 않고 빈 문자열을 사용 해도되는 다른 옵션 일 수 있습니다.

echo "foo: '${arr[@]:-}'"

테스트하려면 :

set -u
arr=()
echo a "${arr[@]:-}" b # note two spaces between a and b
for f in a "${arr[@]:-}" b; do echo $f; done # note blank line between a and b
arr=(1 2)
echo a "${arr[@]:-}" b
for f in a "${arr[@]:-}" b; do echo $f; done

10
이것은 변수를 보간하는 경우에만 작동하지만 배열을 사용하려는 경우 for배열이 정의되지 않거나 비어있는 것으로 정의 될 때 하나의 빈 문자열로 끝납니다. 루프 본문을 원할 수 있습니다. 배열이 정의되지 않은 경우 실행되지 않습니다.
애쉬 베를린 - 테일러

감사합니다 @AshBerlin, 독자들이 알 수 있도록 내 답변에 for 루프를 추가했습니다
Jayen

이 접근 방식에 -1, 그것은 단순히 잘못된 것입니다. 이것은 빈 배열을 동일하지 않은 단일 빈 문자열로 대체합니다. 수락 된 답변에서 제안 된 패턴 ${arr[@]+"${arr[@]}"}은 빈 배열 상태를 올바르게 유지합니다.
dimo414

이 확장이 무너지는 상황을 보여주는 내 답변 도 참조하십시오 .
dimo414

정확하지 않습니다. 빈 문자열을 제공 할 것이라고 명시 적으로 말하며 빈 문자열을 볼 수있는 두 가지 예도 있습니다.
Jayen

7

@ikegami의 대답은 정확하지만 구문을 고려합니다. ${arr[@]+"${arr[@]}"} . 긴 배열 변수 이름을 사용하면 평소보다 더 빨리 스파게티처럼 보이기 시작합니다.

대신 이것을 시도하십시오.

$ set -u

$ count() { echo $# ; } ; count x y z
3

$ count() { echo $# ; } ; arr=() ; count "${arr[@]}"
-bash: abc[@]: unbound variable

$ count() { echo $# ; } ; arr=() ; count "${arr[@]:0}"
0

$ count() { echo $# ; } ; arr=(x y z) ; count "${arr[@]:0}"
3

Bash 배열 슬라이스 연산자가 매우 관대 해 보입니다.

그렇다면 Bash는 왜 배열의 엣지 케이스를 처리하기 어렵게 만들었습니까? 한숨. 버전이 배열 슬라이스 연산자의 남용을 허용 할 것이라고 보장 할 수는 없지만 멋지게 작동합니다.

주의 사항 : 사용중인 GNU bash, version 3.2.25(1)-release (x86_64-redhat-linux-gnu) 마일리지는 다를 수 있습니다.


9
ikegami는 원래 이것을 가지고 있었지만 이론상 (이것이 작동해야하는 이유가 없음)과 실제로 (OP의 bash 버전이 그것을 받아들이지 않았 음) 신뢰할 수 없기 때문에 제거했습니다.

@hvd : 업데이트 해 주셔서 감사합니다. 독자 : 위 코드가 작동하지 않는 bash 버전을 찾으면 주석을 추가하십시오.
kevinarpe

hvp는 이미 그랬고 나도 말해주지 : "${arr[@]:0}"gives -bash: arr[@]: unbound variable.
ikegami

여러 버전에서 작동해야하는 한 가지는 기본 배열 값을로 설정하고 모든 곳 arr=("_dummy_")에서 확장을 사용하는 것 ${arr[@]:1}입니다. 이것은 센티넬 값을 언급하는 다른 답변에서 언급됩니다.
init_js

1
@init_js : 귀하의 편집은 슬프게도 거부되었습니다. 별도의 답변으로 추가하는 것이 좋습니다. (참조 : stackoverflow.com/review/suggested-edits/19027379 )
kevinarpe

6

참으로 "흥미로운"불일치.

더욱이,

$ set -u
$ echo $#
0
$ echo "$1"
bash: $1: unbound variable   # makes sense (I didn't set any)
$ echo "$@" | cat -e
$                            # blank line, no error

@ikegami가 설명하는 의미에서 현재 동작이 버그가 아닐 수 있다는 데 동의하지만, IMO는 버그가 정의 ( "세트") 자체 및 / 또는 일관성없이 적용된다는 사실에 있다고 말할 수 있습니다. 맨 페이지의 앞 단락은 다음과 같습니다.

... ${name[@]}이름의 각 요소를 별도의 단어로 확장합니다. 배열 구성원이 없으면 ${name[@]}아무것도 확장되지 않습니다.

에서 위치 매개 변수의 확장에 대해 말하는 내용과 완전히 일치 "$@"합니다. 배열과 위치 매개 변수의 동작에 다른 불일치가 없다는 것은 아닙니다.하지만 나에게이 세부 사항이 둘 사이에 불일치해야한다는 힌트는 없습니다.

계속,

$ arr=()
$ echo "${arr[@]}"
bash: arr[@]: unbound variable   # as we've observed.  BUT...
$ echo "${#arr[@]}"
0                                # no error
$ echo "${!arr[@]}" | cat -e
$                                # no error

그래서 우리는 요소의 개수 (0) 또는 키의 (빈) 목록을 얻을 수 arr[]없을 정도로 바인딩 이 해제되지 않았 습니까? 나에게 이것들은 합리적이고 유용합니다. 유일한 이상 치는 ${arr[@]}(및 ${arr[*]}) 확장 인 것 같습니다 .


2

@ikegami의 (허용됨) 및 @kevinarpe의 (또한 좋은) 답변을 보완하고 있습니다.

"${arr[@]:+${arr[@]}}"문제를 해결하기 위해 수행 할 수 있습니다 . 오른쪽 (즉, 이후 :+)은 왼쪽이 정의되지 않았거나 null 인 경우에 사용될 표현식을 제공합니다.

구문은 난해합니다. 표현식의 오른쪽은 매개 변수 확장을 거치므로 일관된 인용을 사용하는 데 특별한주의를 기울여야합니다.

: example copy arr into arr_copy
arr=( "1 2" "3" )
arr_copy=( "${arr[@]:+${arr[@]}}" ) # good. same quoting. 
                                    # preserves spaces

arr_copy=( ${arr[@]:+"${arr[@]}"} ) # bad. quoting only on RHS.
                                    # copy will have ["1","2","3"],
                                    # instead of ["1 2", "3"]

@kevinarpe에서 언급 한 것처럼 덜 복잡한 구문은 배열 슬라이스 표기법 ${arr[@]:0}(Bash 버전에서 >= 4.4) 을 사용하는 것입니다.이 표기법 은 인덱스 0부터 시작하는 모든 매개 변수로 확장됩니다. 또한 많은 반복이 필요하지 않습니다. 이 확장은에 관계없이 작동 set -u하므로 언제든지 사용할 수 있습니다. man 페이지에는 다음과 같이 나와 있습니다 ( Parameter Expansion 아래 ) :

  • ${parameter:offset}

  • ${parameter:offset:length}

    ... 매개 변수가 @또는 아래 첨자 인 인덱스 배열 이름 인 경우* 인 경우 결과는로 시작하는 배열의 길이 멤버입니다 ${parameter[offset]}. 음의 오프셋은 지정된 배열의 최대 인덱스보다 큰 오프셋을 가져옵니다. 길이가 0보다 작은 숫자로 평가되면 확장 오류입니다.

다음은 @kevinarpe에서 제공하는 예제로, 출력을 증거에 배치하는 대체 형식을 사용합니다.

set -u
function count() { echo $# ; };
(
    count x y z
)
: prints "3"

(
    arr=()
    count "${arr[@]}"
)
: prints "-bash: arr[@]: unbound variable"

(
    arr=()
    count "${arr[@]:0}"
)
: prints "0"

(
    arr=(x y z)
    count "${arr[@]:0}"
)
: prints "3"

이 동작은 Bash 버전에 따라 다릅니다. 또한 길이 연산자 ${#arr[@]}0에 관계없이 항상 빈 배열 에 대해 평가되며 set -u'바인딩되지 않은 변수 오류'가 발생하지 않습니다.


불행히도 :0Bash 4.2에서는 관용구가 실패하므로 안전한 접근 방식이 아닙니다. 내 대답을 참조하십시오 .
dimo414

1

다음은 센티넬을 사용하는 방법과 조건부 추가를 사용하는 방법으로 이와 같은 작업을 수행하는 몇 가지 방법입니다.

#!/bin/bash
set -o nounset -o errexit -o pipefail
countArgs () { echo "$#"; }

arrA=( sentinel )
arrB=( sentinel "{1..5}" "./*" "with spaces" )
arrC=( sentinel '$PWD' )
cmnd=( countArgs "${arrA[@]:1}" "${arrB[@]:1}" "${arrC[@]:1}" )
echo "${cmnd[@]}"
"${cmnd[@]}"

arrA=( )
arrB=( "{1..5}" "./*"  "with spaces" )
arrC=( '$PWD' )
cmnd=( countArgs )
# Checks expansion of indices.
[[ ! ${!arrA[@]} ]] || cmnd+=( "${arrA[@]}" )
[[ ! ${!arrB[@]} ]] || cmnd+=( "${arrB[@]}" )
[[ ! ${!arrC[@]} ]] || cmnd+=( "${arrC[@]}" )
echo "${cmnd[@]}"
"${cmnd[@]}"

0

흥미로운 불일치; 이렇게하면 "설정되지 않은 것으로 간주되는"항목을 정의 할 수 있지만 출력에는declare -p

arr=()
set -o nounset
echo ${arr[@]}
 =>  -bash: arr[@]: unbound variable
declare -p arr
 =>  declare -a arr='()'

업데이트 : 다른 사람들이 언급 했듯이이 답변이 게시 된 후 릴리스 된 4.4에서 수정되었습니다.


그것은 잘못된 배열 구문입니다. 필요합니다 echo ${arr[@]}(그러나 Bash 4.4 이전에는 여전히 오류가 표시됩니다).
dimo414

@ dimo414에게 감사합니다. 다음 번에는 반대 투표 대신 편집을 제안하십시오. BTW echo $arr[@]직접 시도했다면 오류 메시지가 다른 것을 보았을 것입니다.
MarcH

-2

가장 간단하고 호환되는 방법은 다음과 같습니다.

$ set -u
$ arr=()
$ echo "foo: '${arr[@]-}'"

1
OP 자체는 이것이 작동하지 않는다는 것을 보여주었습니다. 아무것도 아닌 빈 문자열로 확장됩니다.
ikegami

맞습니다. 문자열 보간은 괜찮지 만 루핑은 아닙니다.
Craig Ringer
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.