결과를 find
배열 로 저장하려고 합니다. 내 코드는 다음과 같습니다.
#!/bin/bash
echo "input : "
read input
echo "searching file with this pattern '${input}' under present directory"
array=`find . -name ${input}`
len=${#array[*]}
echo "found : ${len}"
i=0
while [ $i -lt $len ]
do
echo ${array[$i]}
let i++
done
현재 디렉토리 아래에 2 개의 .txt 파일이 있습니다. 그래서 나는 결과로 ‘2’를 기대 ${len}
합니다. 그러나 1을 인쇄합니다. 그 이유는 모든 결과 find
를 하나의 요소로 취하기 때문입니다. 이 문제를 어떻게 해결할 수 있습니까?
추신
: 비슷한 문제에 대한 StackOverFlow에서 몇 가지 해결책을 찾았습니다. 하지만 약간 다르기 때문에 제 경우에는 신청할 수 없습니다. 루프 전에 변수에 결과를 저장해야합니다. 다시 한번 감사드립니다.
답변
Linux 사용자를위한 2020 업데이트 :
Linux를 사용하는 경우와 같이 최신 버전의 bash (4.4-alpha 이상)가있는 경우 Benjamin W.의 답변 을 사용해야합니다 .
마지막으로 확인한 Mac OS에서 bash 3.2를 사용하거나 이전 bash를 사용하는 경우 다음 섹션으로 계속 진행하십시오.
bash 4.3 이하에 대한 답변
다음은의 출력을 배열 find
로 가져 오는 한 가지 솔루션입니다 bash
.
array=()
while IFS= read -r -d $'\0'; do
array+=("$REPLY")
done < <(find . -name "${input}" -print0)
일반적으로 파일 이름에는 공백, 줄 바꿈 및 기타 스크립트에 적대적인 문자가 포함될 수 있기 때문에 이것은 까다 롭습니다. find
파일 이름을 서로 안전하게 분리하고 사용하는 유일한 방법 -print0
은 널 문자로 분리 된 파일 이름을 인쇄하는 것을 사용 하는 것입니다. bash의 readarray
/ mapfile
함수가 null로 구분 된 문자열을 지원하지만 지원하지 않는 경우 이는 불편 하지 않습니다. Bash read
는 위의 루프로 연결됩니다.
[이 답변은 원래 2014 년에 작성되었습니다. 최신 버전의 bash가있는 경우 아래 업데이트를 참조하십시오.]
작동 원리
-
첫 번째 줄은 빈 배열을 만듭니다.
array=()
-
read
명령문이 실행될 때마다 표준 입력에서 널로 구분 된 파일 이름을 읽습니다. 이-r
옵션은read
백 슬래시 문자를 그대로 두도록 지시 합니다. 은-d $'\0'
지시read
입력이 널 (null)로 구분 될 것이다. 에 이름을 생략 했으므로read
쉘은 입력을 기본 이름 :REPLY
. -
이
array+=("$REPLY")
명령문은 배열에 새 파일 이름을 추가합니다array
. -
마지막 줄은 리디렉션과 명령 대체를 결합
find
하여while
루프 의 표준 입력에 대한 출력을 제공합니다 .
프로세스 대체를 사용하는 이유는 무엇입니까?
프로세스 대체를 사용하지 않았다면 루프는 다음과 같이 작성 될 수 있습니다.
array=()
find . -name "${input}" -print0 >tmpfile
while IFS= read -r -d $'\0'; do
array+=("$REPLY")
done <tmpfile
rm -f tmpfile
위의 출력은 find
임시 파일에 저장되며 해당 파일은 while 루프에 대한 표준 입력으로 사용됩니다. 프로세스 대체의 개념은 이러한 임시 파일을 불필요하게 만드는 것입니다. 따라서 while
루프가에서 stdin을 가져 오는 대신에서 stdin을 가져 오도록 tmpfile
할 수 있습니다 <(find . -name ${input} -print0)
.
프로세스 대체는 널리 유용합니다. 명령이 파일에서 읽으려는 많은 위치에서 <(...)
파일 이름 대신 프로세스 대체를 지정할 수 있습니다 . >(...)
명령이 파일 에 쓰려 는 파일 이름 대신 사용할 수 있는 유사한 형식 이 있습니다 .
배열과 마찬가지로 프로세스 대체는 bash 및 기타 고급 셸의 기능입니다. POSIX 표준의 일부가 아닙니다.
대안 : lastpipe
원하는 경우 lastpipe
프로세스 대체 대신 사용할 수 있습니다 (모자 팁 : Caesar ) :
set +m
shopt -s lastpipe
array=()
find . -name "${input}" -print0 | while IFS= read -r -d $'\0'; do array+=("$REPLY"); done; declare -p array
shopt -s lastpipe
bash는 현재 셸 (백그라운드 아님)의 파이프 라인에서 마지막 명령을 실행하도록 지시합니다. 이렇게 array
하면 파이프 라인이 완료된 후에도 남아 있습니다. lastpipe
작업 제어가 꺼진 경우에만 적용 되기 때문에 set +m
. (스크립트에서는 명령 줄과 달리 작업 제어가 기본적으로 꺼져 있습니다.)
추가 참고 사항
다음 명령은 셸 배열이 아닌 셸 변수를 만듭니다.
array=`find . -name "${input}"`
배열을 만들려면 find 출력 주위에 괄호를 넣어야합니다. 순진하게도 다음과 같이 할 수 있습니다.
array=(`find . -name "${input}"`) # don't do this
문제는 쉘이의 결과에 대해 단어 분할을 수행 find
하여 배열의 요소가 원하는대로 보장되지 않는다는 것입니다.
2019 업데이트
버전 4.4-alpha부터 bash는 이제 -d
위의 루프가 더 이상 필요하지 않도록 옵션을 지원 합니다. 대신 다음을 사용할 수 있습니다.
mapfile -d $'\0' array < <(find . -name "${input}" -print0)
이에 대한 자세한 내용은 Benjamin W.의 답변을 참조하십시오 .
답변
Bash 4.4는 /에 대한 -d
옵션을 도입 했으므로 이제 다음과 같이 해결할 수 있습니다.readarray
mapfile
readarray -d '' array < <(find . -name "$input" -print0)
공백, 줄 바꿈 및 글 로빙 문자를 포함하여 임의의 파일 이름으로 작동하는 방법. 예를 들어 GNU find와 같이 find
지원 이 필요합니다 -print0
.
로부터 수동 (다른 옵션을 생략) :
mapfile [-d delim] [array]
-d
의 첫 번째 문자는delim
개행 대신 각 입력 행을 종료하는 데 사용됩니다. 경우delim
빈 문자열입니다,mapfile
그것은 NUL 문자를 읽을 때 줄을 종료합니다.
그리고 readarray
의 동의어입니다 mapfile
.
답변
bash
4 이상을 사용하는 경우 사용을 다음 find
으로 바꿀 수 있습니다.
shopt -s globstar nullglob
array=( **/*"$input"* )
에서 **
활성화 된 패턴 globstar
은 0 개 이상의 디렉토리와 일치하여 패턴이 현재 디렉토리의 임의 깊이와 일치하도록합니다. nullglob
옵션이 없으면 패턴 (매개 변수 확장 후)이 문자 그대로 처리되므로 일치하는 항목이 없으면 빈 배열이 아닌 단일 문자열이있는 배열을 갖게됩니다.
dotglob
숨겨진 디렉토리 (예 :)를 탐색 .ssh
하고 숨겨진 파일 (예 :)도 일치 시키려면 첫 번째 줄에 옵션을 추가하십시오 .bashrc
.
답변
당신은 같은 것을 시도 할 수 있습니다
array=(`find . -type f | sort -r | head -2`)
, 그리고 배열 값을 인쇄하려면 echo와 같은 것을 시도 할 수 있습니다. "${array[*]}"
답변
다음은 macOS의 Bash 및 Z Shell 모두에서 작동하는 것으로 보입니다.
#! /bin/sh
IFS=$'\n'
paths=($(find . -name "foo"))
unset IFS
printf "%s\n" "${paths[@]}"
답변
bash에서 $(<any_shell_cmd>)
명령을 실행하고 출력을 캡처하는 데 도움이됩니다. 이 전달 IFS
로 \n
구분 배열로 그것을 변환하는 데 도움으로.
IFS='\n' read -r -a txt_files <<< $(find /path/to/dir -name "*.txt")
답변
다음과 같이 할 수 있습니다.
#!/bin/bash
echo "input : "
read input
echo "searching file with this pattern '${input}' under present directory"
array=(`find . -name '*'${input}'*'`)
for i in "${array[@]}"
do :
echo $i
done