quantas invocações de comando com o comando find -exec {} +

Sep 16 2020

encontrar estados da página de manual:

   -exec command {} +
          This variant of the -exec action runs the specified command on the selected files,
          but the command line is built by appending each selected file name at the end;
          the total number of invocations of the  command  will  be
          much  less than the number of matched files.

Sempre pensei que isso faria com que fosse findexecutado commandexatamente uma vez. Existe uma maneira de saber quantas vezes o comando é chamado?

Observe que isso é importante como se fosse apenas uma vez, como eu pensei, então há o perigo de construir uma lista de argumentos muito grande commandpara manipular; mas se find acabar dividindo as invocações (algo semelhante a parallel), então isso seria mitigado.

Respostas

3 LSerni Sep 16 2020 at 05:39

O buffer usado depende da findversão e parece ter cerca de 256Kb de tamanho na caixa SuSE que estou disponível aqui.

Então, para calcular quantas vezes o "comando" é chamado, você precisa saber o comprimento de cada caminho de arquivo encontrado, então seria (aproximadamente) a soma de todos os comprimentos do caminho aumentados por um para o espaço de divisão, menos o comando em si, dividido pelo tamanho do buffer.

Por exemplo, você encontra 20.000 arquivos com um comprimento médio de caminho de 200 bytes, ou seja, 4.020.000 bytes, dividido por 256 Kb é 15,33, então você precisaria de cerca de 16 chamadas.

O cálculo exato seria um pouco mais complexo para levar em conta a necessidade de não quebrar um caminho de arquivo entre duas chamadas consecutivas, mas você obtém um valor aproximado.

Veja aqui um segmento (com código-fonte) onde o tamanho é relatado como 32 KB e considerado desnecessariamente baixo (agora que penso nisso, talvez o meu próprio find esteja usando os limites de sistema. Eu não experimentei); coreutilsA versão de, por inferência, parece ser quatro vezes maior, ou seja, 128 Kb .

2 vonbrand Sep 16 2020 at 08:03

O limite dependerá find(1)dos buffers de e do que o comando trata (dependente do kernel). A menos que a última porcentagem de desempenho seja crítica, os padrões em seu sistema devem ser adequados.

Se você está preocupado com o desempenho, considere todo o sistema que faz isso e meça onde estão os gargalos. Provavelmente, você ficará muito surpreso com suas descobertas. Bentley, em seu delicioso "Escrevendo programas eficientes" (Prentice-Hall, 1982), infelizmente há muito tempo esgotado, compartilha várias histórias de "otimizações" cuidadosas que tornaram "mais rápido" código essencialmente não utilizado e fatalmente cheio de erros ou otimizou o loop ocioso de um sistema operacional depois de medir que ocupou uma fração substancial do tempo do computador. As pessoas são notoriamente ruins em adivinhar onde residem as ineficiências. Além disso, compensa muito mais trabalhar nos níveis superiores (arquitetura do sistema, organização geral, algoritmos e estruturas de dados) do que nos detalhes.

2 KamilMaciorowski Sep 16 2020 at 08:57

Nota preliminar: o manual e sua pergunta usam commandpara denotar o comando, mas como POSIX define um utilitário literalmente nomeado command, minha resposta usará cmmnd.


Se você deseja realmente executar cmmnd(s) e apenas contar o número de invocações (para saber após o términofind ), crie um wrapper que faça algo que você possa contar (por exemplo, imprime em stderr, imprime em um arquivo de log, bipes) e, eventualmente, executa o cmmnd. Exemplo:

#!/bin/sh
echo "invoking cmmnd" >&2
cmmnd "$@"

Em seguida, use o em wrappervez do cmmndinterior find.

