$array가 ksh 및 bash에서 전체 배열을 확장하지 않는 이유는 무엇입니까?

$array가 ksh 및 bash에서 전체 배열을 확장하지 않는 이유는 무엇입니까?

에서 영감을 받다이 최근 질문:

bash$ a=(1 2 3)
bash$ echo $a
1

하지만

zsh% a=(1 2 3)
zsh% echo $a
1 2 3
zsh% printf '%s\n' $a
1
2
3

(마지막 부분에서는 와 같지 "${a[@]}"않은 별도의 매개변수로 배열을 확장하는 방법을 보여줍니다 "${a[*]}".)

bash(ksh와 일치)의 동작은 매우 반직관적입니다. "이 배열 변수 확장"에 대한 "첫 번째 요소만"이 어떻게 합리적인 응답입니까?

zsh가 갈라지는 다른 영역에서는 ksh와 bash가 원래 Bourne 쉘에 더 가깝기 때문입니다. 그러나 Bourne에는 사용자 정의 배열 변수가 없습니다.

Bash는 왜 이런 이상한 결정을 내렸을까요? ksh가 복사되면 ksh는 왜 이런 이상한 결정을 내립니까?

긴 댓글 목록 이후 계속됩니다.

이것은 zsh를 비판하거나 칭찬하는 문제가 되어서는 안됩니다. zsh는 이해하기 쉬운 예입니다.가능한다양한 접근 방식이 취해졌습니다.

디자인 결정을 설명하는 한 가지 가능성은 이전 버전과의 호환성입니다. 이전 버전과의 호환성은 의견이 아닙니다. 이는 객관적인 사실입니다.

스크립트를 표시할 수 있는 경우(전체 스크립트, 문맥에서 발췌한 것이 아님) Bourne 셸에서 알려진 방식으로 실행되고(즉, 구문 오류로 폭탄을 터뜨리지 않음) 가상의 "전체 $array 확장이 포함된 Korn 셸"에서 다르게 동작합니다. 이겼다! 이는 이전 버전과의 호환성 문제입니다.

아직 그러한 스크립트가 제공되지 않았습니다. 이것은 다음이 아닙니다:

a=(1 2 3)
printf '%s\n' $a

이는 Bourne 쉘의 구문 오류이기 때문입니다. 한때 구문 오류였던 것에 새로운 의미를 부여하는 것은 이전 버전과의 호환성을 유지하면서 새로운 기능을 만드는 한 가지 방법입니다.

내가 알 수 있는 한, a=(...)원래 구문 오류였다는 사실은 배열을 사용하는 스크립트와 사용하지 않는 스크립트를 명확하게 구분합니다. 첫 번째 범주에서는 이러한 스크립트가 이전 쉘에서 실행되지 않기 때문에 이전 버전과의 호환성을 어떤 이유로든 언급할 수 없습니다. 두 번째 범주에서는 배열 변수 확장 규칙에 관계없이 확장할 배열이 없기 때문에 이전 버전과의 호환성이 유지됩니다!

