Structure des dossiers d'aplatissement de la copie de fichier shell Unix
Question
Sur le shell bash UNIX (en particulier Mac OS X Leopard), quel serait le moyen le plus simple de copier chaque fichier ayant une extension spécifique d'une hiérarchie de dossiers (y compris les sous-répertoires) vers le même dossier de destination (sans sous-dossiers) ?
Il y a évidemment le problème de la présence de doublons dans la hiérarchie source.Cela ne me dérangerait pas s'ils étaient écrasés.
Exemple:Je dois copier chaque fichier .txt dans la hiérarchie suivante
/foo/a.txt
/foo/x.jpg
/foo/bar/a.txt
/foo/bar/c.jpg
/foo/bar/b.txt
Vers un dossier nommé « dest » et obtenez :
/dest/a.txt
/dest/b.txt
La solution
En bash :
find /foo -iname '*.txt' -exec cp \{\} /dest/ \;
find
trouvera tous les fichiers sous le chemin /foo
correspondant au caractère générique *.txt
, sans tenir compte de la casse (C'est ce que -iname
moyens).Pour chaque fichier, find
exécutera cp {} /dest/
, avec le fichier trouvé à la place de {}
.
Autres conseils
Le seul problème avec la solution de Magnus est qu'elle lance un nouveau processus "cp" pour chaque fichier, ce qui n'est pas très efficace, surtout s'il y a un grand nombre de fichiers.
Sous Linux (ou d'autres systèmes avec GNU coreutils), vous pouvez faire :
find . -name "*.xml" -print0 | xargs -0 echo cp -t a
(Le -0 lui permet de fonctionner lorsque vos noms de fichiers contiennent des caractères étranges - comme des espaces.)
Malheureusement, je pense que les Mac sont livrés avec des outils de style BSD.Quelqu'un connaît un équivalent « standard » au commutateur « -t » ?
Les réponses ci-dessus n'autorisent pas les collisions de noms, car le demandeur ne craignait pas que les fichiers soient écrasés.
Cela me dérange que les fichiers soient écrasés, j'ai donc proposé une approche différente.Remplacer chaque / dans le chemin par - conserver la hiérarchie dans les noms et placer tous les fichiers dans un seul dossier plat.
Nous utilisons find pour obtenir la liste de tous les fichiers, puis awk pour créer une commande mv avec le nom de fichier d'origine et le nom de fichier modifié, puis les transmettre à bash pour qu'ils soient exécutés.
find ./from -type f | awk '{ str=$0; sub(/\.\//, "", str); gsub(/\//, "-", str); print "mv " $0 " ./to/" str }' | bash
où ./from et ./to sont les répertoires vers mv from et to.
Si vous voulez vraiment exécuter une seule commande, pourquoi ne pas en lancer une et l'exécuter ?Ainsi:
$ find /foo -name '*.txt' | xargs echo | sed -e 's/^/cp /' -e 's|$| /dest|' | bash -sx
Mais cela n'aura pas beaucoup d'importance en termes de performances, à moins que vous ne le fassiez souvent ou que vous ayez une tonne de fichiers.Attention cependant aux collusions de noms.J'ai remarqué lors des tests que GNU cp avertit au moins des collisions :
cp: will not overwrite just-created `/dest/tubguide.tex' with `./texmf/tex/plain/tugboat/tubguide.tex'
Je pense que le plus propre est :
$ find /foo -name '*.txt' | xargs -i cp {} /dest
Moins de syntaxe à retenir que l'option -exec.
En ce qui concerne la page de manuel de cp sur une machine FreeBSD, il n'est pas nécessaire d'avoir un commutateur -t.cp supposera que le dernier argument de la ligne de commande est le répertoire cible si plus de deux noms sont transmis.