배열을 생성하고 추가합니다. mapfile은 arr+=(input)과 동일합니까, 아니면 뭔가 빠졌습니까?

배열을 생성하고 추가합니다. mapfile은 arr+=(input)과 동일합니까, 아니면 뭔가 빠졌습니까?

mapfile이점이 그것보다 더 큰 상황이 있습니까 arr+=(input)?

간단한 예

매핑 파일 배열 이름, arr:

mkdir {1,2,3}

mapfile -t arr < <(ls)

declare -p arr

산출:

declare -a arr=([0])="1" [1]="2" [2]="3")

편집하다:

다음 헤더를 변경했습니다. 본문에는 y배열 이름이 있지만 헤더에는 arr이름이 있어 혼동을 일으킬 수 있습니다.

y+=(입력)

IFS=$'\n'

y+=($(ls))

declare -p y

산출:

declare -a y=([0])="1" [1]="2" [2]="3")

mapfile한 가지 장점 은 단어 분할에 대해 걱정할 필요가 없다는 점이라고 생각합니다 .

또는 단어 분리를 방지하도록 설정할 수도 있지만 IFS=$'\n'이 예에서는 걱정할 필요가 없습니다.

두 번째 예는 작성하기 더 쉬운 것 같습니다. 뭔가 빠졌나요?

답변1

나중에도 그들은 전혀 같은 것이 아닙니다 IFS=$'\n'.

특히 bash에서(구문은 zsh에서 차용했지만):

arr=( $(cmd) )

