
나는 을 사용하여 파일을 검색 find
하고 해당 파일을 Bash 배열에 넣어 다른 작업을 수행할 수 있도록 하려고 합니다 . 하지만 출력이 파이프로 연결될 때 왜 출력을 읽지 않는지 알 수 없습니다 .ls
grep
readarray
find
현재 디렉토리에 두 개의 파일 이 있고 file1.txt
. file2.txt
따라서 find
출력은 다음과 같습니다.
$ find . -name "file*"
./file1.txt
./file2.txt
그래서 나는 그것을 두 요소가 문자열 "./file1.txt"
과 "./file2.txt"
(물론 따옴표 없이)인 배열로 파이프하고 싶습니다.
나는 몇 가지 다른 것 중에서 이것을 시도했습니다.
$ declare -a FILES
$ find . -name "file*" | readarray FILES
$ echo "${FILES[@]}"; echo "${#FILES[@]}"
0
출력 에서 볼 수 있듯이 echo
내 배열은 비어 있습니다.
그렇다면 내가 여기서 정확히 무엇을 잘못하고 있는 걸까요? 왜 의 출력을 표준 입력으로 readarray
읽고 find
해당 문자열을 배열에 넣지 않습니까?
답변1
파이프라인을 사용할 때 bash는 서브셸¹에서 명령을 실행합니다. 따라서 배열은 채워지지만 하위 쉘에 있으므로 상위 쉘에서는 이에 액세스할 수 없습니다. 또한 -t
파일 이름의 일부가 아니기 때문에 해당 줄 구분 기호를 배열 구성원에 저장하지 않는 옵션을 원할 수도 있습니다 .
프로세스 대체 사용:
readarray -t FILES < <(find .)
경로에 개행 문자가 있는 파일에서는 작동하지 않습니다. 그렇지 않다는 것을 보장할 수 없다면 개행으로 구분된 레코드 대신 NUL로 구분된 레코드를 사용하는 것이 좋습니다.
readarray -td '' < <(find . -print0)
(이 -d
옵션은 bash 4.4에 추가되었습니다)
¹ 옵션을 사용할 때 마지막 파이프 구성 요소는 제외 lastpipe
하지만 이는 의 비대화형 호출에만 해당됩니다 bash
.
답변2
올바른 해결책은 다음과 같습니다.
unset a; declare -a a
while IFS= read -r -u3 -d $'\0' file; do
a+=( "$file" ) # or however you want to process each file
done 3< <(find /tmp -type f -print0)
그거랑 비슷한데그렉의 BashFAQ 020자세하게 설명하고이 답변은 다음과 같습니다.
공백이나 새 줄이 있는 이상한 이름의 파일(이름에 NUL이 포함되지 않음)에는 문제가 없습니다. 그리고 결과는 배열로 설정되므로 추가 처리에 유용합니다.
답변3
readarray
stdin에서도 읽을 수 있습니다
readarray FILES <<< "$(find . -name "file*")"; echo "${#FILES[@]}"
답변4
shopt -s lastpipe
원래 질문에 표시된 솔루션을 사용 하면 마지막 부분 이후의 부분이 |
대부분의 스크립트와 동일한 프로세스에서 실행됩니다.
shopt -s lastpipe
# This is just the code form the original question.
# Of course it could be done with -print0, etc.
declare -a FILES
find . -name "name*" | readarray -t FILES
echo find and readarray exit status: "${PIPESTATUS[@]}"
echo "${FILES[@]}"
echo "${#FILES[@]}"
lastpipe
그러나 이는 프로젝트 전체 표준으로 만들 수 있는 경우에만 실용적입니다 (아마도 set -u
등에 대한 공통 설정이 이미 있을 수 있습니다). 또한 lastpipe
비대화형 쉘에서만 작동한다는 것도 알아두세요.
또 다른 일반적인 파이프 교체 < <(...)
(프로세스 대체)는 자식 프로세스가 성공했는지 확인하기가 상당히 어렵다는 문제가 있습니다.