Cópia de arquivo shell Unix achatando estrutura de pastas
Pergunta
No shell bash do UNIX (especificamente no Mac OS X Leopard), qual seria a maneira mais simples de copiar todos os arquivos com uma extensão específica de uma hierarquia de pastas (incluindo subdiretórios) para a mesma pasta de destino (sem subpastas)?
Obviamente existe o problema de haver duplicatas na hierarquia de origem.Eu não me importaria se eles fossem substituídos.
Exemplo:Preciso copiar todos os arquivos .txt na seguinte hierarquia
/foo/a.txt
/foo/x.jpg
/foo/bar/a.txt
/foo/bar/c.jpg
/foo/bar/b.txt
Para uma pasta chamada 'dest' e obtenha:
/dest/a.txt
/dest/b.txt
Solução
Na festa:
find /foo -iname '*.txt' -exec cp \{\} /dest/ \;
find
encontrará todos os arquivos no caminho /foo
correspondendo ao curinga *.txt
, sem distinção entre maiúsculas e minúsculas (é isso que -iname
significa).Para cada arquivo, find
irá executar cp {} /dest/
, com o arquivo encontrado no lugar de {}
.
Outras dicas
O único problema com a solução do Magnus é que ela cria um novo processo "cp" para cada arquivo, o que não é muito eficiente, especialmente se houver um grande número de arquivos.
No Linux (ou outros sistemas com GNU coreutils) você pode fazer:
find . -name "*.xml" -print0 | xargs -0 echo cp -t a
(O -0 permite que funcione quando seus nomes de arquivos contêm caracteres estranhos - como espaços - neles.)
Infelizmente, acho que os Macs vêm com ferramentas no estilo BSD.Alguém conhece um equivalente "padrão" à opção "-t"?
As respostas acima não permitem colisões de nomes, pois o solicitante não se importou com a substituição dos arquivos.
Eu me importo que os arquivos sejam sobrescritos, então criei uma abordagem diferente.Substituindo cada / no caminho por - mantém a hierarquia nos nomes e coloca todos os arquivos em uma pasta simples.
Usamos find para obter a lista de todos os arquivos, então awk para criar um comando mv com o nome do arquivo original e o nome do arquivo modificado e depois passá-los para o bash para serem executados.
find ./from -type f | awk '{ str=$0; sub(/\.\//, "", str); gsub(/\//, "-", str); print "mv " $0 " ./to/" str }' | bash
onde ./from e ./to são diretórios para mv de e para.
Se você realmente deseja executar apenas um comando, por que não configurar um e executá-lo?Igual a:
$ find /foo -name '*.txt' | xargs echo | sed -e 's/^/cp /' -e 's|$| /dest|' | bash -sx
Mas isso não importa muito em termos de desempenho, a menos que você faça muito isso ou tenha muitos arquivos.Tenha cuidado com conluios de nomes, no entanto.Percebi nos testes que o GNU cp pelo menos avisa sobre colisões:
cp: will not overwrite just-created `/dest/tubguide.tex' with `./texmf/tex/plain/tugboat/tubguide.tex'
Acho que o mais limpo é:
$ find /foo -name '*.txt' | xargs -i cp {} /dest
Menos sintaxe para lembrar do que a opção -exec.
No que diz respeito à página de manual do cp em uma caixa do FreeBSD, não há necessidade de uma opção -t.cp assumirá que o último argumento na linha de comando é o diretório de destino se mais de dois nomes forem passados.