![Bash의 파일에서 마지막으로 수정된 TS를 얻는 가장 빠른 방법은 무엇입니까?](https://linux55.com/image/204314/Bash%EC%9D%98%20%ED%8C%8C%EC%9D%BC%EC%97%90%EC%84%9C%20%EB%A7%88%EC%A7%80%EB%A7%89%EC%9C%BC%EB%A1%9C%20%EC%88%98%EC%A0%95%EB%90%9C%20TS%EB%A5%BC%20%EC%96%BB%EB%8A%94%20%EA%B0%80%EC%9E%A5%20%EB%B9%A0%EB%A5%B8%20%EB%B0%A9%EB%B2%95%EC%9D%80%20%EB%AC%B4%EC%97%87%EC%9E%85%EB%8B%88%EA%B9%8C%3F.png)
거대한 파일 시스템에 수십만 개의 파일 목록이 있는 경우 bash에서 모든 파일의 마지막 수정 시간을 얻는 가장 빠른 방법은 무엇입니까?
속도를 향상시키기 위해 정렬할 방법이 없다고 가정하면 이 질문의 핵심은 실제로 다음과 같습니다. bash에서 파일의 마지막 수정 시간을 얻는 가장 빠른 방법은 무엇입니까? stat
가장 일반적인 접근 방식인 것 같지만 find
(with -printf "%T+"
) 및 date -r
.
(파일 시스템에 따라 달라지나요?)
답변1
GNU가 있는 경우 find
(GNU 지원 -printf
).
find /filesystem/mount/point -xdev -printf '%T@\t%p\0' > timestamps
가장 빠른 것입니다. find
디렉터리 트리를 순회한 다음 lstat()
시스템이 자체 호출하여 타임스탬프를 검색하도록 고도로 최적화되어 있습니다. 또한 lstat()
경로를 찾은 디렉터리에 상대적인 경로를 호출합니다 . 즉, lstat()
전체 경로를 호출하는 것보다 커널이 수행할 작업이 적습니다.
타임스탬프를 십진 시대 시간으로 인쇄 하면 %T@
숫자(초 및 나노초)를 이진에서 십진으로 변환하기만 하면 됩니다. 이는 %T+
사용자 시간대에서 달력 시간을 계산하는 것보다 훨씬 적은 작업입니다.
명령에는 다양하고 호환되지 않는 구현이 많이 있지만 그 중 어느 것도 파일을 찾지 못합니다 . 경로를 인수로 지정한 파일에서 메타데이터 정보를 검색하기 위해 // 또는 동등한 작업을 stat
수행할 뿐이 므로 찾으려면 다른 것이 필요합니다. stat()
파일을 다운로드하고 전체 경로를 .lstat()
statx()
statfs()
stat
대부분의 시스템에서 명령은 제한된 수의 인수만 허용할 수 있기 때문에 이는 stat
유틸리티를 여러 번 호출해야 할 수도 있으며, 매번 자체 프로세스에서 인수를 로드, 초기화 및 처리해야 할 때마다 대기해야 한다는 의미입니다.
한 가지 예외는 stat
내장 함수 가 zsh
GNU 또는 BSD보다 먼저 작동한다는 것입니다(GNU 의 함수 stat
는 아님 ).find
-printf
zsh
파일은 재귀 glob을 통해 찾을 수 있으므로 다른 명령을 실행하지 않고도 전체 프로세스를 완료할 수 있지만 find
.
date -r
(또한 GNU 비표준 확장 stat()
) 은 lstat()
. 다양한 stat
구현 중 일부는 이를 사용하고 stat()
일부 lstat()
는 기본적으로 사용하지만 둘 사이를 전환하라는 지시를 모두 받을 수 있습니다.
find
이를 더욱 최적화하려면 C로 구현하고 추가 보호 장치를 구현 하지 않고도 디렉터리 탐색을 수동으로 수행할 수 있습니다 . 최신 버전의 Linux에서는 statx()
이를 사용하여 더 적은 정보를 검색하라는 지시를 받는 것이 도움이 될 수 있습니다.
locate
// 있는 경우 mlocate
, plocate
캐시된 파일 목록을 사용하면 파일 시스템을 크롤링하지 않아도 되고 프로세스 속도를 높이는 데 도움이 될 수 있습니다(부실한 정보를 제공할 위험이 있음).
버전 4.9부터 GNU는 find
stdin을 사용하여 파일 목록을 process 에 전달할 수 있으므로 -files0-from -
다음을 수행할 수 있습니다.
LC_ALL=C locate -0 '/filesystem/mount/point/*' |
find -files0-from - -prune -printf '%T@\t%p\0' > timestamps
| xargs -r0 stat --printf '%.9Y\t%n\0' --
이는 여러 호출을 실행하는 유사한 것을 사용하는 것(여기서 GNU가 stat
있고 입력 파일 경로가 없다고 가정)보다 더 효율적입니다 .-
stat
파일에 NUL로 구분된 레코드로 저장된 파일 경로 목록이 있는 경우 동일한 접근 방식을 사용할 수 있습니다. 다른 형식인 경우 먼저 변환해야 합니다. 예를 들어, 한 줄에 하나의 경로가 포함된 텍스트 파일에 대해 이 작업을 수행할 수 있습니다. 즉, 개행 문자가 포함된 파일 경로를 저장할 수 없습니다 tr '\n' '\0' < list.txt | find...
.
내 테스트에서는 find
파일을 직접 찾는 것보다 여전히 덜 효율적입니다. 아마도 전체 경로를 find
호출하게 되므로 커널이 각 파일에 대해 전체 조회를 수행해야 함을 의미합니다.lstat()
또한 이보다 긴 파일 경로는 처리할 수 없습니다 PATH_MAX
(일반적으로 Linux에서는 약 4KiB, 출력 참조 ).getconf PATH_MAX /mount/point
어쨌든 성능을 향상시키기 위해 마지막으로 하고 싶은 일은 쉘 루프에서처럼 각 파일에 대해 외부 유틸리티(GNU date
또는 GNU 등) 를 실행하는 것입니다. stat
어떤 이유로 셸에서 파일과 해당 타임스탬프를 반복해야 하는 경우(예: 내장 함수가 bash
없는 경우 ) 다음을 수행할 수 있습니다.stat
while IFS=/ read -u3 -rd '' timestamp filepath; do
something with "$timestamp" and "$filepath"
done 3< <(find /filesystem/mount/point -xdev -printf '%T@/%p\0')
/
구분 기호 문자는 파일 경로 끝에 나타나지 않는 것이 보장되는 유일한 문자이기 때문에 사용합니다 . 한 가지 예외는 에 전달하는 디렉토리입니다 find
. 예를 들어 의 출력에서 find / -xdev -printf '%T@/%p\0'
첫 번째 레코드(및 첫 번째 레코드만)는 종료됩니다 /
. 여기에는 가 포함되며 in 대신 빈 문자열이 저장 <timestamp>//
됩니다 . 대신 을 사용하거나 ( 실제로 구분 기호가 아닌 내부 필드 구분 기호로 처리되는 경우) 파일 경로를 참조할 때 을 사용하여 이 문제를 해결할 수 있습니다 .read
/
$filepath
zsh
bash
$IFS
${filepath:-/}
이는 read
입력을 한 번에 한 바이트씩 읽어야 하기 때문에 본질적으로 비효율적입니다. 바라보다쉘 루프를 사용하여 텍스트를 처리하는 것이 왜 나쁜 습관으로 간주됩니까?더 알아보기. 성능이 문제라면 적절한 프로그래밍 언어를 사용하는 것이 더 나을 것입니다.
파일 수정 시간 검색(및 각 파일에 대해 별도의 유틸리티를 실행하는 데 드는 높은 비용 방지)을 기본적으로 지원하는 내가 아는 셸은 tcsh
및 zsh
busybox ksh93
입니다 sh
.
tcsh
스크립팅에는 별로 유용하지 않습니다.
date
ksh93의 경우 내장된 함수를 사용하여 빌드해야 ls
하지만 이런 경우는 드뭅니다. busybox의 경우 애플릿이 자체적으로 재실행되지 않고 애플릿을 sh
호출할 수 있지만 여전히 하위 프로세스에서 실행되며 프로세스를 분기하는 데 비용이 많이 듭니다. stat
Busybox stat
(GNU와 유사한 API가 있음 stat
)도 1초 미만의 정밀도를 지원하지 않습니다. 또한 NUL로 구분된 레코드는 처리할 수 없습니다 busybox sh
.ksh93
NUL로 구분된 파일 경로를 포함하는 파일의 경우 zsh
:list
zmodload zsh/stat || exit
for filepath (${(0)"$(<list)"})
stat -LF %s.%9. -A timestamp +mtime -- $filepath &&
something with $filepath and $timestamp
list
줄당 하나의(줄 바꿈 없음) 파일 경로가 포함된 파일 경로 (0)
의 경우 (f)
.
ksh93
내장 함수 ls
와 list
한 줄에 하나의 파일 경로를 사용하십시오 .
builtin ls || exit
while IFS= read -ru3 filepath; do
timestamp=${ ls -dZ '%(mtime:%s.%N)s' -- "$filepath"; } &&
something with "$filepath" and "$timestamp"
done 3< list
builtin date; date -f %s.%N -m -- "$filepath"
여기에서도 사용할 수 있지만 . 대신 에 stat()
전달하는 것과 같은 작업을 수행한다는 점에 유의하세요 .-L
ls
lstat()
¹ 해당 date
애플릿은 빌드 시 나노초 정밀도를 지원하도록 구성할 수 있지만 기본 빌드에서는 활성화되지 않습니다.
답변2
가장 먼저 떠오르는 것은 다음과 같습니다.
for file in `head -10000 files.txt`; do stat -c "%n %z $file; done
1m3.546s
처음 실행했을 때 10,000개의 파일을 얻었습니다. 후속 실행 에는 0m33.597s
0m22.127s
, 0m25.038s
및0m19.810s
0m25.246s
head
등등에 너무 많은 시간을 낭비 하지 않도록 마감 처리 for
를 stat
약 .echo
0.270s
find
단일 파일에 사용하면 이상하게 느껴지지만 놀랍게도 조금 더 빠르게 실행됩니다.
for file in `head -10000 files.txt`; do find $file -printf '%p %T+\n'; done;
0m29.357s
, 0m20.185s
, 0m30.540s
및 다양한 실행으로 0m31.000s
완료되었습니다 .0m44.836s
그런 다음 세 번째 옵션이 있습니다.
for file in `head -10000 files.txt`; do echo "$file $(date -In -r $file)"; done;
다양한 실행의 완료 시간은 0m25.828s
, 0m12.649s
, 0m23.695s
, 0m12.789s
, 0m43.782s
, 0m28.396s
, 입니다 0m15.800s
.0m15.510s
분명히 더 확실한 결과를 얻으려면 더 많은 실행을 수행하고 다른 시스템을 시도해야 하지만 아마도 이 세 가지 작업에 소요되는 대부분의 시간이 동일하고 꽤 유사한 숫자로 수렴될 것이라는 느낌이 듭니다 date -r
. 이 제한된 샘플에서는 조금 더 빠른 것 같습니다.