x=$(find . -name "*.txt")
echo $x
Bash 쉘에서 위의 코드를 실행하면 목록이 아닌 공백으로 구분 된 여러 파일 이름이 포함 된 문자열이 나타납니다.
물론 목록을 얻기 위해 공백으로 더 분리 할 수는 있지만 더 좋은 방법이 있다고 확신합니다.
그렇다면 find
명령 결과를 반복하는 가장 좋은 방법은 무엇 입니까?
답변
TL; DR : 가장 정확한 답변을 위해 여기에 온다면 내 개인적인 취향을 원할 것 find . -name '*.txt' -exec process {} \;
입니다 (이 글의 하단 참조). 시간이 있다면 나머지 부분을 읽고 여러 가지 다른 방법과 대부분의 문제를 확인하십시오.
전체 답변 :
가장 좋은 방법은 수행하려는 작업에 따라 다르지만 몇 가지 옵션이 있습니다. 하위 트리의 파일이나 폴더에 이름에 공백이없는 경우 파일을 반복 할 수 있습니다.
for i in $x; do # Not recommended, will break on whitespace
process "$i"
done
조금 더 나은 임시 변수를 잘라내십시오 x
.
for i in $(find -name \*.txt); do # Not recommended, will break on whitespace
process "$i"
done
당신이 할 수있을 때 glob하는 것이 훨씬 좋습니다. 현재 디렉토리의 파일에 대한 공백 안전 :
for i in *.txt; do # Whitespace-safe but not recursive.
process "$i"
done
이 globstar
옵션 을 활성화하면 이 디렉토리와 모든 하위 디렉토리에서 일치하는 모든 파일을 가져올 수 있습니다.
# Make sure globstar is enabled
shopt -s globstar
for i in **/*.txt; do # Whitespace-safe and recursive
process "$i"
done
예를 들어 파일 이름이 이미 파일에있는 경우 read
다음 을 사용해야합니다 .
# IFS= makes sure it doesn't trim leading and trailing whitespace
# -r prevents interpretation of \ escapes.
while IFS= read -r line; do # Whitespace-safe EXCEPT newlines
process "$line"
done < filename
read
find
구분 기호를 적절하게 설정하여 다음 과 같이 안전하게 사용할 수 있습니다 .
find . -name '*.txt' -print0 |
while IFS= read -r -d '' line; do
process "$line"
done
보다 복잡한 검색의 경우 옵션 또는 다음과 find
함께을 사용하는 것이 -exec
좋습니다 -print0 | xargs -0
.
# execute `process` once for each file
find . -name \*.txt -exec process {} \;
# execute `process` once with all the files as arguments*:
find . -name \*.txt -exec process {} +
# using xargs*
find . -name \*.txt -print0 | xargs -0 process
# using xargs with arguments after each filename (implies one run per filename)
find . -name \*.txt -print0 | xargs -0 -I{} process {} argument
find
또한 -execdir
대신을 사용하여 명령을 실행하기 전에 각 파일의 디렉토리에 CD를 넣을 -exec
수 있으며 -ok
대신 -exec
(또는 -okdir
대신 )을 사용하여 대화식 (각 파일에 대해 명령을 실행하기 전에 프롬프트)으로 만들 수 있습니다 -execdir
.
* : 기술적으로 find
and xargs
(기본적으로)는 모든 파일을 처리하는 데 걸리는 횟수만큼 명령 줄에 입력 할 수있는 인수 수만큼 명령을 실행합니다. 실제로 파일 수가 매우 많지 않은 한 중요하지 않으며 길이를 초과하지만 동일한 명령 줄에 모두 필요한 경우 SOL 은 다른 방법을 찾습니다.
답변
무엇을하든 루프를 사용하지 마십시오for
.
# Don't do this
for file in $(find . -name "*.txt")
do
…code using "$file"
done
세 가지 이유 :
- for 루프가 시작
find
되려면 완료까지 실행해야합니다. - 파일 이름에 공백 (공백, 탭 또는 줄 바꿈 포함)이 있으면 두 개의 별도 이름으로 처리됩니다.
- 현재는 아니지만, 명령 행 버퍼를 오버런 할 수 있습니다. 명령 행 버퍼가 32KB를 보유하고
for
루프가 40KB의 텍스트를 리턴 한다고 가정하십시오 . 마지막 8KB는for
루프에서 즉시 삭제되며 결코 알 수 없습니다.
항상 while read
구문을 사용하십시오 .
find . -name "*.txt" -print0 | while read -d $'\0' file
do
…code using "$file"
done
find
명령이 실행 되는 동안 루프 가 실행됩니다. 또한이 명령은 파일 이름에 공백이있는 경우에도 작동합니다. 또한 명령 줄 버퍼가 오버플로되지 않습니다.
은 -print0
파일 분리기 대신 줄 바꿈으로 NULL을 사용하고는 -d $'\0'
읽는 동안 분리로 NULL을 사용합니다.
답변
find . -name "*.txt"|while read fname; do
echo "$fname"
done
참고 : bmargulies로 표시되는 이 방법 과 (두 번째) 방법은 파일 / 폴더 이름의 공백과 함께 사용하는 것이 안전합니다.
파일 / 폴더 이름에 개행 문자가 포함되도록하기 위해서는 다음 -exec
과 find
같은 조건을 사용해야 합니다.
find . -name '*.txt' -exec echo "{}" \;
은 {}
발견 된 항목에 대한 자리 표시 자이며,이 \;
종료하는 데 사용되는 -exec
술어를.
그리고 완전성을 위해 또 다른 변형을 추가하겠습니다. 다목적 성을 위해 * nix 방법을 좋아해야합니다.
find . -name '*.txt' -print0|xargs -0 -n 1 echo
이것은 인쇄 된 항목을 \0
파일 또는 폴더 이름의 파일 시스템에서 허용되지 않는 문자로 분리 하므로 모든베이스를 다루어야합니다. xargs
하나씩 하나씩 집어 들고 …
답변
파일 이름에는 공백과 제어 문자가 포함될 수 있습니다. bash에서 쉘 확장을위한 공백은 (기본) 구분 기호이며 x=$(find . -name "*.txt")
질문 의 결과로 전혀 권장되지 않습니다. find가 공백이있는 파일 이름을 얻는 경우, 예 "the file.txt"
를 들어 x
루프에서 처리하는 경우 처리를 위해 2 개의 분리 된 문자열을 얻게됩니다 . IFS
예를 들어 구분 기호 (bash 변수)를 로 변경하여이를 개선 할 수 \r\n
있지만 파일 이름은 제어 문자를 포함 할 수 있으므로 (완전히) 안전한 방법은 아닙니다.
필자의 견해로는 파일 처리에 권장되는 (안전한) 두 가지 패턴이 있습니다.
1. 루프 및 파일 이름 확장에 사용 :
for file in ./*.txt; do
[[ ! -e $file ]] && continue # continue, if file does not exist
# single filename is in $file
echo "$file"
# your code here
done
2. 읽기-읽기 및 프로세스 대체 사용
while IFS= read -r -d '' file; do
# single filename is in $file
echo "$file"
# your code here
done < <(find . -name "*.txt" -print0)
비고
패턴 1 :
- bash는 일치하는 파일이 없으면 검색 패턴 ( “* .txt”)을 반환하므로 “파일이 존재하지 않으면 계속합니다”라는 추가 줄이 필요합니다. 참조 배쉬 설명서, 파일 이름 확장을
nullglob
이 추가 라인을 피하기 위해 쉘 옵션을 사용할 수 있습니다.- ”
failglob
쉘 옵션이 설정되어 있고 일치하는 것이 없으면 오류 메시지가 인쇄되고 명령이 실행되지 않습니다.” (위의 Bash Manual에서) - shell option
globstar
: “설정하면 파일 이름 확장 컨텍스트에 사용 된 ‘**’패턴은 모든 파일과 0 개 이상의 디렉토리 및 하위 디렉토리와 일치합니다. 패턴 뒤에 ‘/’가 있으면 디렉토리와 하위 디렉토리 만 일치합니다.” 참조 , 배쉬는 수동 shopt 내부 기본 제공된을 - 파일 이름 확장을위한 다른 옵션 :
extglob
,nocaseglob
,dotglob
및 쉘 변수GLOBIGNORE
패턴 2 :
-
파일명은 공백, 탭, 공간 바꿈을 포함 할 수 있으며, … 안전한 방법으로 처리 파일명에
find
함께-print0
사용된다 : 파일명 모든 제어 문자로 인쇄 및 NUL 종료. 또한 볼 은 GNU findutils의 맨, 안전하지 않은 파일 이름 처리 , 안전한 파일 이름 처리 , 파일 이름에 이상한 문자가 . 이 주제에 대한 자세한 설명은 아래 David A. Wheeler를 참조하십시오. -
while 루프에서 찾기 결과를 처리 할 수있는 몇 가지 패턴이 있습니다. 다른 사람들 (kevin, David W.)은 파이프를 사용 하여이 작업을 수행하는 방법을 보여주었습니다.
files_found=1
find . -name "*.txt" -print0 |
while IFS= read -r -d '' file; do
# single filename in $file
echo "$file"
files_found=0 # not working example
# your code here
done
[[ $files_found -eq 0 ]] && echo "files found" || echo "no files found"이 코드를 시도하면 작동하지 않는 것을 알 수 있습니다.
files_found
항상 “true”이고 코드는 항상 “파일을 찾을 수 없습니다”를 에코합니다. 이유는 다음과 같습니다. 파이프 라인의 각 명령은 별도의 하위 셸에서 실행되므로 루프 내에서 변경된 변수 (별도의 하위 셸)는 기본 셸 스크립트의 변수를 변경하지 않습니다. 그렇기 때문에 프로세스 대체를 “더 나은”보다 유용하고 일반적인 패턴으로 사용하는 것이 좋습니다. 파이프 라인에있는 루프에 변수를 설정하는 방법을
참조하십시오 . 이 주제에 대한 자세한 논의를 위해 왜 사라지는가? (Greg의 Bash FAQ에서)
추가 참조 및 출처 :
답변
(@Socowi의 탁월한 속도 향상을 포함하도록 업데이트)
$SHELL
그것을 지원하는 어떤 것으로 (대시 / zsh / bash …) :
find . -name "*.txt" -exec $SHELL -c '
for i in "$@" ; do
echo "$i"
done
' {} +
끝난.
원래 답변 (더 짧지 만 느림) :
find . -name "*.txt" -exec $SHELL -c '
echo "$0"
' {} \;
답변
# Doesn't handle whitespace
for x in `find . -name "*.txt" -print`; do
process_one $x
done
or
# Handles whitespace and newlines
find . -name "*.txt" -print0 | xargs -0 -n 1 process_one
답변
find
나중에 출력을 다음과 같이 사용하려면 출력을 배열에 저장할 수 있습니다 .
array=($(find . -name "*.txt"))
이제 각 요소를 줄 바꿈으로 인쇄하려면 for
배열의 모든 요소에 대해 루프 반복을 사용하거나 printf 문을 사용할 수 있습니다.
for i in ${array[@]};do echo $i; done
또는
printf '%s\n' "${array[@]}"
다음을 사용할 수도 있습니다.
for file in "`find . -name "*.txt"`"; do echo "$file"; done
이것은 개행으로 각 파일 이름을 인쇄합니다
find
출력을 목록 형식으로 만 인쇄하려면 다음 중 하나를 사용할 수 있습니다.
find . -name "*.txt" -print 2>/dev/null
또는
find . -name "*.txt" -print | grep -v 'Permission denied'
그러면 오류 메시지가 제거되고 파일 이름 만 줄 바꿈으로 출력됩니다.
파일 이름으로 무언가를하고 싶다면 배열에 저장하는 것이 좋습니다. 그렇지 않으면 해당 공간을 소비 할 필요가 없으므로의 출력을 직접 인쇄 할 수 있습니다 find
.