1
O primeiro:
for f in *; do
echo "$f"
done
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:
find * -prune | while read f; do
echo "$f"
done
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. Em printf '%s\n'
vez disso, você também poderia ter usado o que, como printf
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. Em zsh
:
for f (*(oN)) printf '%s\n' $f
ou simplesmente:
printf '%s\n' *(oN)
bash
não tem equivalente, tanto quanto eu posso dizer, então você precisaria recorrer find
.
3)
find . ! -name . -prune ! -name '.*' -print0 |
while IFS= read -rd '' f; do
printf '%s\n' "$f"
done
(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 o for
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á uma lstat
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 o lstat
:
find . -maxdepth 1 ! -name '.*' -print0 |
while IFS= read -rd '' f; do
printf '%s\n' "$f"
done
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 de find
concluir a localização de todos os arquivos. Nos sistemas GNU e FreeBSD, você pode stdbuf
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:
find . ! -name . -prune ! -name '.*' -exec some-cmd {} ';'
No echo
entanto, isso é menos eficiente do que fazer o loop no shell, pois o shell terá uma versão interna do echo
while find
e precisará gerar um novo processo e executá /bin/echo
-lo para cada arquivo.
Se você precisar executar vários comandos, poderá:
find . ! -name . -prune ! -name '.*' -exec cmd1 {} ';' -exec cmd2 {} ';'
Mas cuidado, isso cmd2
só é executado se cmd1
for bem-sucedido.
6
Uma maneira canônica de executar comandos complexos para cada arquivo é chamar um shell com -exec ... {} +
:
find . ! -name . -prune ! -name '.*' -exec sh -c '
for f do
cmd1 "$f"
cmd2 "$f"
done' sh {} +
Nesse momento, voltamos a ser eficientes, echo
pois estamos usando sh
o incorporado e a -exec +
versão gera o sh
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 primeiro for i in *
loop simples (embora, como de costume, bash
é muito mais lento que outros shells para isso).
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 .