Bash: Copiez les fichiers nommés de manière récursive, en préservant la structure des dossiers

103

J'espérais:

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

donnerait une structure symétrique dans le répertoire de destination:

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

mais à la place, les deux fichiers sont copiés dans / tmp / package. Un exemplaire à plat. (Ceci est sur OSX).

Y a-t-il une fonction bash simple que je peux utiliser pour copier tous les fichiers, y compris les fichiers spécifiés par joker (par exemple src / *. Js) à leur place dans le répertoire de destination. Un peu comme "pour chaque fichier, exécutez mkdir -p $(dirname "$file"); cp "$file" $(dirname "$file")", mais peut-être une seule commande.

Ceci est un fil pertinent, ce qui suggère que ce n'est pas possible. La solution de l'auteur ne m'est pas si utile cependant, car je voudrais simplement fournir une liste de fichiers, joker ou non, et les faire tous copiés dans le répertoire de destination. IIRC MS-DOS xcopy le fait, mais il ne semble pas y avoir d'équivalent pour cp.

Mahemoff
la source

Réponses:

154

Avez-vous essayé d'utiliser l'option --parents? Je ne sais pas si OS X prend en charge cela, mais cela fonctionne sous Linux.

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

Si cela ne fonctionne pas sous OS X, essayez

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

comme l'aif l'a suggéré.

ustun
la source
4
Merci. s'avère que "cp --parents" n'est pas possible sur mac, mais c'est bien de connaître le drapeau pour les autres Unixen. rsync -R est la solution portable la plus simple à ce problème.
mahemoff
1
J'ai accepté celui-ci pour son élégance / mémorisation, mais je viens de découvrir que cela ne copie pas des répertoires entiers (au moins sur OSX), contrairement à celui ci-dessous.
mahemoff
cp --parentsest une option illégale dans OSX (BSD cp), mais gcp(GNU cp) fonctionne bien. S'il n'est pas encore dans votre système, utilisez brew install coreutils. U aura de nombreux utils avec le préfixe g.
kyb
@mahemoff cp -R --parentset rsync -rRcopie les fichiers et les répertoires de manière relative.
Vortico
22

Une manière:

tar cf - <files> | (cd /dest; tar xf -)
Randy Proctor
la source
oh, j'aime bien mieux que ma réponse.
EMPraptor
4
Vous pouvez également utiliser l' -Coption pour faire le chdir pour vous - tar cf - _files_ | tar -C /dest xf -ou quelque chose comme ça.
D.Shawley
merci, c'est concis, même si je préfère rsync pour plus de simplicité.
mahemoff
1
Génial! Est-ce que quelqu'un sait comment transformer cela en une commande shell? Il doit accepter N entrées, les N-1 premiers sont les fichiers à copier et le dernier est le dossier de destination.
arod
@arod $ {! #} est le dernier paramètre et utilisez ceci pour obtenir les arguments précédents stackoverflow.com/questions/1215538/… . Si vous écrivez la commande, veuillez créer un lien vers l'essentiel ici.
mahemoff
18

Sinon, si vous êtes old-school, utilisez cpio:

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

De toute évidence, vous pouvez filtrer la liste de fichiers à votre guise.

L'option '-p' est pour le mode 'pass-through' (par opposition à '-i' pour l'entrée ou '-o' pour la sortie). Le '-v' est détaillé (liste les fichiers au fur et à mesure qu'ils sont traités). Le '-m' préserve les temps de modification. Le '-B' signifie utiliser 'gros blocs' (où les gros blocs font 5120 octets au lieu de 512 octets); il est possible que cela n'ait aucun effet ces jours-ci.

Jonathan Leffler
la source
1
Il est préférable de l'utiliser -print0avec la combinaison d' --nulloptions pour ne pas rompre avec les caractères spéciaux et autres:find . -print0 | cpio -pvdmB --null /target
haridsv
Appelez-le old-school, appelez-le portable, appelez-le comme ça, mais je fais vraiment confiance cpiopour cette tâche. Je suis d'accord que les options -print0et -nulldevraient être utilisées, sinon, à un moment donné, quelqu'un vous donnera des dossiers avec des «caractères spéciaux» (espaces, très probablement) et quelque chose se passera. Non pas que je parle d'expérience personnelle, mais vous pourriez essayer de sauvegarder un tas de fichiers et vous retrouver avec seulement la moitié d'entre eux sauvegardés en raison d'espaces dans les noms de fichiers. (D'accord, je parle d'expérience personnelle.)
bballdave025
@ bballdave025: Vous rencontrez uniquement des problèmes avec cpiocomme indiqué si les noms de fichiers contiennent des retours à la ligne - alors vous vous retrouvez normalement avec un message d'erreur indiquant que deux (ou plus) noms de fichiers ne sont pas trouvés pour chaque nouvelle ligne dans un nom de fichier. (Parfois, vous pouvez recevoir moins de messages - mais cela nécessite un soin considérable dans la construction du scénario de test.). Quand j'ai utilisé cpio, il n'y avait pas d' --nulloption; Les options à deux tirets ne faisaient pas partie des notations des options SVR4, et le concept de -print0n'était pas présent dans les finddeux. Mais c'était il y a longtemps (au milieu des années 90 par exemple. Avant que Linux ne domine).
Jonathan Leffler le
Merci pour les détails, @Jonathan_Leffler. J'adore apprendre des choses ici. Maintenant, j'essaie de me souvenir de l'erreur de copie récursive que j'ai commise qui m'a posé des problèmes avec les espaces - il y a de nombreuses façons dont j'aurais pu faire cette erreur,
bballdave025
@ bballdave025 - Une possibilité est d'utiliser xargsdans le mix - il se divise en espace blanc - blancs, tabulations, retours à la ligne. OTOH, je ne sais pas comment ni pourquoi vous feriez ça. Le cpiomanuel GNU sur le mode de sortie est clair sur un nom de fichier par ligne. Le manuel de l'utilisateur du SVR4 (imprimé en 1990) est plus vague: cpio -o(mode copie) lit l'entrée standard pour obtenir une liste de chemins et copie ces fichiers sur la sortie standard avec le chemin d'accès et les informations d'état. Il y a donc une chance que cela ait cassé des noms aux espaces.
Jonathan Leffler le
16

L'option -R de rsync fera ce que vous attendez. C'est un copieur de fichiers très riche en fonctionnalités. Par exemple:

$ 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

Exemples de résultats:

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

Essayer...

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

donc pour ce que vous faisiez à l'origine ...

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
la source
Vous n'avez pas besoin echoou $vici. Cette méthode échouera également si le répertoire correspondant n'existe pas dans la destination.
Weijun Zhou