( arr+=( $(cmd) )다음 용도로 사용됩니다.추가의따라서 배열의 요소는 keys=( -1 "${!arr[@]}" ); readarray -tO "$(( ${keys[@]: -1} + 1))" arr < <(cmd)²와 비교됩니다.

하다:

  1. cmd파이프의 쓰기 끝에서 표준 출력이 열린 하위 쉘에서 실행됩니다 .
  2. 그 사이에 상위 셸 프로세스는 파이프의 다른 쪽 끝에서 데이터를 읽고 다음을 수행합니다.
    • NUL 문자 및 후행 줄 바꿈 제거
    • 특수 변수의 내용을 기준으로 $IFS결과 문자열을 분할합니다 . $IFS공백 문자 (예: 개행 문자) 의 경우 동작이 더 복잡합니다.
      • 선행 및 후행 항목이 제거됩니다. (개행의 경우 위와 같이 명령 대체에 의해 제거되었습니다.)
      • 하나 이상의 시퀀스가 ​​고려됩니다.하나분할기. 예를 들어 의 출력은 및 의 printf '\n\n\na\n\n\nb\n\n\n'두 가지 요소로만 나뉩니다 .ab
    • noglob그러면 이러한 각 단어는 파일 이름 생성(일명 와일드카드)의 적용을 받게 되며, 그 동작은 , nullglob, failglob, extglob, globasciiranges, glabstar, 등 다양한 옵션의 영향을 받습니다 nocaseglob. 이는 *, ?, [및 일부 bash 버전을 포함하는 단어에 대해 작동하며 \활성화된 경우 그 이상 버전도 사용할 수 있습니다.extglob
  3. 그런 다음 결과 단어가 $arr배열의 요소로 할당됩니다.

예:

bash-5.1$ touch x '\x' '?x' aX $'foo\n\n\n\n*'
bash-5.1$ IFS=$'\n'
bash-5.1$ ls | cat
aX
foo



*
?x
\x
x
bash-5.1$ arr=( $(ls) )
bash-5.1$ typeset -p arr
declare -a arr=([0]="aX" [1]="foo" [2]="aX" [3]=$'foo\n\n\n\n*' [4]="?x" [5]="\\x" [6]="x" [7]="?x" [8]="\\x" [9]="\\x" [10]="x")

보시다시피 파일은 현재 작업 디렉토리의 파일 목록으로 $'foo\n\n\n\n*'분할 foo되어 확장 *됩니다 . 이는 우리가 둘 다 얻는 이유 와 유사하게 행 의 출력이 둘 다 일치하기 때문에 3번 얻는 이유를 설명합니다.*foo$'foo\n\n\n\n*'?x\x"\\x"\xls*?x

bash 5.0을 사용하면 다음을 얻을 수 있습니다.

bash-5.0$ arr=( $(ls) )
bash-5.0$ typeset -p arr
declare -a arr=([0]="aX" [1]="foo" [2]="aX" [3]=$'foo\n\n\n\n*' [4]="?x" [5]="\\x" [6]="x" [7]="?x" [8]="\\x" [9]="x" [10]="x")

이 버전에서는 백슬래시가 두 번만 세 번이고, 뒤에 와일드카드가 오지 않더라도 백슬래시는 와일드카드이므로 \x와일드카드와 일치합니다.x\xx

그 후에 shopt nocaseglob우리는 다음을 얻습니다:

bash-5.1$ shopt -s nocaseglob
bash-5.1$ arr=( $(ls) )
bash-5.1$ typeset -p arr
declare -a arr=([0]="aX" [1]="foo" [2]="aX" [3]=$'foo\n\n\n\n*' [4]="?x" [5]="\\x" [6]="x" [7]="aX" [8]="?x" [9]="\\x" [10]="\\x" [11]="x")

aX역시 일치 하므로 3번 표시됩니다 ?x.

뒤쪽에 shopt -s failglob:

bash-5.0$ shopt -s failglob
bash-5.0$ arr=( $(printf '\\z\n') )
bash: no match: \z
bash-5.0$ arr=( $(printf 'WTF\n?') )
bash: no match: WTF?

그리고arr=( $(echo '/*/*/*/*/../../../../*/*/*/*/../../../../*/*/*/*') )

메모리 부족으로 인해 몇 분 동안 시스템을 사용할 수 없습니다.

따라서 요약하면 IFS=$'\n'; arr=( $(cmd) )의 출력 라인은 cmd배열에 저장되지 않지만 출력의 비어 있지 않은 라인 확장으로 인한 파일 이름은 cmdglob 패턴으로 처리됩니다.


mapfile오해의 소지가 적은 별칭을 사용하십시오 readarray.

readarray -t arr < <(cmd)
  1. 위에서 언급한 것처럼 cmd서브셸에서 실행될 때 표준 출력은 파이프의 쓰기 측에서 열립니다.
  2. 파이프라인의 읽기 끝에서 열려 있는 상위 셸의 파일 설명자가 있는 곳 과 같은 것으로 <(...)확장됩니다 ./dev/fd/63/proc/self/fd/6363
  3. <리디렉션 약어를 사용하면 0</dev/fd/63이 fd 0에서 읽기 위해 열립니다. 이는 stdin이 readarray해당 파이프의 읽기 끝이기도 함을 의미합니다.
  4. readarray이 파이프에서 각 줄을 읽고( cmd쓰는 동안) 줄 구분 기호( -t)를 버리고 에 저장합니다 $arr.

따라서 결국 출력에 NUL이 없다고 $arr가정하면 비어 있거나 전역 문자가 포함되어 있는지 여부에 관계없이 cmd출력의 모든 줄 내용이 포함됩니다 .cmd

위의 예를 들어보세요:

bash-5.1$ readarray -t arr < <(ls)
bash-5.1$ typeset -p arr
declare -a arr=([0]="aX" [1]="foo" [2]="" [3]="" [4]="" [5]="*" [6]="?x" [7]="\\x" [8]="x")

이는 이전 출력에서 ​​본 것과 일치 ls | cat하지만 현재 작업 디렉터리에 있는 파일 목록을 가져오는 것이 목적이라면 여전히 잘못된 것입니다. 최신 버전(9.0 이상) 과 같이 GNU에서 구현한 일부 확장을 사용하지 않으면 의 출력을 ls사후 처리할 수 없습니다.ls--quoting-style=shell-always--zero

bash-5.2$ readarray -td '' arr < <(ls --zero)
bash-5.2$ typeset -p arr
declare -a arr=([0]="aX" [1]=$'foo\n\n\n\n*' [2]="?x" [3]="\\x" [4]="x")

이번에는 readarrayNUL 제한 레코드의 내용을 . NUL은 변수에 저장할 수 없으므로 사용할 수 없습니다.d$arrIFS=$'\0'bashbash

또는:

bash-5.1$ eval "arr=( $(ls --quoting-style=shell-always) )"
bash-5.1$ typeset -p arr
declare -a arr=([0]="aX" [1]=$'foo\n\n\n\n*' [2]="?x" [3]="\\x" [4]="x")

어쨌든 현재 작업 디렉터리의 파일 목록을 배열에 넣는 올바른 방법은 다음과 같습니다.

shopt -s nullglob
arr=( * )

목록을 크기나 수정 시간별로 정렬하려는 경우 에만 ls --zerobash glob(zsh와 반대)으로는 수행할 수 없는 작업입니다.

좋다:

다루기 힘든 최신 GNU bash + GNU coreutils
new_to_old=( *.txt(Nom) ) readarray -td '' new_to_old < <(ls -td --zero -- *.txt)
four_largest=( *.txt(NOL[1,4]) ) readarray -td '' four_largest < <(ls -tdrS --zero -- *.txt | head -zn4)

a=($(cmd))과 사이의 또 다른 차이점 readarray < <(cmd)은 종료 상태입니다. 전자는 이고 cmd후자는 입니다 readarray. 최신 버전에서는 bash.cmdwait "$!"; cmd_status=$?


¹ arr=( ... )구문은 zsh에서 유래했습니다(배열은 1996년 2.0까지 bash에 나타나지 않았습니다). 그러나 zsh에서 명령 대체는 후행 줄 바꿈도 제거하고 $IFS-stripping의 영향을 받지만 NUL을 무시하지 않습니다(NUL은 심지어 기본값은 $IFS)이며 다른 Bourne과 유사한 쉘과 같은 와일드카드의 영향을 받지 않으므로 보다 안전한 쉘이 됩니다.

²일명 추가 모드는 readarray없지만 최신 버전에서는 아래와 mapfile같이 첫 번째 요소의 인덱스와 요소 저장을 시작할 위치를 알 수 있습니다 . -Obash에서 마지막 요소의 인덱스를 찾는 것은 매우 어렵습니다(배열이 ksh만큼 희박합니다!). 여기서는 cmdto 의 출력 행을 추가하는 대신 $arrwith 임시 배열로 행을 읽어서 with readarray -r tmp < <(cmd)에 요소를 추가할 수도 있습니다 . 또한 변수가 스칼라 또는 결합형으로 선언된 경우 이러한 메서드 간의 동작이 다르다는 점에 유의하세요.$arrarr+=( "${tmp[@]}" )arr

관련 정보