"찾기"로 이름이 변경된 파일을 "찾을"까요? 왜 안 돼?

"찾기"로 이름이 변경된 파일을 "찾을"까요? 왜 안 돼?

대답하는 동안오래된 질문놀랍게도 find아래 예에서는 파일을 여러 번 처리하는 것이 가능한 것 같습니다.

find dir -type f -name '*.txt' \
    -exec sh -c 'mv "$1" "${1%.txt}_hello.txt"' sh {} ';'

또는 더 효율적

find dir -type f -name '*.txt' \
    -exec sh -c 'for n; do mv "$n" "${n%.txt}_hello.txt"; done' sh {} +

이 명령은 파일을 찾고 해당 .txt파일 이름 접미사를 ..txt_hello.txt

이렇게 하면 디렉토리는 *.txt이름이 패턴과 일치하는 새 파일을 축적하기 시작합니다 _hello.txt.

질문: 실제로 처리되지 않는 이유는 무엇입니까 find? 내 경험상 그렇지 않고 우리도 그렇게 되기를 원하지 않기 때문입니다. 그렇게 하면 무한 루프가 발생하기 때문입니다. 그런데 mv대체의 경우도 마찬가지입니다.cp

이것POSIX 표준에 따르면(내 강조)

검색 중인 디렉터리 계층 구조에서 파일이 제거되거나 추가된 경우find검색에 파일을 포함 할지 여부는 지정되지 않았습니다..

새 파일이 포함될지 여부가 지정되지 않았으므로 아마도 더 안전한 접근 방식은 다음과 같습니다.

find dir -type d -exec sh -c '
    for n in "$1"/*.txt; do
        test -f "$n" && mv "$n" "${n%.txt}_hello.txt"
    done' sh {} ';'

여기서는 파일이 아니라 디렉터리를 찾고 있으며 스크립트 for내부의 루프는 sh첫 번째 반복 전에 해당 범위를 한 번 평가하므로 동일한 잠재적인 문제가 발생하지 않습니다.

GNU find매뉴얼은 이를 명시적으로 밝히지 않으며 findOpenBSD 매뉴얼도 마찬가지입니다.

답변1

find디렉토리를 순회하면서 생성된 파일을 찾을 수 있나요 ?

즉, 그렇습니다. 하지만 구현에 따라 다릅니다. 이미 처리된 파일은 무시되도록 조건을 작성하는 것이 좋습니다.

언급했듯이 POSIX는 어느 쪽이든 보장하지 않습니다.readdir()기본 시스템 호출은 보장되지 않습니다.:

가장 최근 호출 opendir()시 또는 그 이후에 파일이 제거되거나 디렉토리에 추가되는 경우 rewinddir()후속 호출이 해당 파일에 대한 항목을 반환하는지 여부 readdir()는 지정되지 않습니다 .


find내 Debian(GNU 찾기, Debian 패키지 버전 ) 4.6.0+git+20161106-2에서 테스트했습니다 . strace작업을 수행하기 전에 전체 디렉터리를 읽는다는 것을 보여줍니다.

소스 코드를 좀 더 살펴보면 GNU find가 gnulib의 일부를 사용하여 디렉토리를 읽는 것처럼 보입니다.gnulib/lib/fts.c( gl/lib/fts.c압축 find패키지):

/* If possible (see max_entries, below), read no more than this many directory
   entries at a time.  Without this limit (i.e., when using non-NULL
   fts_compar), processing a directory with 4,000,000 entries requires ~1GiB
   of memory, and handling 64M entries would require 16GiB of memory.  */
#ifndef FTS_MAX_READDIR_ENTRIES
# define FTS_MAX_READDIR_ENTRIES 100000
#endif

한도를 100으로 변경한 후 실행했습니다.

mkdir test; cd test; touch {0000..2999}.foo
find . -type f -exec sh -c 'mv "$1" "${1%.foo}.barbarbarbarbarbarbarbar"' sh {} \; -print

이름이 다섯 번 변경된 이 파일과 같은 재미있는 결과가 나옵니다.

1046. 바바 바바 바바 바바 바바 바바 바바 바바 바바 바바 바바 바바 바바 바바 바바

분명히 GNU find의 기본 빌드에서 이 효과를 트리거하려면 매우 큰 디렉토리(100,000개 이상의 항목)가 필요하지만 캐싱이 없는 간단한 readdir+process 루프는 더 취약합니다.

이론적으로 이와 같은 간단한 구현은 운영 체제가 항상 파일이 반환되는 순서대로 마지막에 이름이 바뀐 파일을 추가하는 경우 readdir()무한 루프에 빠질 수도 있습니다 .

Linux에서 readdir()C 라이브러리는 getdents()이미 여러 디렉터리 항목을 한 번에 반환하는 시스템 호출을 통해 구현됩니다. 이는 이후 호출이 readdir()삭제된 파일을 반환할 수 있지만 매우 작은 디렉터리의 경우 시작 상태의 스냅샷을 효과적으로 얻을 수 있음을 의미합니다. 다른 시스템은 모르겠네요.

위의 테스트에서는 파일 이름이 그 자리에서 덮어쓰이는 것을 방지하기 위해 의도적으로 더 긴 파일 이름으로 이름을 바꿨습니다. 어쨌든 동일한 길이의 이름 변경에 대한 동일한 테스트가 이중 및 삼중 이름 변경으로도 수행되었습니다. 물론 이것이 중요한지 여부와 방식은 파일 시스템의 내부 구조에 따라 다릅니다.

find이 모든 것을 염두에 두고 표현함으로써 전체 문제를 피하는 것이 현명할 수 있습니다.아니요처리된 파일과 일치합니다. 즉, -name "*.foo"내 예나 ! -name "*_hello.txt"질문의 ​​명령에 추가되었습니다.

관련 정보