Pourquoi find -exec mv {} ./target/ + ne fonctionne pas?

98

Je veux savoir exactement ce que {} \;et {} \+et | xargs ...faire. Veuillez les clarifier avec des explications.

En dessous de 3 commandes s'exécutent et produisent le même résultat, mais la première commande prend un peu de temps et le format est également un peu différent.

find . -type f -exec file {} \;
find . -type f -exec file {} \+
find . -type f | xargs file

C'est parce que le premier exécute la filecommande pour chaque fichier provenant de la findcommande. Donc, fondamentalement, il fonctionne comme:

file file1.txt
file file2.txt

Mais les 2 derniers trouvent avec les -execcommandes exécutez la commande de fichier une fois pour tous les fichiers comme ci-dessous:

file file1.txt file2.txt

Ensuite, j'exécute les commandes suivantes sur lesquelles la première s'exécute sans problème mais la seconde donne un message d'erreur.

find . -type f -iname '*.cpp' -exec mv {} ./test/ \;
find . -type f -iname '*.cpp' -exec mv {} ./test/ \+ #gives error:find: missing argument to `-exec'

Pour la commande avec {} \+, cela me donne le message d'erreur

find: missing argument to `-exec'

pourquoi donc? quelqu'un peut-il expliquer ce que je fais de mal?

Shahadat Hossain
la source
La vraie question est simple, pourquoi le premier fonctionne et le second non? (1) trouver. -type f -iname ' .cpp' -exec mv {} ./test/ \; (2) trouver. -type f -iname ' .cpp' -exec mv {} ./test/ \ +
Shahadat Hossain

Réponses:

185

La page de manuel (ou le manuel GNU en ligne ) explique à peu près tout.

commande find -exec {} \;

Pour chaque résultat, command {}est exécuté. Toutes les occurrences de {}sont remplacées par le nom de fichier. ;est précédé d'une barre oblique pour empêcher le shell de l'interpréter.

commande find -exec {} +

Chaque résultat est ajouté commandet exécuté par la suite. En tenant compte des limitations de longueur de commande, je suppose que cette commande peut être exécutée plus de fois, avec la page de manuel me soutenant:

le nombre total d'appels de la commande sera bien inférieur au nombre de fichiers correspondants.

Notez cette citation de la page de manuel:

La ligne de commande est construite à peu près de la même manière que xargs construit ses lignes de commande

C'est pourquoi aucun caractère n'est autorisé entre {}et à l' +exception des espaces. +fait que find détecte que les arguments doivent être ajoutés à la commande comme xargs.

La solution

Heureusement, l'implémentation GNU de mvpeut accepter le répertoire cible comme argument, avec l'un -tdes paramètres ou le paramètre le plus long --target. Son utilisation sera:

mv -t target file1 file2 ...

Votre findcommande devient:

find . -type f -iname '*.cpp' -exec mv -t ./test/ {} \+

Depuis la page de manuel:

commande -exec;

Exécuter la commande; true si l'état 0 est renvoyé. Tous les arguments suivants à rechercher sont considérés comme des arguments de la commande jusqu'à ce qu'un argument consistant en `; ' est rencontré. La chaîne «{}» est remplacée par le nom du fichier en cours de traitement partout où il apparaît dans les arguments de la commande, pas seulement dans les arguments où il est seul, comme dans certaines versions de find. Ces deux constructions peuvent devoir être échappées (avec un `\ ') ou entre guillemets pour les protéger de l'expansion par le shell. Voir la section EXEMPLES pour des exemples d'utilisation de l'option -exec. La commande spécifiée est exécutée une fois pour chaque fichier correspondant. La commande est exécutée dans le répertoire de départ. Il existe des problèmes de sécurité inévitables entourant l'utilisation de l'action -exec; vous devriez utiliser l'option -execdir à la place.

-exec commande {} +

Cette variante de l'action -exec exécute la commande spécifiée sur les fichiers sélectionnés, mais la ligne de commande est construite en ajoutant chaque nom de fichier sélectionné à la fin; le nombre total d'appels de la commande sera bien inférieur au nombre de fichiers correspondants. La ligne de commande est construite à peu près de la même manière que xargs construit ses lignes de commande. Une seule instance de «{}» est autorisée dans la commande. La commande est exécutée dans le répertoire de départ.

Lekensteyn
la source
1
Je sais réellement comment cela fonctionne, j'ai parcouru ce manuel plusieurs fois, mais j'ai reçu un message d'erreur pour l'utilisation de {} +, bien que cela fonctionne pour {} \; et j'utilise Cygwin dans Windows.
Shahadat Hossain le
1
@Shahadat: avez-vous lu la partie avant "De la page de manuel"? Vous avez mis ./test/entre {}et +, mais aucun caractère non blanc n'est autorisé entre ceux-ci.
Lekensteyn
ru en disant que je ne devrais pas mettre ./test/ entre {} et +. Alors comment la commande mv fonctionnera; mv a besoin d'une source qui est {} et a besoin d'une destination qui est ./test/ et d'une terminaison avec +. pouvez-vous s'il vous plaît écrire la commande ce que vous pensez bien?
Shahadat Hossain le
@Shahadat: Je vois ce que vous essayez d'accomplir. Windows est lent à exécuter les programmes, vous voulez donc le combiner en une seule commande. J'ajouterai une alternative à la réponse.
Lekensteyn
1
La +commande AFAIU est un peu étrange car elle colle les fichiers à «la fin» (et non à la place de {}) alors pourquoi utiliser {}du tout - c'est déroutant. Merci pour l' -toption que je ne connaissais pas, il semble que cette option ait été créée pour contourner ce -exec +problème!
e2-e4
6

