"find"명령 결과를 Bash에 배열로 저장하려면 어떻게해야합니까?


92

결과를 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에서 몇 가지 해결책을 찾았습니다. 하지만 약간 다르기 때문에 제 경우에는 신청할 수 없습니다. 루프 전에 변수에 결과를 저장해야합니다. 다시 한번 감사드립니다.

답변:


133

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가있는 경우 아래 업데이트를 참조하십시오.]

작동 원리

  1. 첫 번째 줄은 빈 배열을 만듭니다. array=()

  2. read명령문이 실행될 때마다 표준 입력에서 널로 구분 된 파일 이름을 읽습니다. 이 -r옵션은 read백 슬래시 문자를 그대로 두도록 지시 합니다. 은 -d $'\0'지시 read입력이 널 (null)로 구분 될 것이다. 에 이름을 생략 했으므로 read쉘은 입력을 기본 이름 : REPLY.

  3. array+=("$REPLY")명령문은 배열에 새 파일 이름을 추가합니다 array.

  4. 마지막 줄은 리디렉션과 명령 대체를 결합 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 lastpipebash는 현재 셸 (백그라운드 아님)의 파이프 라인에서 마지막 명령을 실행하도록 지시합니다. 이렇게 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.의 답변을 참조하십시오 .


1
@JuneyoungOh 도움이되었습니다. 프로세스 대체 섹션을 추가했습니다.
John1024

3
@Rockallite 좋은 관찰이지만 불완전합니다. 여러 단어로 분할하지 않는 것이 사실이지만 IFS=입력 줄의 시작 또는 끝에서 공백을 제거 하지 않아야 합니다. 당신의 출력을 비교하여 쉽게 테스트 할 수 read var <<<' abc '; echo ">$var<"의 출력과 IFS= read var <<<' abc '; echo ">$var<". 전자의 경우 앞뒤의 공백 abc이 제거됩니다. 후자에서는 그렇지 않습니다. 공백으로 시작하거나 끝나는 파일 이름은 비정상적 일 수 있지만 존재하는 경우 올바르게 처리되기를 원합니다.
John1024

1
난 당신의 코드를 실행 한 후 안녕, 난 근처 메시지 구문 오류가 예기치 않은 토큰 <' 일 << '(AAA / -not -newermt "$ last_build_timestamp_v"타입 F -print0 찾기)
Przemysław Sienkiewicz

1
참고 : 더 간단한 ''것을 대신 사용할 수 있습니다 $'\0'.n=0; while IFS= read -r -d '' line || [ "$line" ]; do echo "$((++n)):$line"; done < <(printf 'first\nstill first\0second\0third')
glenn jackman

1
@theeagle 나는 당신이 쓰기를 의도했다고 가정합니다 BLAH=$(find . -name '*.php'). 답변에서 논의했듯이 해당 접근 방식은 제한된 경우에만 작동하지만 일반적으로 모든 파일 이름에서 작동하지 않으며 OP가 예상 한대로 array를 생성하지 않습니다 .
John1024

35

Bash 4.4는 /에 대한 -d옵션을 도입 했으므로 이제 다음과 같이 해결할 수 있습니다.readarraymapfile

readarray -d '' array < <(find . -name "$input" -print0)

공백, 줄 바꿈 및 글 로빙 문자를 포함하여 임의의 파일 이름으로 작동하는 방법. 예를 들어 GNU find와 같이 find지원 이 필요합니다 -print0.

로부터 수동 (다른 옵션을 생략) :

mapfile [-d delim] [array]

-d
의 첫 번째 문자는 delim개행 대신 각 입력 행을 종료하는 데 사용됩니다. 경우 delim빈 문자열입니다, mapfile그것은 NUL 문자를 읽을 때 줄을 종료합니다.

그리고 readarray의 동의어입니다 mapfile.


18

bash4 이상을 사용하는 경우 사용을 다음 find으로 바꿀 수 있습니다.

shopt -s globstar nullglob
array=( **/*"$input"* )

에서 **활성화 된 패턴 globstar은 0 개 이상의 디렉토리와 일치하여 패턴이 현재 디렉토리의 임의 깊이와 일치하도록합니다. nullglob옵션이 없으면 패턴 (매개 변수 확장 후)이 문자 그대로 처리되므로 일치하는 항목이 없으면 빈 배열이 아닌 단일 문자열이있는 배열을 갖게됩니다.

dotglob숨겨진 디렉토리 (예 :)를 탐색 .ssh하고 숨겨진 파일 (예 :)도 일치 시키려면 첫 번째 줄에 옵션을 추가하십시오 .bashrc.


4
어쩌면 nullglob너무…
kojiro 2014-04-29

1
그래, 난 항상 잊어 버려.
chepner 2014

5
dotglob설정 하지 않는 한 숨겨진 파일과 디렉토리는 포함되지 않습니다 (원할 수도 있고 원하지 않을 수도 있지만 언급 할 가치가 있습니다).
gniourf_gniourf 2014

10

당신은 같은 것을 시도 할 수 있습니다

array=(`find . -type f | sort -r | head -2`)
, 그리고 배열 값을 인쇄하려면 echo와 같은 것을 시도 할 수 있습니다. "${array[*]}"


7
공백 또는 glob 문자가있는 파일 이름이 있으면 중단됩니다.
gniourf_gniourf dec.

1

다음은 macOS의 Bash 및 Z Shell 모두에서 작동하는 것으로 보입니다.

#! /bin/sh

IFS=$'\n'
paths=($(find . -name "foo"))
unset IFS

printf "%s\n" "${paths[@]}"

-1

bash에서 $(<any_shell_cmd>)명령을 실행하고 출력을 캡처하는 데 도움이됩니다. 이 전달 IFS\n구분 배열로 그것을 변환하는 데 도움으로.

IFS='\n' read -r -a txt_files <<< $(find /path/to/dir -name "*.txt")

3
이것은 결과의 첫 번째 파일 만 find배열로 가져옵니다.
Benjamin W.

-2

다음과 같이 할 수 있습니다.

#!/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

감사. 많이. 그러나 @anishsane이 지적했듯이 파일 이름의 빈 공간은 내 프로그램에서 고려해야합니다. 어쨌든 고마워!
오준영 2014-04-29

-3

나를 위해 이것은 cygwin에서 잘 작동했습니다.

declare -a names=$(echo "("; find <path> <other options> -printf '"%p" '; echo ")")
for nm in "${names[@]}"
do
    echo "$nm"
done

이것은 공백과 함께 작동하지만 디렉토리 이름에 큰 따옴표 ( ")를 사용하지 않습니다 (어쨌든 Windows 환경에서는 허용되지 않음).

-printf 옵션의 공백을주의하십시오.


3
Broken and dangerous : 따옴표를 처리하지 않으며 임의 코드 삽입의 대상이됩니다. 사용하지 마세요.
gniourf_gniourf dec.

2
누군가이 게시물을 삭제하기 위해 신고 한 것 같습니다. "틀렸다"는 삭제 이유가 아닙니다. 사용자가 답변을 시도하고 주제에 관한 것이며 답변 기준을 충족합니다. downvote 버튼은 삭제 버튼이 아니라 유용성과 정확성을 측정하는 데 사용됩니다.
Frambot

3
gniourf가 지적했듯이 웹 페이지와 같이 다른 사용자가 시스템에 옵션을 입력하는 환경을위한 것이 아닙니다. 하지만 모두가 그 환경을 위해 프로그램하는 것은 아닙니다. 디렉토리의 파일 이름을 바꾸는 데 사용했습니다.
R Risack
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.