Ao percorrer os arquivos, há duas maneiras:
use um
for
loop:for f in *; do echo "$f" done
use
find
:find * -prune | while read f; do echo "$f" done
Supondo que esses dois loops encontrarão a mesma lista de arquivos, quais são as diferenças nessas duas opções em desempenho e manipulação?
bash
shell-script
performance
rubo77
fonte
fonte
find
não abre os arquivos que encontra. A única coisa que posso ver mordendo você aqui em relação a um grande número de arquivos é ARG_MAX .read f
os nomes dos arquivos serão alterados à medida que forem lidos (por exemplo, nomes com espaços em branco à esquerda). Tambémfind * -prune
parece ser uma maneira muito complicada de dizer simplesmentels -1
sim?find .
, nãofind *
.ls -l
é uma má ideia. Mas analisarls -1
(isso1
não é uml
) não é pior do que analisarfind * -prune
. Ambos falham em arquivos com novas linhas nos nomes.Respostas:
1
O primeiro:
falha para arquivos chamados
-n
,-e
e variantes como-nene
e com algumas implementações festança, com nomes de arquivos que contém barras invertidas.O segundo:
falha para ainda mais casos (arquivos chamados
!
,-H
,-name
,(
, nomes de arquivos que começam ou terminam com espaços em branco ou contêm caracteres de nova linha ...)É o shell que se expande
*
,find
não faz nada além de imprimir os arquivos que recebe como argumentos. Emprintf '%s\n'
vez disso, você também poderia ter usado o que, comoprintf
está embutido, evitaria o erro potencial de muitos argumentos .2)
A expansão de
*
é classificada, você pode torná-la um pouco mais rápida se não precisar da classificação. Emzsh
:ou simplesmente:
bash
não tem equivalente, tanto quanto eu posso dizer, então você precisaria recorrerfind
.3)
(acima, usando uma
-print0
extensão não padrão do GNU / BSD ).Isso ainda envolve gerar um comando find e usar um
while read
loop lento ; portanto, provavelmente será mais lento do que usar ofor
loop, a menos que a lista de arquivos seja enorme.4)
Além disso, ao contrário da expansão de curinga do shell,
find
fará umalstat
chamada do sistema em cada arquivo, portanto, é improvável que a não classificação compense isso.Com o GNU / BSD
find
, isso pode ser evitado usando sua-maxdepth
extensão que acionará uma otimização, salvando olstat
:Como
find
inicia a saída de nomes de arquivos assim que os encontra (exceto para o buffer de saída stdio), onde pode ser mais rápido é se o que você faz no loop é demorado e a lista de nomes de arquivos é mais que um buffer stdio (4 / 8 kB). Nesse caso, o processamento no loop será iniciado antes defind
concluir a localização de todos os arquivos. Nos sistemas GNU e FreeBSD, você podestdbuf
fazer isso acontecer mais cedo (desativando o buffer do stdio).5)
A maneira POSIX / standard / portátil de executar comandos para cada arquivo
find
é usar o-exec
predicado:No
echo
entanto, isso é menos eficiente do que fazer o loop no shell, pois o shell terá uma versão interna doecho
whilefind
e precisará gerar um novo processo e executá/bin/echo
-lo para cada arquivo.Se você precisar executar vários comandos, poderá:
Mas cuidado, isso
cmd2
só é executado secmd1
for bem-sucedido.6
Uma maneira canônica de executar comandos complexos para cada arquivo é chamar um shell com
-exec ... {} +
:Nesse momento, voltamos a ser eficientes,
echo
pois estamos usandosh
o incorporado e a-exec +
versão gera osh
mínimo possível.7)
Nos meus testes em um diretório com 200.000 arquivos com nomes abreviados no ext4, o
zsh
(parágrafo 2.) é de longe o mais rápido, seguido pelo primeirofor i in *
loop simples (embora, como de costume,bash
é muito mais lento que outros shells para isso).fonte
!
faz no comando find?!
é para negação.! -name . -prune more...
fará-prune
(emore...
como-prune
sempre retorna true) para todos os arquivos, exceto.
. Portanto, ele funcionarámore...
em todos os arquivos incluídos.
, mas excluirá.
e não descerá em subdiretórios de.
. Portanto, é o equivalente padrão do GNU-mindepth 1 -maxdepth 1
.Eu tentei isso em um diretório com 2259 entradas e usei o
time
comandoA saída de
time for f in *; do echo "$f"; done
(menos os arquivos!) É:A saída de
time find * -prune | while read f; do echo "$f"; done
(menos os arquivos!) É:Eu executei cada comando várias vezes, para eliminar erros de cache. Isso sugere que mantê-lo
bash
(para i em ...) é mais rápido do que usarfind
e canalizar a saída (parabash
)Apenas para completar, deixei cair o cano
find
, já que no seu exemplo, é totalmente redundante. A saída de justfind * -prune
é:Além disso,
time echo *
(a saída não é separada por nova linha, infelizmente):Neste ponto, suspeito que o motivo
echo *
é mais rápido: não está produzindo tantas novas linhas; portanto, a saída não está rolando tanto. Vamos testar ...rendimentos:
enquanto
time find * -prune > /dev/null
produz:e
time for f in *; do echo "$f"; done > /dev/null
produz:e finalmente:
time echo * > /dev/null
produz:Algumas das variações podem ser explicadas por fatores aleatórios, mas parece claro:
for f in *; do ...
é mais lento do quefind * -prune
, por si só, mas para as construções acima envolvendo tubos, é mais rápido.Além disso, como um aparte, ambas as abordagens parecem manipular nomes com espaços muito bem.
EDITAR:
Horários para
find . -maxdepth 1 > /dev/null
vs.find * -prune > /dev/null
:time find . -maxdepth 1 > /dev/null
:find * -prune > /dev/null
:Então, conclusão adicional:
find * -prune
é mais lento do quefind . -maxdepth 1
- no primeiro, o shell está processando um glob e construindo uma linha de comando (grande) parafind
. NB:find . -prune
retorna apenas.
.Mais testes
time find . -maxdepth 1 -exec echo {} \; >/dev/null
:Conclusão:
fonte
find * -prune | while read f; do echo "$f"; done
possui o tubo redundante - tudo o que o tubo está fazendo é produzir exatamente o que é produzidofind
por si próprio. Sem um pipe, seria simplesmentefind * -prune
O pipe é redundante apenas especificamente porque a coisa do outro lado do pipe simplesmente copia stdin para stdout (na maior parte). É um no-op caro. Se você quiser fazer coisas com a saída de find, além de cuspir novamente, é diferente.*
. Como o BitsOfNix afirmou: Eu ainda sugiro fortemente não usar*
e.
emfind
vez disso.find . -prune
é mais rápido, porquefind
estará lendo uma entrada de diretório literalmente, enquanto o shell estará fazendo o mesmo, potencialmente compatível com a glob (pode ser otimizada para*
), e então construindo a grande linha de comando parafind
.find . -prune
imprime apenas.
no meu sistema. Quase não funciona. Não é o mesmofind * -prune
que mostra todos os nomes no diretório atual. Um simplesread f
irá modificar nomes de arquivos com espaços à esquerda.Eu iria definitivamente com o find, embora eu alterasse o seu achado para exatamente isso:
Em termos de desempenho,
find
é muito mais rápido, dependendo das suas necessidades, é claro. O que você tem atualmentefor
exibirá apenas os arquivos / diretórios no diretório atual, mas não o conteúdo dos diretórios. Se você usar o find, também mostrará o conteúdo dos subdiretórios.Eu digo que o find é melhor, pois com o seu
for
o*
arquivo terá que ser expandido primeiro e eu tenho medo de que, se você tiver um diretório com uma quantidade enorme de arquivos, ele possa dar uma lista de argumentos de erro muito longa . O mesmo vale parafind *
Como exemplo, em um dos sistemas que atualmente uso, existem alguns diretórios com mais de 2 milhões de arquivos (<100k cada):
fonte
-prune
para tornar os dois exemplos mais parecidos. e eu prefiro o tubo com enquanto isso, é mais fácil de aplicar mais comandos no circuitoé um uso inútil de
find
- O que você está dizendo é efetivamente "para cada arquivo no diretório (*
), não encontra nenhum arquivo. Além disso, não é seguro por vários motivos:-r
opçãoread
. Este não é um problema com ofor
loop.for
loop.find
É difícil lidar com qualquer nome de arquivo , portanto, você deve usar afor
opção de loop sempre que possível por esse motivo. Além disso, a execução de um programa externo comofind
, em geral, será mais lenta do que a execução de um comando de loop internofor
.fonte
find
's-print0
nemxargs
'-0
são POSIX compatível, e você não pode colocar comandos arbitrários emsh -c ' ... '
(aspas simples não pode ser escapado dentro de aspas simples), por isso não é tão simples.Mas somos otários por questões de desempenho! Essa solicitação de experiência faz pelo menos duas suposições que a tornam terrivelmente válida.
A. Suponha que eles encontrem os mesmos arquivos ...
Bem, eles vão encontrar os mesmos arquivos em primeiro lugar, porque eles são ambos iteração sobre o mesmo glob, a saber
*
. Masfind * -prune | while read f
sofre de várias falhas que tornam possível que não encontre todos os arquivos que você espera:find
implementações faz, mas ainda assim, você não deve confiar nisso.find *
pode quebrar quando você bateARG_MAX
.for f in *
, porqueARG_MAX
se aplica aexec
, não embutidos.while read f
pode romper com os nomes de arquivos que começam e terminam em espaço em branco, que será removido. Você pode superar isso comwhile read
seu parâmetro padrãoREPLY
, mas isso ainda não ajudará quando se trata de nomes de arquivos com novas linhas.B.
echo
. Ninguém fará isso apenas para ecoar o nome do arquivo. Se você quiser isso, basta fazer um destes:O
while
canal para o loop aqui cria um subshell implícito que fecha quando o loop termina, o que pode não ser adequado para alguns.Para responder à pergunta, aqui estão os resultados em um diretório meu que possui 184 arquivos e diretórios.
fonte
$ ps ax | grep bash 20784 pts/1 Ss 0:00 -bash 20811 pts/1 R+ 0:00 grep bash $ while true; do while true; do while true; do while true; do while true; do sleep 100; done; done; done; done; done ^Z [1]+ Stopped sleep 100 $ bg [1]+ sleep 100 & $ ps ax | grep bash 20784 pts/1 Ss 0:00 -bash 20924 pts/1 S+ 0:00 grep bash
find *
não funcionará corretamente se*
produzir tokens que parecem predicados e não caminhos.Você não pode usar o
--
argumento usual para corrigir isso, porque--
indica o fim das opções, e as opções de localização vêm antes dos caminhos.Para corrigir esse problema, você pode usar
find ./*
. Mas então não está produzindo exatamente as mesmas seqüências de caracteresfor x in *
.Observe que
find ./* -prune | while read f ..
, na verdade, não usa a funcionalidade de digitalização defind
. É a sintaxe globbing./*
que realmente atravessa o diretório e gera nomes. Em seguida, ofind
programa precisará executar pelo menos umastat
verificação em cada um desses nomes. Você tem a sobrecarga de iniciar o programa e fazê-lo acessar esses arquivos e, em seguida, executar E / S para ler sua saída.É difícil imaginar como isso poderia ser tudo menos menos eficiente que
for x in ./* ...
.fonte
Bom para iniciantes
for
é uma palavra-chave shell, incorporada ao Bash, enquantofind
é um executável separado.O
for
loop encontrará apenas os arquivos do personagem globstar quando se expandir, e não recursará em nenhum diretório encontrado.A busca por outro lado também receberá uma lista expandida pela globstar, mas encontrará recursivamente todos os arquivos e diretórios abaixo dessa lista expandida e direcionará cada um deles para o
while
loop.Ambas as abordagens podem ser consideradas perigosas no sentido de que não tratam caminhos ou nomes de arquivos que contêm espaços.
É tudo o que consigo pensar em comentar essas duas abordagens.
fonte
Se todos os arquivos retornados por find puderem ser processados por um único comando (obviamente não aplicável ao seu exemplo de eco acima), você poderá usar xargs:
fonte
Por anos eu tenho usado isso: -
procurar certos arquivos (por exemplo, * .txt) que contenham um padrão que o grep possa procurar e canalizá-lo mais para que ele não role para fora da tela. Às vezes, uso o pipe >> para gravar os resultados em outro arquivo que posso ver mais tarde.
Aqui está uma amostra do resultado: -
fonte