1.
O primeiro:
for f in *; do echo "$f" done
falha em arquivos chamados -n
, -e
e variantes como -nene
e com algumas implementações do bash, com nomes de arquivos contendo barras invertidas.
O segundo:
find * -prune | while read f; do echo "$f" done
falha em 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 expande *
, find
não faz nada além de imprimir os arquivos que recebe como argumentos. Você poderia também ter usado printf '%s\n'
em vez disso, pois como printf
está embutido, também evitaria o erro potencial muitos args .
2.
A expansão de *
está 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, pelo que eu posso dizer, então você precisa recorrer a find
.
3.
find . ! -name . -prune ! -name '.*' -print0 |
while IFS= read -rd '' f; do
printf '%s\n' "$f"
done
(acima usando uma extensão não padrão do GNU / BSD -print0
).
Isso ainda envolve gerar um comando find e usar um loop while read
lento, então provavelmente será mais lento do que usar o loop for
, a menos que a lista de arquivos seja enorme.
4.
Além disso, ao contrário da expansão com curinga, find
fará uma chamada lstat
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 extensão -maxdepth
, que acionará uma otimização para salvar 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 eles são encontrados (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 do que um buffer stdio (4/8 kB). Nesse caso, o processamento dentro do loop será iniciado antes que find
tenha encontrado todos os arquivos. Nos sistemas GNU e FreeBSD, você pode usar stdbuf
para fazer com que isso aconteça mais cedo (desabilitando o buffer stdio).
5.
A maneira POSIX / padrão / portátil de executar comandos para cada arquivo com find
é usar o predicado -exec
:
find . ! -name . -prune ! -name '.*' -exec some-cmd {} ';'
No caso de echo
, é menos eficiente do que fazer o loop no shell, pois o shell terá uma versão incorporada de echo
, enquanto find
precisará gerar um novo processo e executar /bin/echo
nele para cada arquivo.
Se você precisar executar vários comandos, faça o seguinte:
find . ! -name . -prune ! -name '.*' -exec cmd1 {} ';' -exec cmd2 {} ';'
Mas tenha em atenção que 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 {} +
Dessa vez, voltamos a ser eficientes com echo
, pois estamos usando sh
e a versão -exec +
gera o mínimo de sh
possível.
7.
Em meus testes em um diretório com 200.000 arquivos com nomes curtos no ext4, o zsh
one (parágrafo 2.) é de longe o mais rápido, seguido pelo primeiro loop for i in *
simples (embora, como de costume, bash
é muito mais lento que outros shells para isso).