De acordo com as Leis 12.965/2014 e 13.709/2018, que regulam o uso da Internet e o tratamento de dados pessoais no Brasil, ao me inscrever na newsletter do portal DICAS-L, autorizo o envio de notificações por e-mail ou outros meios e declaro estar ciente e concordar com seus Termos de Uso e Política de Privacidade.


Procurando e copiando arquivos com seus caminhos originais

Colaboração: Paulo Roberto Bagatini

Data de Publicação: 30 de março de 2015

Ao longo dos anos fui amontoando alguns arquivos .reg com configuração personalizada do PuTTY. Não é muita coisa, mas está tudo espalhado em um HD USB com mais de 1 TiB de dados e não há a menor chance de lembrar quantos são, em que diretórios estão e como se chamam; só sei que provavelmente têm "putty" no nome, alguma alternância entre maiúsculo e minúsculo, e a extensão é "reg".

Como achá-los e copiá-los automaticamente para um único diretório, cuidando para não sobrescrever os que tiverem o mesmo nome? Bom, se cada arquivo for copiado com seu caminho original para dentro do novo diretório "pai", certamente não haverá nenhuma sobreposição.

A primeira técnica será a forma rápida, limpa e suficientemente nerd de fazer isso (no Unix, MacOSX e Windows com CygWin): com um singelo cp associado a um find.

Primeiro preparamos adequadamente o ambiente:

  $ padrao="*putty*.reg" 
  $ pesquisa="/longa/raiz/comum/a/todos/os/arquivos/e/que/nao/interessa/manter/" 
  $ destino="/diretorio/para/os/arquivos/encontrados/" 
  $ mkdir -p "$destino" 
  $ alias time="/usr/bin/time -f %E" 

Então executamos o comando:

  $ ( cd "$pesquisa"; cp -va --parents $(time find . -type f -iname "$padrao") "$destino" ) 

O alias do comando time é apenas um plus a mais :-p para saber em um formato user friendly quanto tempo a pesquisa do find demorou.

Beleza, funcionou! Em 17 min varreu 1.3 TiB e copiou 28 arquivos.

Mas como nem tudo são flores no mundo encantado da TI, esse método tem pelo menos dois pontos de falha em potencial.

Um deles é o tamanho da entrada gerada pela saída do find. No meu Linux 3.16.0 em arquitetura 32 bits, se todos os argumentos do cp resultarem em uma string com mais de 2.097.152 (=2^21, bc -l <<< "scale=5;l(2097152)/l(2)" ;-) caracteres, ele vai reclamar:

  $ ( cd "$pesquisa"; find . -type f -iname "$padrao" | wc -c ) # mostra com quantos bytes fica a lista de argumentos resultante do padrão 
  2981219 
  $ ( cd "$pesquisa"; cp -va --parents $(find . -type f -iname "$padrao") "$destino" ) 
  bash: /bin/cp: Lista de argumentos muito longa 
  $ # ou, em inglês: 
  $ ( cd "$pesquisa"; LANGUAGE=C cp -va --parents $(find . -type f -iname "$padrao") "$destino" ) 
  bash: /bin/cp: Argument list too long 

Podemos até não gostar dessa limitação de 2 MiB, mas lembremos que o "comprimento" dos comandos no terminal do Windows não passa de míseros (8Ki-1)B >:-) (support.microsoft.com/pt-br/kb/830473)

O tamanho que a lista de argumentos pode ter no Unix está definido na constante ARG_MAX e, pelo menos no Linux, pode ser consultado com o comando getconf:

  $ getconf ARG_MAX 
  2097152 
  $ getconf -a            # todos as constantes 
  $ getconf -a | grep MAX # constantes explicitamente relacionadas a limites máximos, vale a pena uma olhada 

Alguma informação sobre o ARG_MAX está disponível em in-ulm.de/~mascheck/various/argmax/, inclusive uma tabela comparando seu valor em vários sistemas operacionais.

