trouver -exec dans le script bash avec une expansion variable

14

J'essaie d'exécuter une commande similaire à celle ci-dessous dans un script bash. Il doit rechercher dans tous les sous-dossiers de $sourcediret copier tous les fichiers d'un certain type au niveau racine de $targetdir.

#!/bin/bash

# These are set as arguments to the script, not hard-coded
sourcedir="/path/to/sourcedir"
targetdir="/path/to/targetdir"

find "$sourcedir" -type f -name "*.type" -exec sh -c 'cp "$1" "$2/`basename "$1"`"' "{}" "$targetdir" \;

Cela semble être assez proche, sauf que {}n'est pas passé en tant que $2de-exec sh -c ...

Je voudrais le faire aussi près que possible de la "bonne façon", avec une tolérance pour les caractères spéciaux dans les noms de fichiers (en particulier les guillemets simples).

Edit: Je vois des gens suggérer d'utiliser xargsou de chaîner des arguments. J'avais l'impression que ce n'était acceptable que pour un nombre limité d'arguments. Si j'ai, par exemple, des milliers de fichiers .jpg que j'essaie de copier d'un certain nombre de répertoires de la galerie dans un répertoire de diaporamas géants, les solutions d'enchaînement des arguments fonctionneront-elles toujours?

Edit 2: Mon problème était qu'il me manquait un _avant ma première option pour sh dans la -execcommande. Pour toute personne curieuse de savoir comment faire fonctionner la commande find, ajoutez le _et tout ira bien:

find "$sourcedir" -type f -name "*.type" -exec sh -c 'cp "$1" "$2"' _ "{}" "$targetdir" \;

J'ai accepté une réponse ci-dessous, car elle accomplit la même tâche, mais est plus efficace et élégante.

Matthieu
la source
4
C'est exactement la raison pour laquelle il xargsa été créé, pour gérer automatiquement d'énormes quantités d'arguments sur des commandes régulières qui avaient des limites. De plus, la plupart des limites maximales d'arguments ont été considérablement améliorées pour les utilitaires GNU standard. Vous verrez également un avantage en termes de performances, en évitant toutes ces fourchettes de processus, qui sont pertinentes sur des milliers de fichiers.
JM Becker
Avec gnu-find et + au lieu de ";", vous pouvez gérer plusieurs arguments à la fois avec find aussi. Et vous enregistrez l'argument compliqué en passant avec -print0.
utilisateur inconnu
@userunknown: je réponds à ceci sous votre réponse.
JM Becker
@user unknown Eh bien, j'adore ce code. Il est au moins entièrement compatible POSIX et fonctionnera sans aucun élément GNU sur la machine. Il y a ces moments où vous avez besoin de cela, en particulier sur les serveurs au travail.
erreur de syntaxe

Réponses:

6

Vous souhaitez copier des fichiers d'un type spécifique, dans un répertoire spécifique? Il vaut mieux le faire xargs, et vous n'en avez même pas besoin sh. C'est une façon plus appropriée de le faire, devrait également fonctionner plus efficacement.

find "$sourcedir" -type f -name "*.type" | xargs cp -t targetdir

Si vous devez gérer des noms de fichiers spéciaux, utilisez-les NULLcomme séparateur

find "$sourcedir" -type f -name "*.type" -print0 | xargs -0 cp -t "$targetdir"
JM Becker
la source
1
Pour le 2ème cas, n'oubliez pas d'ajouter -print0à findet -0àxargs
SiegeX
@ SiegeX, le faisait déjà avant que je remarque votre commentaire.
JM Becker
De plus, ce NULLn'est pas nécessaire si vous utilisez '{}', c'est important quand vous ne l'utilisez pas. Le véritable avantage entre l'un et l'autre, autre que la POSIXconformité, est la performance.
JM Becker
6

Vous devez passer {}comme argument au shell puis faire une boucle sur chaque argument.

find "$sourcedir" -type f -name "*.type" -exec sh -c 'for f; do cp "$f" "$0"; done' "$targetdir" {} +

Remarque : La façon dont cela fonctionne est que le premier argument d'un shell est le nom du shell , nous pouvons l'exploiter en passant le nom en tant que $targetdir, puis utiliser le paramètre spécial $0à l'intérieur du script shell pour accéder à ce répertoire cible.

SiegeX
la source
1
"$targetdir"n'est pas développé à l'intérieur de guillemets simples.
enzotib
5

Si vous ne croyez pas à l'église de xargs:

find "$sourcedir" -type f -name "*.mp3" -exec cp -t "$targetdir" {} +

Explication:

cp -t a b c d 

copie b, c et d dans le répertoire cible a.

-exec cmd {} +

invoque la commande sur un grand nombre de fichiers à la fois, pas l'un après l'autre (ce qui est standard, si vous utilisez à la ";"place de +). C'est pourquoi vous devez tirer le répertoire cible vers l'avant et le marquer explicitement comme cible.

Cela fonctionne pour gnu-find et peut-être pas pour d'autres implémentations de find. Bien sûr, il s'appuie également sur le drapeau -t.

TechZilla est bien sûr à l'extrême droite, car il shn'est pas nécessaire d'invoquer cp.

Si vous n'utilisez pas xargs, ce qui est la plupart du temps inutile en combinaison avec find, vous êtes sauvé de l'apprentissage du drapeau -print0et -0.

Utilisateur inconnu
la source
1
Visiblement, la méthode que n'importe qui pourrait préférer est quelque peu subjective. Cela dit, il existe des raisons de continuer à utiliser xargs. Le plus grand exemple, que faire si vous ne trouvez pas de fichiers? J'utilise xargs à la place des forboucles générales tout le temps, findest beaucoup plus petit. De plus, findne prend en charge que +si vous utilisez GNU find, il n'est pas défini dans POSIX. Donc, alors que vous devriez vous sentir libre de préférer findseul, il ne fait pas tout ce que fait xargs. Lorsque vous considérez GNU, xargsvous obtenez également -Pce qui est multi-core. Cela vaut la peine d'apprendre xargsmalgré tout.
JM Becker
Parlant de subjectivité, mon opinion diffère évidemment. D'un autre côté, votre réponse est également correcte. C'est l'une des rares meilleures solutions disponibles.
JM Becker
@TechZilla: J'espère me souvenir de revoir ce site, lorsque find commencera à prendre en charge l'invocation parallèle. :) Dans la plupart des cas avec la copie / le déplacement, la vitesse du disque sera le facteur limitant, mais les SSD peuvent changer l'image. Vous avez raison, qu'une solution non GNU à fournir est une bonne chose. Sinon, la solution de recherche est objectivement plus courte, plus simple et n'utilise que deux processus.
utilisateur inconnu
0
read -p "SOURCE: " sourcedir
read -p "TYPE: " type
read -p "TARGET: " targetdir
find -L $sourcedir -iname "*.$type" -exec cp -v {} $targetdir \;
Berthad33
la source
3
Pensez à ajouter quelques explications sur votre solution.
HalosGhost