`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.