Bash: copia os arquivos nomeados recursivamente, preservando a estrutura da pasta

103

Eu estava esperando:

cp -R src/prog.js images/icon.jpg /tmp/package

resultaria em uma estrutura simétrica no dir de destino:

/tmp
|
+-- package
    |
    +-- src
    |   |
    |   +-- prog.js
    |
    +-- images
        |
        +-- icon.jpg

mas em vez disso, ambos os arquivos são copiados em / tmp / package. Uma cópia plana. (Isso está no OSX).

Existe uma função bash simples que posso usar para copiar todos os arquivos, incluindo arquivos especificados por curinga (por exemplo, src / *. Js) em seu devido lugar no diretório de destino. Um pouco como "para cada arquivo, execute mkdir -p $(dirname "$file"); cp "$file" $(dirname "$file")", mas talvez um único comando.

Este é um tópico relevante, o que sugere que não é possível. A solução do autor não é tão útil para mim, porque eu gostaria simplesmente de fornecer uma lista de arquivos, curinga ou não, e ter todos eles copiados para o diretório de destino. IIRC MS-DOS xcopy faz isso, mas parece não haver equivalente para cp.

mahemoff
fonte

Respostas:

153

Você já tentou usar a opção --parents? Não sei se o OS X suporta isso, mas funciona no Linux.

cp --parents src/prog.js images/icon.jpg /tmp/package

Se isso não funcionar no OS X, tente

rsync -R src/prog.js images/icon.jpg /tmp/package

como aif sugerido.

ustun
fonte
4
obrigado. Acontece que "cp --parents" não é possível no mac, mas é bom saber o sinalizador para outro unixen. rsync -R é a solução portátil mais simples para este problema.
mahemoff de
1
Aceitei este por sua elegância / memorabilidade, mas acabei de descobrir que não copia diretórios inteiros (no OSX pelo menos), enquanto o tar abaixo sim.
mahemoff
cp --parentsé uma opção ilegal no OSX (BSD cp), mas gcp(GNU cp) funciona bem. Se ainda não estiver em seu sistema, use brew install coreutils. Você terá muitos utilitários com prefixo g.
kyb
@mahemoff cp -R --parentse rsync -rRcopia arquivos e diretórios relativamente.
Vortico
22

Mão única:

tar cf - <files> | (cd /dest; tar xf -)
Randy Proctor
fonte
oh, gosto muito mais disso do que da minha resposta.
EMPraptor
4
Você também pode usar a -Copção de fazer o chdir para você - tar cf - _files_ | tar -C /dest xf -ou algo parecido.
D.Shawley
obrigado, isso é conciso, embora eu prefira rsync pela simplicidade.
mahemoff de
1
Ótimo! Alguém sabe como transformar isso em um comando shell? Deve aceitar N entradas, o primeiro N-1 são os arquivos a serem copiados e o último é a pasta de destino.
arod
@arod $ {! #} é o último parâmetro e use-o para obter os argumentos anteriores stackoverflow.com/questions/1215538/… . Se você escrever o comando, faça o link para a essência aqui.
mahemoff
18

Como alternativa, se você for da velha escola, use cpio:

cd /source;
find . -print | cpio -pvdmB /target

Claramente, você pode filtrar a lista de arquivos conforme desejar.

A opção '-p' é para o modo 'pass-through' (em oposição a '-i' para entrada ou '-o' para saída). O '-v' é prolixo (lista os arquivos à medida que são processados). O '-m' preserva os tempos de modificação. O '-B' significa usar 'blocos grandes' (onde blocos grandes são 5120 bytes em vez de 512 bytes); é possível que não tenha nenhum efeito hoje em dia.

Jonathan Leffler
fonte
1
É melhor usar -print0com a combinação de --nullopção para que não seja interrompida com caracteres especiais e tais:find . -print0 | cpio -pvdmB --null /target
haridsv
Chame de old-school, chame de portátil, chame de qualquer coisa, mas eu definitivamente confio cpiopara essa tarefa. Eu concordo que as opções -print0e -nulldevem ser usadas, caso contrário, em algum momento, alguém lhe dará algumas pastas com 'caracteres especiais' (espaços, provavelmente) e algo acontecerá. Não que eu esteja falando por experiência própria, mas você pode tentar fazer backup de um monte de arquivos e acabar com o backup da metade deles devido aos espaços estarem nos nomes dos arquivos. (Ok, estou falando por experiência própria.)
bballdave025
@ bballdave025: Você só tem problemas com cpioo mostrado se os nomes de arquivo contiverem novas linhas - então você normalmente acaba com uma mensagem de erro sobre dois (ou mais) nomes de arquivo não sendo encontrados para cada nova linha em um nome de arquivo. (Às vezes, você pode receber menos mensagens - mas isso requer um cuidado considerável na construção do caso de teste.). Quando usei cpio, não havia --nullopção; As opções de traço duplo não faziam parte das notações de opção do SVR4, e o conceito de -print0não estava presente em findnenhuma delas. Mas isso foi há muito tempo (meados dos anos 90, por exemplo. Antes do Linux alcançar o domínio).
Jonathan Leffler
Obrigado pelos detalhes, @Jonathan_Leffler. Adoro aprender coisas aqui. Agora, estou tentando lembrar que erro de cópia recursiva cometi que me deu problemas com espaços - há muitas maneiras de eu ter cometido esse erro,
bballdave025
@ bballdave025— Uma possibilidade é usar xargsna combinação - ele se divide em espaços em branco - espaços em branco, tabulações, novas linhas. OTOH, não tenho certeza de como ou por que você faria isso. O cpiomanual GNU no modo de saída é claro sobre um nome de arquivo por linha. O manual do usuário do SVR4 (impresso em 1990) é mais vago: cpio -o(modo de cópia) lê a entrada padrão para obter uma lista de nomes de caminho e copia esses arquivos na saída padrão junto com o nome do caminho e as informações de status. Há uma chance, portanto, de que ele quebrou nomes em espaços.
Jonathan Leffler
16

A opção -R do rsync fará o que você espera. É uma copiadora de arquivos com muitos recursos. Por exemplo:

$ rsync -Rv src/prog.js images/icon.jpg /tmp/package/
images/
images/icon.jpg
src/
src/prog.js

sent 197 bytes  received 76 bytes  546.00 bytes/sec
total size is 0  speedup is 0.00

Resultados da amostra:

$ find /tmp/package
/tmp/package
/tmp/package/images
/tmp/package/images/icon.jpg
/tmp/package/src
/tmp/package/src/prog.js
Ryan Bright
fonte
1

Experimentar...

for f in src/*.js; do cp $f /tmp/package/$f; done

então, pelo que você estava fazendo originalmente ...

for f in `echo "src/prog.js images/icon.jpg"`; do cp $f /tmp/package/$f; done

ou

v="src/prog.js images/icon.jpg"; for f in $v; do cp $f /tmp/package/$f; done
EMPraptor
fonte
Você não precisa echoou $vaqui. Além disso, este método falhará se o diretório correspondente não existir no destino.
Weijun Zhou