J'ai rencontré le même problème sur Mac OSX , en utilisant un shell ZSH : dans ce cas, il n'y a pas d' -toption pour mv, donc j'ai dû trouver une autre solution. Cependant, la commande suivante a réussi:

find .* * -maxdepth 0 -not -path '.git' -not -path '.backup' -exec mv '{}' .backup \;

Le secret était de citer les accolades . Pas besoin que les accolades soient à la fin de la execcommande.

J'ai testé sous Ubuntu 14.04 (avec les shells BASH et ZSH ), cela fonctionne de la même manière.

Cependant, lors de l'utilisation du +signe, il semble bien que ce soit à la fin de la execcommande.

arvymétal
la source
{}doit être cité dans les coquilles fishet rc, mais pas dans zsh, bashni dans les autres coquilles des familles Bourne ou csh.
Stephane Chazelas
@StephaneChazelas Oui, retesté sous Ubuntu avec bash, en effet les citations ne sont pas nécessaires. Curieusement, j'ai eu un problème si je ne les citais pas sous MacOS (en utilisant zsh). Mais je n'ai pas de Mac à portée de main pour réessayer ...
arvymetal
3

L'équivalent standard de find -iname ... -exec mv -t dest {} +pour les findimplémentations qui ne prennent pas en charge -inameou les mvimplémentations qui ne prennent pas en charge -test d'utiliser un shell pour réorganiser les arguments:

find . -name '*.[cC][pP][pP]' -type f -exec sh -c '
  exec mv "$@" /dest/dir/' sh {} +

En utilisant -name '*.[cC][pP][pP]', nous évitons également de nous fier aux paramètres régionaux actuels pour décider quelle est la version majuscule de cou p.

Notez que +, contrairement à ce ;n'est pas spécial dans aucun shell, il n'a donc pas besoin d'être cité (bien que les guillemets ne nuiront pas, sauf bien sûr avec des shells comme rccelui-ci ne sont pas pris en charge \comme opérateur de guillemets).

La fuite /en /dest/dir/est ainsi que mvéchoue avec une erreur au lieu de renommer foo.cppà /dest/dirdans le cas où un seul cppfichier a été trouvé et /dest/dirn'existait ou non un répertoire (ou symlink dans le répertoire).

Stéphane Chazelas
la source
+1 ... une opération dans le shell comme préalable à l'exécution d'une commande est en fait utile pour une variété de cas d'utilisation ... sympa.
Cbhihe
0
find . -name "*.mp3" -exec mv --target-directory=/home/d0k/Музика/ {} \+
DarkLabs
la source
Veuillez ajouter quelques explications à votre réponse afin que les autres puissent en tirer des leçons
Nico Haase
Vous devez répondre à la question, qui a demandé des explications. Le code uniquement n'est pas une réponse.
Lajos Arpad
-1

non, la différence entre +et \;doit être inversée. +ajoute les fichiers à la fin de la commande exec, puis exécute la commande exec et \;exécute la commande pour chaque fichier.

Le problème est find . -type f -iname '*.cpp' -exec mv {} ./test/ \+qu'il find . -type f -iname '*.cpp' -exec mv {} ./test/ + ne devrait pas être nécessaire d'y échapper ou de mettre fin au+

xargs Je n'ai pas utilisé depuis longtemps mais je pense que ça marche comme +.

Mike Ramirez
la source
J'ai également essayé avec cela mais j'ai reçu le même message d'erreur. De plus, partout où j'ai trouvé d'utiliser uniquement + mais dans mon cygwin, je dois utiliser \ + ou "+" pour travailler.
Shahadat Hossain le
oh c'est un environnement cygwin. Désolé alors je ne sais pas, je n'utilise pas le shell cygwin, j'utilise juste un * nix.
Mike Ramirez
1
@Shahadat Hossain essaie -name "*.cpp"J'utilise à peine -iname à moins que je ne veuille faire une recherche regex difficile, comme -iname '??? work. * \. Cpp'
Mike Ramirez
1
@Mike: Je pense que vous comprenez mal la différence entre -inameet -name. -inameest la version insensible à la casse de -nameet n'a pas de différences dans la gestion des expressions régulières. Je suggère d'essayer les commandes avant de publier, votre commande échoue également dans mon shell.
Lekensteyn le
1
@Lekensteyn Il était déjà établi que c'était le cas avant votre commentaire. Je pensais avoir reconnu Shahadat avant votre message, c'était un simple "ok". Non, je ne l'ai pas exécuté manuellement, je l'ai fait du haut de ma tête et j'utilise rarement cette forme de recherche regex avec find. C'était juste une chose de type «pourrait aider».
Mike Ramirez