파일을 반복 할 때 두 가지 방법이 있습니다.
-
–
for
루프를 사용하십시오 :for f in *; do echo "$f" done
-
사용
find
:find * -prune | while read f; do echo "$f" done
이 두 루프가 동일한 파일 목록을 찾을 것이라고 가정하면 성능 과 처리 에서 두 옵션의 차이점은 무엇 입니까?
답변
1.
첫번째:
for f in *; do echo "$f" done
라는 파일에 실패 -n
, -e
그리고 같은 변종 -nene
파일 이름은 백 슬래시를 포함하여, 일부 bash는 배포와 함께.
두번째:
find * -prune | while read f; do echo "$f" done
(라는 파일보다 경우에 실패 !
, -H
, -name
, (
… 시작이나 끝 공백이나 개행 문자가 포함 된 파일 이름)
그것은 확장하는 쉘이며 *
, find
인수로받은 파일을 인쇄하는 것 외에는 아무것도하지 않습니다. 당신은뿐만 아니라 사용할 수도 printf '%s\n'
로하는 대신 printf
내장도 피할 것입니다 너무 많은 인수를 잠재적 인 오류가 발생했습니다.
2.
의 확장 *
은 정렬되어 있으므로 정렬이 필요하지 않으면 조금 더 빨라질 수 있습니다. 에서 zsh
:
for f (*(oN)) printf '%s\n' $f
또는 간단히 :
printf '%s\n' *(oN)
bash
내가 말할 수있는 한 그에 상응하는 것이 없기 때문에에 의지해야 find
합니다.
삼.
find . ! -name . -prune ! -name '.*' -print0 |
while IFS= read -rd '' f; do
printf '%s\n' "$f"
done
(GNU / BSD -print0
비표준 확장 사용).
여전히 find 명령을 생성하고 느린 while read
루프를 사용하므로 for
파일 목록이 크지 않으면 루프를 사용하는 것보다 느릴 것 입니다.
4.
또한 쉘 와일드 카드 확장과 달리 각 파일에 대해 시스템 호출을 find
수행 lstat
하므로 정렬되지 않은 것이이를 보완하지는 않습니다.
GNU / BSD find
에서는 -maxdepth
확장을 사용하여 피할 수 있습니다 lstat
.
find . -maxdepth 1 ! -name '.*' -print0 |
while IFS= read -rd '' f; do
printf '%s\n' "$f"
done
find
stdio 출력 버퍼링을 제외하고 파일 이름을 찾 자마자 출력 하기 시작하므로 루프에서 수행하는 작업에 시간이 많이 걸리고 파일 이름 목록이 stdio 버퍼 이상인 경우 더 빠를 수 있습니다 (4 / 8 kB). 이 경우 루프 내 처리 find
가 모든 파일 찾기를 마치기 전에 시작됩니다 . GNU 및 FreeBSD 시스템에서는 stdbuf
더 빨리 발생 하도록 사용할 수 있습니다 (stdio 버퍼링 사용 안함).
5.
각 파일에 대해 명령을 실행하는 POSIX / standard / portable 방법 find
은 -exec
술어 를 사용하는 것입니다 .
find . ! -name . -prune ! -name '.*' -exec some-cmd {} ';'
그러나 echo
쉘에는 내장 버전이 echo
있지만 find
새로운 프로세스를 생성하고 /bin/echo
각 파일마다 실행해야 하므로 쉘에서 루핑을 수행하는 것보다 덜 효율적 입니다.
여러 명령을 실행해야하는 경우 다음을 수행 할 수 있습니다.
find . ! -name . -prune ! -name '.*' -exec cmd1 {} ';' -exec cmd2 {} ';'
그러나 성공한 cmd2
경우에만 실행됩니다 cmd1
.
6.
각 파일에 대해 복잡한 명령을 실행하는 정식 방법은 다음을 사용하여 쉘을 호출하는 것입니다 -exec ... {} +
.
find . ! -name . -prune ! -name '.*' -exec sh -c '
for f do
cmd1 "$f"
cmd2 "$f"
done' sh {} +
그 시간, 우리 효율적인있는 방법입니다 다시 echo
우리가 사용하고 이후 sh
의 내장의 하나는 -exec +
몇 가지로 버전 급부상 sh
가능합니다.
7.
에서 200.000 파일과 디렉토리에 내 테스트 에서 ext4에 짧은 이름의 zsh
하나 (제 2)가 첫 번째 간단한에 이어, 지금까지 가장 빠른 것입니다 for i in *
루프 (평소와 같이, 비록 bash
많은 느린 다른 조개에 비해 그입니다).
답변
2259 항목이있는 디렉토리에서 이것을 시도하고 time
명령을 사용했습니다 .
time for f in *; do echo "$f"; done
파일을 뺀 결과 는 다음과 같습니다.
real 0m0.062s
user 0m0.036s
sys 0m0.012s
time find * -prune | while read f; do echo "$f"; done
파일을 뺀 결과 는 다음과 같습니다.
real 0m0.131s
user 0m0.056s
sys 0m0.060s
캐시 누락을 제거하기 위해 각 명령을 여러 번 실행했습니다. 이것에 유지 제안 bash
빨리 사용하는 것보다 (… I에 대해) find
와 (출력을 배관 bash
)
완벽 find
을 기하기 위해 예제에서 파이프 가 완전히 중복되었으므로 파이프를에서 삭제 했습니다. 의 결과 find * -prune
는 다음과 같습니다.
real 0m0.053s
user 0m0.016s
sys 0m0.024s
또한 time echo *
(출력은 줄 바꿈으로 구분되지 않습니다) :
real 0m0.009s
user 0m0.008s
sys 0m0.000s
이 시점에서 나는 그 이유 echo *
가 더 빠르다고 생각합니다. 줄 바꿈이 너무 많지 않아서 출력이 많이 스크롤되지 않기 때문입니다. 테스트합시다 …
time find * -prune | while read f; do echo "$f"; done > /dev/null
수율 :
real 0m0.109s
user 0m0.076s
sys 0m0.032s
반면 time find * -prune > /dev/null
수익률 :
real 0m0.027s
user 0m0.008s
sys 0m0.012s
그리고 time for f in *; do echo "$f"; done > /dev/null
수확량 :
real 0m0.040s
user 0m0.036s
sys 0m0.004s
그리고 마지막으로 : time echo * > /dev/null
수확량 :
real 0m0.011s
user 0m0.012s
sys 0m0.000s
변형의 일부는 임의의 요인으로 설명 할 수 있지만 다음과 같이 분명해 보입니다.
- 출력이 느리다
- 배관 비용이 약간
for f in *; do ...
find * -prune
자체 보다 속도가 느리지 만 파이프가 포함 된 위의 구조에서는 속도가 더 빠릅니다.
또한 두 가지 접근 방식은 공백이있는 이름을 잘 처리하는 것으로 보입니다.
편집하다:
find . -maxdepth 1 > /dev/null
대 타이밍 find * -prune > /dev/null
:
time find . -maxdepth 1 > /dev/null
:
real 0m0.018s
user 0m0.008s
sys 0m0.008s
find * -prune > /dev/null
:
real 0m0.031s
user 0m0.020s
sys 0m0.008s
따라서 추가 결론 :
find * -prune
find . -maxdepth 1
전자 보다 속도가 느리면 쉘은 glob를 처리 한 다음에 (대형) 명령 줄을 작성find
합니다. NB :find . -prune
그냥 반환합니다.
.
추가 테스트 : time find . -maxdepth 1 -exec echo {} \; >/dev/null
:
real 0m3.389s
user 0m0.040s
sys 0m0.412s
결론:
- 지금까지 가장 느린 방법입니다. 이 접근법이 제안 된 답변에 대한 의견에서 지적했듯이 각 인수는 쉘을 생성합니다.
답변
찾기를 다음과 같이 바꾸더라도 찾기와 함께 확실히 갈 것입니다.
find . -maxdepth 1 -exec echo {} \;
현명한 성능 find
은 물론 필요에 따라 훨씬 빠릅니다. 현재 가지고 for
있는 것은 현재 디렉토리의 파일 / 디렉토리 만 표시하지만 디렉토리 내용은 표시하지 않습니다. find를 사용하면 하위 디렉토리의 내용도 표시됩니다.
나는 당신 for
의 *
의지가 먼저 확장되어야 하기 때문에 찾기가 더 낫다고 말하고 파일의 양이 많은 디렉토리를 가지고 있다면 오류 인수 목록이 너무 길 것을 두려워합니다 . 동일find *
예를 들어, 현재 사용중인 시스템 중 하나에는 2 백만 개가 넘는 파일이있는 두 개의 디렉토리가 있습니다 (각 <100k).
find *
-bash: /usr/bin/find: Argument list too long
답변
find * -prune | while read f; do
echo "$f"
done
쓸모없는 사용 find
-당신이 말하는 것은 효과적으로 “디렉토리 ( *
) 의 각 파일에 대해 어떤 파일도 찾지 못합니다. 또한 여러 가지 이유로 안전하지 않습니다 :
- 경로의 백 슬래시는
-r
옵션 없이 특별히 처리 됩니다read
. 이것은for
루프 문제가 아닙니다 . - 경로의 줄 바꿈은 루프 내부의 사소한 기능을 손상시킵니다. 이것은
for
루프 문제가 아닙니다 .
로 파일 이름을 처리하는 find
것은 어렵 기 때문에 for
가능할 때마다 루프 옵션을 사용해야합니다 . 또한 같은 외부 프로그램을 실행하면 find
일반적으로 같은 내부 루프 명령을 실행하는 것보다 느립니다 for
.
답변
그러나 우리는 성능 문제에 대한 빨판입니다! 이 실험 요청은별로 유효하지 않은 가정을 적어도 두 번 만듭니다.
A. 동일한 파일을 찾는다고 가정합니다.
글쎄, 그들은 처음에 같은 파일을 찾을 것입니다 . 왜냐하면 둘 다 같은 글 로프를 반복하기 때문 *
입니다. 그러나 find * -prune | while read f
몇 가지 결함으로 인해 예상되는 모든 파일을 찾을 수는 없습니다.
- POSIX find는 둘 이상의 경로 인수를 허용하지 않습니다. 대부분의
find
구현은 여전히 그렇지만 당신은 그것에 의존해서는 안됩니다. find *
때 때 깰 수 있습니다ARG_MAX
. 내장for f in *
이 아닌에ARG_MAX
적용 되기 때문exec
입니다.while read f
공백으로 시작하고 끝나는 파일 이름으로 중단 될 수 있습니다.while read
기본 매개 변수로 이를 극복 할 수는REPLY
있지만 줄 바꿈이있는 파일 이름에 대해서는 여전히 도움이되지 않습니다.
B echo
.. 아무도 파일 이름을 에코하기 위해이 작업을 수행하지 않습니다. 원하는 경우 다음 중 하나를 수행하십시오.
printf '%s\n' *
find . -mindepth 1 -maxdepth 1 # for dotted names, too
while
루프 의 파이프 는 루프가 끝날 때 닫히는 암시 적 서브 쉘을 생성하는데, 이는 직관적이지 않을 수 있습니다.
질문에 대답하기 위해 여기에 184 개의 파일과 디렉토리가있는 내 디렉토리의 결과가 있습니다.
$ time bash -c 'for i in {0..1000}; do find * -prune | while read f; do echo "$f"; done >/dev/null; done'
real 0m7.998s
user 0m5.204s
sys 0m2.996s
$ time bash -c 'for i in {0..1000}; do for f in *; do echo "$f"; done >/dev/null; done'
real 0m2.734s
user 0m2.553s
sys 0m0.181s
$ time bash -c 'for i in {0..1000}; do printf '%s\n' * > /dev/null; done'
real 0m1.468s
user 0m1.401s
sys 0m0.067s
$ time bash -c 'for i in {0..1000}; do find . -mindepth 1 -maxdepth 1 >/dev/null; done '
real 0m1.946s
user 0m0.847s
sys 0m0.933s
답변
find *
*
경로가 아닌 술어처럼 보이는 토큰을 생성하면 올바르게 작동하지 않습니다 .
옵션의 끝을 나타내며 find의 옵션이 경로 앞에 --
오므로 일반적인 인수를 사용하여이 문제를 해결할 수 없습니다 --
.
이 문제를 해결하려면 find ./*
대신 사용할 수 있습니다 . 그러나 정확히 같은 문자열을 생성하지는 않습니다 for x in *
.
주 find ./* -prune | while read f ..
사실의 스캔 기능을 사용하지 않습니다 find
. ./*
실제로 디렉토리를 탐색하고 이름을 생성하는 것은 글 로빙 구문 입니다. 그런 다음 find
프로그램은 stat
해당 이름 각각에 대해 최소한 확인 을 수행해야 합니다. 프로그램을 시작하고 이러한 파일에 액세스 한 다음 I / O를 수행하여 출력을 읽는 오버 헤드가 있습니다.
그것이보다 효율적일 수있는 방법을 상상하기는 어렵습니다 for x in ./* ...
.
답변
우선 for
Bash에 내장 된 쉘 키워드 find
는 별개의 실행 파일입니다.
$ type -a for
for is a shell keyword
$ type -a find
find is /usr/bin/find
for
이 확장 할 때 루프 만 발견 된 어떤 디렉토리에 재귀하지 않습니다는 globstar 문자에서 파일을 찾을 수 있습니다.
반면에 찾기에는 globstar가 확장 한 목록이 제공되지만이 확장 목록 아래의 모든 파일과 디렉토리를 재귀 적으로 찾아서 각각 while
루프로 파이프 합니다.
이 두 가지 접근 방식은 공백이 포함 된 경로 나 파일 이름을 처리하지 않는다는 점에서 위험한 것으로 간주 될 수 있습니다.
이것이이 두 가지 접근법에 대해 언급 할 가치가있는 모든 것입니다.