O Note findusará /absolute/path/to/wrapperao criar comandos que não sejam muito longos; então o invólucro será usado /absolute/path/to/cmmnd. Se o último for mais longo, então algumas linhas de comando que o contêm podem acabar sendo muito longas. Portanto, essa abordagem não é tão direta quanto desejamos. Você pode estender o caminho anterior fornecendo-o findliteralmente com barras adicionais (por exemplo /absolute/path/to/////wrapper).


Agora eu suponho que você queira saber o número antes de decidir correr cmmnd(s). Como em um caso em que chamar cmmndduas vezes é uma coisa ruim (por qualquer motivo) e você quer ter certeza de findque executará exatamente uma vez.

O wrapper acima com cmmnd "$@"comentário pode ser usado. Abaixo estão algumas outras idéias (no final, não tão diferentes).

Vamos supor que você queira fazer isso:

find . -exec cmmnd … {} +

(onde …denota argumentos constantes). Descubra qual cmmndé realmente o caminho absoluto . Por exemplo, pode ser /bin/cmmnd. Em seguida, execute algo assim:

find . -exec /aaa/zzzzz … {} +

onde /aaa/zzzzzé um comando inexistente cujo nome tem o mesmo comprimento que /bin/cmmnd. Agora find, as linhas de comando serão construídas com /aaa/zzzzzo mesmo comprimento que as linhas de comando /bin/cmmnd. Você vai ter

find: '/aaa/zzzzz': No such file or directory

uma ou mais vezes. Conte-os para obter o número que deseja. Esta abordagem simples:

find . -exec /aaa/zzzzz … {} + 2>&1 | wc -l

não é o melhor porque tambémfind pode imprimir, por exemplo, alguns arquivos que encontrar. Mas se você criar um executável válido que imprima exatamente uma linha (pode ser uma linha vazia), isso deve funcionar:permission denied/aaa/zzzzz

find . -exec /aaa/zzzzz … {} + | wc -l

Outra melhoria é nomear a ferramenta /a(em vez de /aaa/zzzzz) e chamá-la como /////aou /////////////////aetc., dependendo do comprimento de que você precisa. Exemplo:

find . -exec /////////a … {} + | wc -l

Para ser completo, apode ser assim:

#!/bin/sh
echo

É quase como nosso invólucro sem cmmnd "$@", ele usa o stdout embora.

Notas:

  • O número exato de /caracteres não é crítico. Um erro de poucos não mudará drasticamente o resultado . Se precisar de um resultado de estimativa , você pode usar cegamente ///////////aou algo assim, a menos que o caminho para o cmmndseja excepcionalmente longo. Observe que usar exatamente /afornecerá o limite inferior.

  • Na prática, você costuma fazer outros testes antes -exec cmmnd … {} +. Se você substituir cmmndpor /////////aou assim, os outros testes ainda serão realizados. Você não deve omiti-los porque eles decidem quais caminhos chegarão -execem primeiro lugar. Mas se os testes fazem ou mudam algo, pode ser que realizá-los sem o cmmndseja errado.

    Por exemplo, você pode querer excluir arquivos com -delete -exec cmmnd … {} +, onde cmmndgera um relatório sobre os arquivos que foram excluídos. Neste caso, o uso de /////////aexcluirá arquivos sem gerar nenhum relatório. Portanto, pense antes de agir.

  • Certifique-se de testes / ações / qualquer outra coisa que -exec /////////a … {} +não seja imprimir nada no stdout. Ou deixe /ausar algum outro canal.

  • Processar a (s) árvore (s) de diretório fornecida (s) e realizar (outros) testes pode demorar um pouco, mesmo sem cmmnd(s).

ilkkachu Sep 17 2020 at 00:58

Bem, o texto padrão diz:

O tamanho de qualquer conjunto de dois ou mais nomes de caminhos deve ser limitado de forma que a execução do utilitário não faça com que o limite de {ARG_MAX} do sistema seja excedido.

Portanto, não deve construir uma lista de argumentos muito grande para ser executada. Isso anularia o objetivo de um recurso como este.

Quantas invocações ele faz exatamente depende da implementação e provavelmente é algo com o qual você não deve se preocupar muito. O padrão promete que as invocações da mesma -execcláusula não se sobrepõem, o que pode ser relevante para a correção se você executar algo que tenha um estado externo.

No entanto, no Linux, o tamanho máximo real dos argumentos da linha de comando é baseado no tamanho da pilha e pode ser alterado indiretamente com ulimit -s. E parece que, ao contrário xargs, por exemplo , do findno meu Debian e Ubuntu não verifica o limite em tempo de execução, então é teoricamente possível ter problemas.

$ mkdir bar $ touch bar/{00000..99999}
$ ulimit -Ss 512 $ getconf ARG_MAX
131072
$ find bar -type f -exec sh ./args.sh {} +
find: ‘sh’: Argument list too long
find: ‘sh’: Argument list too long
...

No entanto, o padrão para ulimit -sé 8192, portanto, provavelmente você não terá esse problema, exceto em um sistema muito restrito.