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)
²와 비교됩니다.
하다:
cmd
파이프의 쓰기 끝에서 표준 출력이 열린 하위 쉘에서 실행됩니다 .- 그 사이에 상위 셸 프로세스는 파이프의 다른 쪽 끝에서 데이터를 읽고 다음을 수행합니다.
- NUL 문자 및 후행 줄 바꿈 제거
- 특수 변수의 내용을 기준으로
$IFS
결과 문자열을 분할합니다 .$IFS
공백 문자 (예: 개행 문자) 의 경우 동작이 더 복잡합니다.- 선행 및 후행 항목이 제거됩니다. (개행의 경우 위와 같이 명령 대체에 의해 제거되었습니다.)
- 하나 이상의 시퀀스가 고려됩니다.하나분할기. 예를 들어 의 출력은 및 의
printf '\n\n\na\n\n\nb\n\n\n'
두 가지 요소로만 나뉩니다 .a
b
noglob
그러면 이러한 각 단어는 파일 이름 생성(일명 와일드카드)의 적용을 받게 되며, 그 동작은 ,nullglob
,failglob
,extglob
,globasciiranges
,glabstar
, 등 다양한 옵션의 영향을 받습니다nocaseglob
. 이는*
,?
,[
및 일부 bash 버전을 포함하는 단어에 대해 작동하며\
활성화된 경우 그 이상 버전도 사용할 수 있습니다.extglob
- 그런 다음 결과 단어가
$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"
\x
ls
*
?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
\x
x
그 후에 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
배열에 저장되지 않지만 출력의 비어 있지 않은 라인 확장으로 인한 파일 이름은 cmd
glob 패턴으로 처리됩니다.
mapfile
오해의 소지가 적은 별칭을 사용하십시오 readarray
.
readarray -t arr < <(cmd)
- 위에서 언급한 것처럼
cmd
서브셸에서 실행될 때 표준 출력은 파이프의 쓰기 측에서 열립니다. - 파이프라인의 읽기 끝에서 열려 있는 상위 셸의 파일 설명자가 있는 곳 과 같은 것으로
<(...)
확장됩니다 ./dev/fd/63
/proc/self/fd/63
63
<
리디렉션 약어를 사용하면0<
/dev/fd/63이 fd 0에서 읽기 위해 열립니다. 이는 stdin이readarray
해당 파이프의 읽기 끝이기도 함을 의미합니다.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")
이번에는 readarray
NUL 제한 레코드의 내용을 . NUL은 변수에 저장할 수 없으므로 사용할 수 없습니다.d
$arr
IFS=$'\0'
bash
bash
또는:
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 --zero
bash 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
.cmd
wait "$!"; cmd_status=$?
¹ arr=( ... )
구문은 zsh에서 유래했습니다(배열은 1996년 2.0까지 bash에 나타나지 않았습니다). 그러나 zsh에서 명령 대체는 후행 줄 바꿈도 제거하고 $IFS
-stripping의 영향을 받지만 NUL을 무시하지 않습니다(NUL은 심지어 기본값은 $IFS
)이며 다른 Bourne과 유사한 쉘과 같은 와일드카드의 영향을 받지 않으므로 보다 안전한 쉘이 됩니다.
²일명 추가 모드는 readarray
없지만 최신 버전에서는 아래와 mapfile
같이 첫 번째 요소의 인덱스와 요소 저장을 시작할 위치를 알 수 있습니다 . -O
bash에서 마지막 요소의 인덱스를 찾는 것은 매우 어렵습니다(배열이 ksh만큼 희박합니다!). 여기서는 cmd
to 의 출력 행을 추가하는 대신 $arr
with 임시 배열로 행을 읽어서 with readarray -r tmp < <(cmd)
에 요소를 추가할 수도 있습니다 . 또한 변수가 스칼라 또는 결합형으로 선언된 경우 이러한 메서드 간의 동작이 다르다는 점에 유의하세요.$arr
arr+=( "${tmp[@]}" )
arr