Bash의 파일에서 마지막으로 수정된 TS를 얻는 가장 빠른 방법은 무엇입니까?

Bash의 파일에서 마지막으로 수정된 TS를 얻는 가장 빠른 방법은 무엇입니까?

거대한 파일 시스템에 수십만 개의 파일 목록이 있는 경우 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내장 함수 가 zshGNU 또는 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는 findstdin을 사용하여 파일 목록을 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/$filepathzshbash$IFS${filepath:-/}

이는 read입력을 한 번에 한 바이트씩 읽어야 하기 때문에 본질적으로 비효율적입니다. 바라보다쉘 루프를 사용하여 텍스트를 처리하는 것이 왜 나쁜 습관으로 간주됩니까?더 알아보기. 성능이 문제라면 적절한 프로그래밍 언어를 사용하는 것이 더 나을 것입니다.

파일 수정 시간 검색(및 각 파일에 대해 별도의 유틸리티를 실행하는 데 드는 높은 비용 방지)을 기본적으로 지원하는 내가 아는 셸은 tcshzshbusybox ksh93입니다 sh.

tcsh스크립팅에는 별로 유용하지 않습니다.

dateksh93의 경우 내장된 함수를 사용하여 빌드해야 ls하지만 이런 경우는 드뭅니다. busybox의 경우 애플릿이 자체적으로 재실행되지 않고 애플릿을 sh호출할 수 있지만 여전히 하위 프로세스에서 실행되며 프로세스를 분기하는 데 비용이 많이 듭니다. statBusybox 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내장 함수 lslist한 줄에 하나의 파일 경로를 사용하십시오 .

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()전달하는 것과 같은 작업을 수행한다는 점에 유의하세요 .-Llslstat()


¹ 해당 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.038s0m19.810s0m25.246s

head등등에 너무 많은 시간을 낭비 하지 않도록 마감 처리 forstat약 .echo0.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. 이 제한된 샘플에서는 조금 더 빠른 것 같습니다.

관련 정보