이것은 아니다입증하다배열을 스크립트에 몰래 넣을 수 있는 방법이 없다고 결정하는 데 부분적으로 직관에 의존했기 때문에 =(호환되지 않는 동작을 나타내는 스크립트가 없었습니다. 존재하지 않는다고 주장하는 것의 장점은 그것을 끝내려면 반례만 제시하면 된다는 것입니다.

댓글에 나온 내용이 a=$@설명에 도움이 되는 것 같습니다. 배열 변수를 생성하는 경우 a이 스크립트는 다음을 수행합니다.

a=$@
printf '%s\n' $a

차이점이 표시되어야 합니다. 하지만 제가 테스트한 결과에는 이런 일이 발생하지 않았습니다. 모든 쉘(heirloom sh, modern ksh, bash 및 zsh)은 첫 번째 줄을 동일한 방식으로 처리하는 것 같습니다. a배열이 아니라 공백이 포함된 문자열일 뿐입니다. (zsh는 의 값을 토큰화하지 않기 때문에 두 번째 줄에서 분기되지만 $a이는 배열 변수와 관련이 없습니다)

답변1

답변을 드릴 수는 없지만 몇 가지 가능한 설명을 제안해 드립니다.

실제로 ksh 및 해당 복제본(pdksh 및 추가 파생 항목 및 bash)을 제외하고 배열( csh, tcsh, rc, es, akanga, fish, zsh) 이 있는 다른 모든 셸은 배열의 모든 구성원 yash으로 확장되었습니다 .$array

그러나 이 목록에 있는 두 개의 Bourne 유사 쉘인 및 yash( 에뮬레이션하는 zsh동안 sh) 확장은 여전히 ​​분할+glob의 영향을 받습니다( 에뮬레이트 zsh하지 않는 경우에도 sh여전히 null 제거임). 따라서 여전히 어색한 구문을 사용해야 합니다 "${array[@]}"(또는 "${(@)array}"입력하기가 더 쉬움 "$array[@]") zsh목록을 유지하려면( 유사한 문제가 csh있음 tcsh) 분할+glob 및 null 삭제는 Bourne 레거시입니다(그 자체는 매크로 확장과 유사한 Thompson 쉘 레거시로 인해 발생함 $1).

rcfishBourne 수하물이 없고 더 깔끔한 접근 방식을 취하는 최신 쉘의 두 가지 예입니다 . 그들은 쉘이 명령줄 해석기이고 그들이 다루는 주요 작업은 목록(명령의 매개변수 목록)이므로 목록/배열이 주요 데이터 유형(유형은 하나뿐이며 목록임 rc)임을 인정하고 제거합니다. Bourne 쉘의 분할+ glob-upon-expansion 버그/오류 기능(기본 유형이 배열이므로 더 이상 필요하지 않음).

$array그럼에도 불구하고 David Korn이 모든 요소로 확장하지 않고 인덱스 0의 요소로 확장하기로 선택한 이유는 설명되지 않습니다 .

csh이제 /를 제외한 모든 쉘은 Bourne 쉘과 Unix V7이 출시된 지 불과 몇 년 후인 tcsh1980년대 초반에 개발된 것보다 훨씬 최신입니다 . kshUnix V7에도 환경이 도입되었습니다. 이는 당시로서는 매우 참신한 일이었습니다. 환경은 깔끔하고 유용하지만, 어떤 형태의 인코딩을 사용하지 않으면 환경 변수에 배열이 포함될 수 없습니다.

이것은 단지 추측일 뿐이지만 David Korn이 이 접근 방식을 선택한 이유 중 하나는 환경과의 인터페이스를 수정하지 않기 위해서라고 생각합니다.

rc와 마찬가지로 ksh88에서는 모든 변수가 배열입니다(아주 희박하긴 하지만 키가 양의 정수로 제한되는 연관 배열과 비슷합니다. 이는 다른 쉘이나 프로그래밍 언어에 비해 또 다른 이상한 점입니다. 완전히 구현되지는 않았습니다). 예를 들어 키 목록을 검색하는 것이 불가능하기 때문에 고려했습니다. 새로운 디자인에서는 var=value약어가 가 됩니다 var[0]=value. 여전히 모든 변수를 내보낼 수 있지만 export var배열 인덱스 0의 요소를 환경으로 내보낼 수 있습니다.

rc모든 변수를 환경에 배치하여 배열 내보내기를 지원 fish하지만 여러 요소가 있는 배열의 경우(적어도 plan9의 rc에서 Unix 포트로의 경우) 인코딩 형식에 의존해야 합니다. 그들만이 이해합니다.

csh, tcsh, zsh는 배열 내보내기를 지원하지 않습니다(비록 현재로서는 이것이 큰 제한 사항처럼 들리지 않을 수도 있음). 에서 배열을 내보낼 수 있지만 yash배열 요소는 와 연결된 :( (a "" "" b)따라서 (a : b)내보낸 값과 동일함) 환경 변수로 내보내지며 가져올 때 배열로 다시 변환되지 않습니다.

또 다른 가능한 이유는 $@Bourne's/와의 일관성 일 수 있습니다 $*(그러나 배열 인덱스는 왜 1이 아닌 0에서 시작합니까(당시 다른 쉘/언어에 비해 또 다른 이상한 점)?). ksh이는 무료 소프트웨어가 아니라 상업용 기업이며 요구 사항 중 하나는 Bourne 호환성입니다. ksh완료된 필드 분할을 제거합니다.모든목록의 맥락에서 인용되지 않은 단어(Bourne 쉘에서는 분명히 유용하지 않기 때문에)는 확장을 위해 보존되어야 합니다(그러나 스크립트는 var="file1 file2"; cmd $var배열이 없는 Bourne 쉘과 같은 것을 사용하기 때문에 "$@"). 배열이 있는 셸에 보관하는 것은 별 의미가 없지만 Ksh가 여전히 소비자 기반의 스크립트를 해석할 수 있다면 Korn에는 옵션이 거의 없습니다. $scalar분할+글로브의 영향을 받는 경우 $array일관성을 위한 것이어야 하므로 "${array[@]}"일반화로 "$@"이해하는 것이 좋습니다 . zsh비슷한 제약이 없으므로 확장 시 배열을 추가하는 동안 분할+글로브를 자유롭게 제거할 수 있습니다(그러나 Bourne의 하위 호환성이 손상되는 대가를 치르게 됩니다).

@Arrow가 제공하는 또 다른 설명은 기존 연산자를 오버로드하여 다양한 유형의 변수에 대해 다르게 동작하도록 만들고 싶지 않다는 것입니다(예: Bourne 쉘에 또는 , 가 ${#var}없더라도 ). 이는 사용자 혼란을 초래할 수 있습니다. ( 일부 연산자가 배열 및 스칼라를 사용하는 방법이 항상 명확하지는 않기 때문입니다).${#array}${var-value}${var#pattern}zsh

관련 자료:


a=$@편집 상황 에 관해서는 실제로 ksh가 Bourne 쉘과의 호환성을 깨뜨리는 경우입니다.

Bourne 쉘에서는 위치 인수와 공백 문자의 연결을 포함합니다 $@. 삽입된 공백과 동일하게 확장되지만 인용되지는 않으므로 인용된 경우 $*에만 특별합니다 (최신 버전에서는 빈 목록의 특수한 경우가 Solaris에서와 같이 처리되었습니다). 공백을 제거하면 목록 컨텍스트에서 하나의 인수로만 확장됩니다(0은 목록 내의 빈 목록을 의미함).$@"$*"$IFS"$@"안정적인위에서 언급한 버전). 따옴표가 없으면 다른 변수처럼 동작합니다(반드시 원래 위치 인수로 분할할 필요는 없지만 의 문자로 분할 $*) . 예를 들어 Bourne 쉘에서는 다음과 같습니다.$@$IFS

'set' 'a:b'   'c'
IFS=:
printf '<%s>\n' $@
printf '[%s]\n' "$@"

출력됩니다:

<a>
<b c>
[a:b c]

$@Ksh88은 와 의 첫 번째 문자가 $*연결 되도록 이를 변경했습니다 $IFS. 비어 "$@"있지 않으면 목록 컨텍스트에서 위치 매개변수를 구분합니다 $IFS.

$IFS비어 있으면 구분 기호 없이 인용 $*하지 않는 한 공백으로 연결됩니다 $*.

예:

$ set a b
$ IFS=:
$ a=$@ b=$* c="$@" d="$*"
$ printf '<%s>\n' "$a" "$b" "$c" "$d" $@ $* "$@" "$*"
<a:b>
<a:b>
<a:b>
<a:b>
<a>
<b>
<a>
<b>
<a>
<b>
<a:b>
$ IFS=
$ a=$@ b=$* c="$@" d="$*"
$ printf '<%s>\n' "$a" "$b" "$c" "$d" $@ $* "$@" "$*"
<a b>
<a b>
<a b>
<ab>
<a b>
<a b>
<a b>
<ab>

ksh93 및 ksh88을 포함하여 Bourne/Korn 유사 셸의 다양한 변형을 볼 수 있습니다. 또한 상황에는 다음과 같은 몇 가지 변화가 있습니다.

set --
cmd ''"$@"
cmd $empty"$@"

또는 $IFS멀티바이트 문자나 유효한 문자를 형성하지 않는 바이트가 포함된 경우입니다.


yash그러나 에서는 "$array"동작이 "${array[@]}"while zsh"$array"동작과 유사합니다 "${array[*]}". 덜 추악하고 창피하지만 어쩌면 더 놀랄 수도 있습니다.

답변2

이 답변에 대한 귀하의 의견에서 귀하는 zsh의 패러다임이 "더 우수"하므로 모든 쉘이 작동하는 방식이라고 말하고 받아들이는 답변을 기대했습니다. "더 좋다"는 것은 단지 의견일 뿐이다. 의견에는 토론이 필요하지 않습니다.

  • $a아마도 당신이 기대하는 것은 이것이 배열에서도 작동하는 이유에 대한 세부 사항일 것입니다.

    이것이 zsh가 하려는 일이며, 이 방향으로 많은 작업을 수행하더라도
    어떤 경우에는 여전히 실패합니다. (아직) 배열을 복사하지 않습니다.

    $ a=(1 2 3)
    $ b=$a
    $ printf '<%s>' $a ' ' $b; echo
    <1><2><3>< ><1 2 3>
    

  • 하지만 이 질문은 사실 그것과는 거리가 멀다. 이것은 언어의 문제입니다.

우리 언어에서는 모든 아이디어에 이름을 사용합니다. 모든 새로운 아이디어는 이전 아이디어와 완전히 다릅니다.~ 해야 하다자신의 이름이 있습니다. 우리 언어에서 우리는 새로운 아이디어를 표현하기 위해 자연스럽게 새로운 단어를 받아들입니다. 새로운 아이디어의 새로운 이름인 "인터넷"이라는 이름을 생각해 보십시오. 새로운 개념을 표현하기 위해 오래된 이름을 사용하면 항상 혼란과 오해가 발생합니다. 이것이 우리 인간이 만들어진 방식이며, 생각해보면 합리적인 것처럼 들립니다.

  • 셸(및 모든 프로그래밍 언어)에서 우리는 각 특정 아이디어에 대해 특정 구문을 사용합니다.

처음부터 쉘의 변수에는 a부호 확장(쉘 프로시저)의 결과인 값을 갖는 이름(가정)이 있습니다 $a. 변수에는 문자열이나 숫자가 포함될 수 있습니다(자동 변환).

  • 변수의 새로운 내용(값 배열) 소개~ 해야 하다새로운 구문을 사용하세요.

@a이것은 Perl이 using을 사용하여 표현하는 것과 정확히 같습니다.목록, $a유지 하면서 an scalar.
그건:위의 두 가지를 각각 SCALAR 및 LIST 컨텍스트라고 합니다.(펄에서).

이것이 우리가 a=(1 2)배열을 할당하는 이유입니다(다른 방법도 있지만 이것이 가장 일반적입니다). 이전 셸의 구문이 잘못되었습니다.

sh(이 경우 대시):

$ a=(12)
sh: 2: Syntax error: "(" unexpected

그리고 변수의 확장은 새로운 구문 $a또는 동등한 ${a}새로운 구문(sh에서는 유효하지 않음) `${a[@]}에서 비롯됩니다.

$ a=(aa bb cc)
$ printf '%s\n' "${a[@]}"
aa
bb
cc

더 간단한 쉘(이 경우에는 ash)에서:

$ a=Strin-Of-Text
$ printf '%s\n' "$a"
ash: syntax error: bad substitution

배열을 작성하는 데 이렇게 복잡한 방법을 선택했다는 것은 불행한 일입니다.

만약 내가 새로운 구문을 생각해낸다면 아마도 Perl의 지시를 따라 "@a"(또는 어쩌면 #a또는 %a)와 같은 값 목록을 사용할 것입니다. 이는 합의에 도달하기 위한 논의의 문제가 되어야 합니다.

  • 그러나 그것은 (슬프게도) 수행된 작업이 아니며 선택된 작업은 다음과 같습니다 ${a[@]}.

간단히 말해서, 이전 버전과의 호환성을 위해 단순 변수의 확장은 다음 $a과 같은 결과만 가져올 것으로 예상됩니다.하나값 목록이 아니라 분리된 값입니다.

POSIX에서는 배열이 정의되어 있지 않으므로 POSIX에서 정의를 참조하는 것이 유효하지 않을 수 있습니다.매개변수 확장설명하다:

매개변수 값(있는 경우)을 교체해야 합니다.

그리고 실제로 대부분의 쉘은 스칼라만 인쇄합니다.$a

bash(4.4)       : <1><====><1><2><3>
lksh            : <1><====><1><2><3>
mksh            : <1><====><1><2><3>
ksh93           : <1><====><1><2><3>
astsh           : <1><====><1><2><3>
zsh/ksh         : <1><====><1><2><3>
zsh             : <1><2><3><====><1><2><3>

따라서 zsh [a]는 쉘 스크립트 작성에 있어서 이상한 선택입니다 .

이 스크립트를 사용하여 다음을 테스트하세요.

a=(1 2 3)
printf '<%s>' $a '====';
printf '<%s>' "${a[@]}" ;
echo

[a] 야쉬. csh 호환 쉘에 대해서는 테스트되지 않았습니다. csh를 사용하는 스크립트에는 알려진 문제가 있습니다.

관련 정보