Ainda que o processo sobreviva ao risco do tamanho final da lista de parâmetros do cp, pode não escapar da existência de diretórios ou arquivos com espaço no nome (/longa/raiz/comum a/todos/os/arquivos/e/que/nao/interessa/manter/configs/putty fileserver.reg):

  cp: impossível obter estado de  /longa/raiz/comum : Arquivo ou diretório não encontrado 
  cp: impossível obter estado de  a/todos/os/arquivos/e/que/nao/interessa/manter/configs/putty : Arquivo ou diretório não encontrado 
  cp: impossível obter estado de  fileserver.reg : Arquivo ou diretório não encontrado 

Para contornar esse outro ponto de falha, precisamos de alguma técnica que permita deixar entre aspas o nome com caminho de cada arquivo, ou o que quer que o represente.

Ignorando a alternativa óbvia de iterar a saída do find em um loop, uma segunda técnica permite driblar simultaneamente o problema do tamanho da lista de argumentos e dos espaços em nomes, de pelo menos duas formas:

  • executando o cp de dentro do find
  • passando o resultado do find via xargs para o cp
  $ ( cd "$pesquisa"; time find . -type f -iname "$padrao" -exec cp -va --parents "{}" "$destino" \; ) 
  $ ( cd "$pesquisa"; time find . -type f -iname "$padrao" | xargs -I+ cp -va --parents "+" "$destino" ) 

Por fim, uma terceira técnica combina o comando find com duas chamadas do comando tar. Tem características interessantes:

  • é possível compactar o stream antes da cópia, usando parâmetros do próprio tar ou alguma ferramenta auxiliar;
  • aliando o comando ssh, é possível transferência criptografada do stream através pela rede;
  • uma chave privada sem passphrase para o ssh torna desnecessária a interação humana para autenticação por senha;
  • além da compressão por ferramentas auxiliares, o próprio ssh pode efetuar compressão dos dados em seu tráfego. De qualquer forma, é basicamente em redes lentas que ela será útil de fato.

Usando ssh, é especialmente importante verificar se temos as permissões necessárias para criar o diretório $destino.

O tar pode ser usado com xargs para contornar o estouro do tamanho da lista de argumentos ou pode usar um arquivo com a lista de entrada a ser manipulada. Para evitar a criação explícita de um arquivo intermediário, podemos aplicar substituição de processos (apoie.org/JulioNeves/PapoXI.htm#Substitui_o_de_processos, wikipedia.org/wiki/Process_substitution).

  $ ( cd "$pesquisa"; time find . -type f -iname "$padrao" -print0 | xargs -0 tar cf - ) | tar xvf - -C "$destino" 
  
  $ ( cd "$pesquisa"; tar  cf - -T <(time find . -type f -iname "$padrao") ) | tar xvf - -C "$destino" 
  
  $ chave="/arquivo/com/chave/privada/sem/passphrase" 
  $ remoto="user@host" 
  
  $ # compactação gzip 
  $ ( cd "$pesquisa"; tar  cf - -T <(time find . -type f -iname "$padrao") ) | ssh -CTi "$chave" $remoto "mkdir -p '$destino'; tar  xvf - -C '$destino'" 
  
  $ # compactação bzip2 
  $ ( cd "$pesquisa"; tar jcf - -T <(time find . -type f -iname "$padrao") ) | ssh  -Ti "$chave" $remoto "mkdir -p '$destino'; tar jxvf - -C '$destino'" 
  
  $ # compactação lzma 
  $ ( cd "$pesquisa"; tar  cf - -T <(time find . -type f -iname "$padrao") ) | xz -9e | ssh -Ti "$chave" $remoto "mkdir -p '$destino'; xz -d | tar xvf - -C '$destino'" 

Comandos usados nesse artigo

Adicionar comentário

* Campos obrigatórios
5000
Powered by Commentics

Comentários

Nenhum comentário ainda. Seja o primeiro!


Veja a relação completa dos artigos de Paulo Roberto Bagatini