Usando find para expandir listas de arquivos em argumentos de modo seguro

Publicado por Paulo em 10/11/2012

[ Hits: 5.345 ]

Blog: http://unixntools.blogspot.com.br/

 


Usando find para expandir listas de arquivos em argumentos de modo seguro



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.

Outras dicas deste autor

Problemas com teclado ABNT2 no QEMU [Resolvido]

Leitura recomendada

GNU parallel - Um ferramenta para execução de processos em paralelo

ITM 6.x - Configurando um Universal Agent(UA) a partir de um script shell coletor

Políticas personalizadas do SELinux

Como contornar o erro -bash: 08: value too great for base (error token is "08")

Monitoramento de link de internet de servidores

  

Comentários
[1] Comentário enviado por julio_hoffimann em 10/11/2012 - 22:06h

Ótima dica Paulo!

Abraço!

[2] Comentário enviado por andrezc em 11/11/2012 - 17:21h

Sensacional!

Obrigado, Paulo!



Contribuir com comentário




Patrocínio

Site hospedado pelo provedor RedeHost.
Linux banner

Destaques

Artigos

Dicas

Tópicos

Top 10 do mês

Scripts