Iremos alguma vez "encontrar" arquivos cujos nomes são alterados por "find"? Por que não?

6

Ao responder uma pergunta mais antiga me pareceu que find , no exemplo a seguir, potencialmente processaria arquivos várias vezes:

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

ou o mais eficiente

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

O comando encontra .txt arquivos e altera seu sufixo de nome de arquivo de .txt para _hello.txt .

Ao fazer isso, os diretórios começarão a acumular novos arquivos cujos nomes correspondem ao padrão *.txt , ou seja, esses arquivos _hello.txt .

Pergunta: Por que eles não são realmente processados por find ? Porque na minha experiência eles não são, e nós não queremos que eles sejam como isso introduziria uma espécie de loop infinito. Este é também o caso com mv substituído por cp , a propósito.

O padrão POSIX diz (minha ênfase)

If a file is removed from or added to the directory hierarchy being searched it is unspecified whether or not find includes that file in its search.

Como não é especificado se novos arquivos serão incluídos, talvez uma abordagem mais segura seja

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

Aqui, não procuramos por arquivos, mas por diretórios, e o for do script interno sh avalia seu intervalo uma vez antes da primeira iteração, portanto, não temos o mesmo problema em potencial. / p>

O manual GNU find não diz explicitamente nada sobre isso e nem o manual do OpenBSD find .

    
por Kusalananda 13.02.2018 / 19:56

1 resposta

6

Pode find encontrar arquivos que foram criados enquanto estava andando no diretório? Sim, a menos que a implementação específica evite explicitamente isso lendo a lista de arquivos antes de fazer qualquer coisa. A definição POSIX de readdir() não garante:

If a file is removed from or added to the directory after the most recent call to opendir() or rewinddir(), whether a subsequent call to readdir() returns an entry for that file is unspecified.

Eu testei o find no meu Debian (GNU find, versão do pacote Debian 4.6.0+git+20161106-2 ). strace mostrou que leu o diretório completo antes de fazer qualquer coisa.

Navegar no código-fonte um pouco mais faz parecer que o GNU find usa partes do gnulib para ler os diretórios, e existe isso em gnulib / lib / fts.c ( gl/lib/fts.c no find tarball):

/* 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

Alterei esse limite para 100 e fiz

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

resultando em resultados hilariantes como este arquivo (ele foi renomeado cinco vezes):

1046.barbarbarbarbarbarbarbar.barbarbarbarbarbarbarbar.barbarbarbarbarbarbarbar.barbarbarbarbarbarbarbar.barbarbarbarbarbarbarbar

Obviamente, um diretório muito grande (mais de 100.000 entradas) seria necessário para disparar esse efeito em uma construção padrão do GNU find, mas um loop de processo readdir + sem cache seria ainda mais vulnerável.

No entanto, no Linux, o readdir() na biblioteca C é implementado por meio da chamada de sistema getdents() , que retorna várias entradas de diretório de uma só vez. O que significa que as chamadas posteriores para readdir() podem retornar arquivos que já foram removidos, mas, para diretórios muito pequenos, você obteria efetivamente um instantâneo do estado inicial. Eu não sei sobre outros sistemas.

Eu fiz as renomeações para um nome de arquivo maior de propósito: para evitar que o nome fosse substituído no local. Não importa, o mesmo teste em uma renomeação de mesmo tamanho também fez renomeações duplas e triplas. Se e como isso importa, é claro, dependerá dos componentes internos do sistema de arquivos.

Pode ser inteligente evitar isso fazendo com que a expressão find não corresponda aos arquivos que já foram processados. Isto é, para adicionar -name "*.foo" no exemplo.

    
por 13.02.2018 / 22:01

Tags