Esta dica surgiu de uma postagem minha em resposta a uma dúvida publicada no fórum da comunidade "Shell Script" no VOL.
Uma prática muito comum, porém ruim, faz com que encontremos com frequência em scripts algumas construções como as mostradas abaixo.
Exemplo potencialmente nocivo 1:
for arq in /algum_diretorio/*; do
faz_alguma_coisa_com "$arq"
done
Exemplo potencialmente nocivo 2 (pior que o outro, por ser redundante e mais dispendioso):
for arq in `ls /algum_diretorio/*`; do
faz_alguma_coisa_com "$arq"
done
O mal dessas construções está naquele asterisco, pois ele pode expandir para uma quantidade muito grande de entradas, gerando uma linha de comando longa demais para ser aceita pelo shell, como se mostra no exemplo abaixo, tirado de um servidor de e-mail real.
# /bin/rm /var/spool/mqueue/*
bash: /bin/rm: The parameter or environment lists are too long.
Além disso, é teoricamente possível que os nomes dos arquivos contenham espaços (e até mesmo quebras de linhas), que podem ser confundidos se sua expansão não for feita com cuidado.
Para contornar problemas com essas expansões, pode-se usar o
find. Eu imagino três principais casos de uso nos quais os exemplos acima poderiam ser reescritos com mais segurança e confiabilidade, dependendo de como seja o "faz_alguma_coisa_com".
CASO 1: Se o "faz_alguma_coisa_com" for um simples comando e puder ser aplicado a vários arquivos ao mesmo tempo:
# find /algum_diretorio -maxdepth 1 -mindepth 1 -print0 | xargs -0 comando
CASO 2: Se o "faz_alguma_coisa_com" for um simples comando mas tiver de ser aplicado a um arquivo de cada vez:
find /algum_diretorio -maxdepth 1 -mindepth 1 -exec comando "{}" ";"
CASO 3: Se o "faz_alguma_coisa_com" for uma sequência de comandos, pode-se fazer um script com esses comandos e chamar tal script, como se fosse um dos casos acima, ou embutir os múltiplos comandos num comando só, via shell, como mostrado abaixo, neste exemplo que mostra a correspondência dos nomes dos arquivos com letras maiúsculas:
find /algum_diretorio -maxdepth 1 -mindepth 1 -print0 | xargs -0 \
sh -c 'for a in "$@"; do b="`echo \"$a\" | tr \[a-z\] \[A-Z\]`"; echo "$a --> $b"; done'
Nos exemplos acima, eu supus o find (e seu parceiro xargs) da GNU. Outros sistemas, como Solaris ou AIX, têm um find que não dispõe de muitas das operações da versão da GNU, incluindo -print0, -mindepth e -maxdepth, usados nos exemplos.
A ausência mais grave é a de -print0, o que impede tratar arquivos que contenham quebras de linha no nome. Felizmente, tais arquivos são raros. Mas os operadores disponíveis em versões menos elaboradas do find ainda permitem conseguir efeitos como os mostrados acima, mas exigem expressões mais elaboradas. Mesmo assim, é bom levar em consideração essas diferenças de versões em scripts que tenham de rodar em diferentes sistemas (ou versões de um mesmo sistema). Assim sendo, o find mostrado no exemplo do "CASO 1", acima, teria de ser reescrito numa forma parecida com a que se segue:
find /algum_diretorio \( -name etc -o \( -type d -print -prune \) \) -o -print | xargs comando
Para sua leitura de aprendizado, recomendo ler as manpages de find e xargs.