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
Foi útil?

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.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top