Comment puis-je utiliser xargs pour copier des fichiers qui ont des espaces et des guillemets dans leur nom?

j'essaie de copier un tas de fichiers sous un répertoire et un certain nombre de fichiers ont des espaces et des guillemets dans leur nom. Quand j'essaie de la chaîne find et grep avec xargs , j'obtiens l'erreur suivante:

find .|grep "FooBar"|xargs -I{} cp "{}" ~/foo/bar
xargs: unterminated quote

des suggestions pour une utilisation plus robuste de xargs?

C'est sur Mac OS X 10.5.3 (Leopard) avec BSD xargs .

204
demandé sur Peter Mortensen 2008-09-27 11:10:48

21 réponses

vous pouvez combiner tout cela en un seul find commande:

find . -iname "*foobar*" -exec cp -- "{}" ~/foo/bar \;

cela permet de gérer les noms de fichiers et les répertoires avec des espaces. Vous pouvez utiliser -name pour obtenir des résultats sensibles à la casse.

Note: le drapeau -- passé à cp l'empêche de traiter des fichiers commençant par - comme options.

179
répondu godbyk 2018-03-29 20:19:09

find . -print0 | grep --null 'FooBar' | xargs -0 ...

Je ne sais pas si grep supporte --null , ni si xargs supporte -0 , sur Leopard, mais sur GNU c'est tout bon.

110
répondu Chris Jester-Young 2015-02-02 21:38:10

c'est plus efficace car il ne tourne pas "cp" plusieurs fois:

find -name '*FooBar*' -print0 | xargs -0 cp -t ~/foo/bar
67
répondu Tometzky 2008-09-29 14:50:21

la façon la plus facile de faire ce que l'affiche originale veut est de changer le délimiteur de n'importe quel espace à juste le caractère de fin de ligne comme ceci:

find whatever ... | xargs -d "\n" cp -t /var/tmp
66
répondu user87601 2016-11-08 09:03:58

j'ai rencontré le même problème. Voici comment je l'ai résolu:

find . -name '*FoooBar*' | sed 's/.*/"&"/' | xargs cp ~/foo/bar

j'ai utilisé sed pour remplacer chaque ligne d'entrée par la même ligne, mais entourée de doubles guillemets. De la page de manuel sed , ` 151980920 "...un ampli ( " & " ) apparaissant dans le remplacement est remplacé par la chaîne correspondant à L'objet... " -- dans ce cas, .* , toute la ligne.

cela résout l'erreur xargs: unterminated quote .

53
répondu oyouareatubeo 2012-08-22 06:08:25

cette méthode fonctionne sur Mac OS X v10.7.5 (Lion):

find . | grep FooBar | xargs -I{} cp {} ~/foo/bar

j'ai aussi testé la syntaxe exacte que vous avez posté. Cela a aussi bien fonctionné le 10.7.5.

43
répondu the_minted 2018-07-29 20:51:48

Voici une solution (POSIX) portable, c'est-à-dire qui ne nécessite pas d'extensions spécifiques à GNU find , xargs ou cp :"

find . -name "*FooBar*" -exec sh -c 'cp -- "$@" ~/foo/bar' sh {} +

il gérera correctement les fichiers et les répertoires avec des espaces intégrés, des lignes nouvelles ou autre, et est plus efficace (i.e. plus rapide ) que les réponses acceptées et la plupart sinon toutes les autres réponses.

9
répondu jlliagre 2018-03-13 13:27:41

regardez en utilisant l'option --null commandline pour xargs avec l'option-print0 dans find.

8
répondu Shannon Nelson 2008-09-27 07:16:20

pour ceux qui s'appuient sur des commandes autres que find, par exemple ls :

find . | grep "FooBar" | tr \n \0 | xargs -0 -I{} cp "{}" ~/foo/bar
7
répondu Aleksandr Guidrevitch 2014-03-20 14:10:43
find | perl -lne 'print quotemeta' | xargs ls -d

je crois que cela fonctionnera de manière fiable pour n'importe quel personnage sauf line-feed (et je soupçonne que si vous avez des line-feed dans vos noms de fichiers, alors vous avez des problèmes pires que celui-ci). Il ne nécessite pas GNU findutils, juste Perl, donc il devrait fonctionner à peu près n'importe où.

5
répondu mavit 2012-02-09 22:23:51

j'ai trouvé que la syntaxe suivante fonctionne bien pour moi.

find /usr/pcapps/ -mount -type f -size +1000000c | perl -lpe ' s{ }{\ }g ' | xargs ls -l | sort +4nr | head -200

dans cet exemple, je cherche les 200 plus gros fichiers de plus de 1.000.000 octets dans le système de fichiers monté sur"/usr/pcapps".

la doublure de ligne Perl entre" find "et" xargs "échappe/cite chaque blanc de sorte que" xargs "passe n'importe quel nom de fichier avec des blancs intégrés à" ls " comme un seul argument.

5
répondu Peter Mortensen 2018-07-29 20:33:23

sachez que la plupart des options discutées dans d'autres réponses ne sont pas standard sur les plateformes qui n'utilisent pas les utilitaires GNU (Solaris, AIX, HP-UX, par exemple). Voir la spécification POSIX pour le comportement xargs "standard".

je trouve aussi que le comportement de xargs qui exécute la commande au moins une fois, même sans aucune entrée, est une nuisance.

j'ai écrit ma propre version privée de xargs (xargl) pour traiter avec le les problèmes d'Espaces Dans les noms (seules les nouvelles lignes séparent-bien que le ' find ... - la combinaison print0' et 'xargs -0 'est assez soignée étant donné que les noms de fichiers ne peuvent pas contenir de caractères ASCII NUL' \0'. Mon xargl n'est pas aussi complet qu'il devrait l'être pour qu'il vaille la peine d'être publié - d'autant plus que GNU dispose d'installations au moins aussi bonnes.

2
répondu Jonathan Leffler 2008-10-02 07:00:01

si les versions find et xarg de votre système ne prennent pas en charge les commutateurs -print0 et -0 (par exemple AIX find et xargs), vous pouvez utiliser ce code terriblement attrayant:

 find . -name "*foo*" | sed -e "s/'/\\'/g" -e 's/"/\"/g' -e 's/ /\ /g' | xargs cp /your/dest

ici sed se chargera de s'échapper des espaces et des citations pour xargs.

testé sur AIX 5.3

1
répondu Jan Ptáčník 2015-03-27 14:49:21

j'ai créé un petit script d'emballage portable appelé" xargsL "autour de" xargs " qui traite la plupart des problèmes.

contrairement à xargs, xargsL accepte un chemin par ligne. Les noms de chemin peuvent contenir n'importe quel caractère sauf (évidemment) les octets de newline ou de NUL.

aucune citation n'est autorisée ou prise en charge dans la liste des fichiers - vos noms de fichiers peuvent contenir toutes sortes de blancs, antislashs, antisticks, caractères génériques d'interpréteur de commandes et similaires - xargsL les traitera comme des caractères littéraux, aucun mal fait.

en bonus, xargsL et non lancera la commande une fois s'il n'y a pas d'entrée!

noter la différence:

$ true | xargs echo no data
no data

$ true | xargsL echo no data # No output

tout argument donné à xargsL sera transmis à xargs.

voici le script shell "XARGSL" POSIX:

#! /bin/sh
# Line-based version of "xargs" (one pathname per line which may contain any
# amount of whitespace except for newlines) with the added bonus feature that
# it will not execute the command if the input file is empty.
#
# Version 2018.76.3
#
# Copyright (c) 2018 Guenther Brunthaler. All rights reserved.
#
# This script is free software.
# Distribution is permitted under the terms of the GPLv3.

set -e
trap 'test $? = 0 || echo ""151910920" failed!" >& 2' 0

if IFS= read -r first
then
        {
                printf '%s\n' "$first"
                cat
        } | sed 's/./\&/g' | xargs ${1+"$@"}
fi

mettre le script dans un répertoire de votre $ PATH et n'oubliez pas de

$ chmod +x xargsL

le script pour le rendre exécutable.

1
répondu Guenther Brunthaler 2018-03-17 18:09:17

la version Perl de bill_starr ne fonctionnera pas bien pour les nouvelles lignes intégrées (seulement les copes avec des espaces). Pour ceux sur Solaris par exemple où vous n'avez pas les outils GNU, une version plus complète pourrait être (en utilisant sed)...

find -type f | sed 's/./\&/g' | xargs grep string_to_find

ajustez les arguments find et grep ou d'autres commandes selon vos besoins, mais le sed corrigera vos lignes/espaces/onglets.

1
répondu Peter Mortensen 2018-07-29 20:36:20

j'ai utilisé le projet de Loi de la Star de réponse légèrement modifié sur Solaris:

find . -mtime +2 | perl -pe 's{^}{\"};s{$}{\"}' > ~/output.file

cela placera des guillemets autour de chaque ligne. Je n'ai pas utilisé l'option "- l " Bien que cela aiderait probablement.

la liste des fichiers que j'allais parcourir pourrait avoir '-', mais pas newlines. Je n'ai pas utilisé le fichier de sortie avec d'autres commandes car je veux passer en revue ce qui a été trouvé avant que je commence à les supprimer massivement via xargs.

1
répondu Carl Yamamoto-Furst 2018-07-29 20:37:43

avec Bash (pas POSIX) vous pouvez utiliser la substitution de processus pour obtenir la ligne courante à l'intérieur d'une variable. Cela vous permet d'utiliser des guillemets pour échapper les caractères spéciaux:

while read line ; do cp "$line" ~/bar ; done < <(find . | grep foo)
1
répondu StackedCrooked 2018-07-29 20:53:20

j'ai joué avec ça un peu, j'ai commencé à envisager de modifier xargs, et j'ai réalisé que pour le genre d'utilisation dont nous parlons ici, une simple réimplementation en Python est une meilleure idée.

pour une chose, avoir ~80 lignes de code pour l'ensemble signifie qu'il est facile de comprendre ce qui se passe, et si un comportement différent est requis, vous pouvez simplement le hacker dans un nouveau script en moins de temps qu'il ne faut pour obtenir une réponse sur quelque chose comme le débordement de la pile.

voir https://github.com/johnallsup/jda-misc-scripts/blob/master/yargs et https://github.com/johnallsup/jda-misc-scripts/blob/master/zargs.py .

avec yargs comme écrit (et Python 3 installé) vous pouvez taper:

find .|grep "FooBar"|yargs -l 203 cp --after ~/foo/bar

pour copier 203 fichiers à la fois. (Ici 203 est juste un espace réservé, bien sûr, et en utilisant un nombre étrange comme 203 Il est clair que ce le nombre n'a aucune autre signification.)

si vous voulez vraiment quelque chose de plus rapide et sans avoir besoin de Python, prenez zargs et yargs comme prototypes et réécrivez en C++ ou C.

1
répondu John Allsup 2018-07-29 20:58:21

pour moi, j'essayais de faire quelque chose d'un peu différent. Je voulais copier la mienne .des fichiers txt dans mon dossier tmp. Le. les noms de fichiers txt contiennent des espaces et des caractères apostrophes. Ça a marché sur mon Mac.

$ find . -type f -name '*.txt' | sed 's/'"'"'/\'"'"'/g' | sed 's/.*/"&"/'  | xargs -I{} cp -v {} ./tmp/
1
répondu Moises 2018-07-29 20:59:07

vous pourriez avoir besoin de grep Foobar répertoire comme:

find . -name "file.ext"| grep "FooBar" | xargs -i cp -p "{}" .
0
répondu fred 2013-06-26 16:02:22

si vous utilisez Bash, vous pouvez convertir stdout en un tableau de lignes par mapfile :

find . | grep "FooBar" | (mapfile -t; cp "${MAPFILE[@]}" ~/foobar)

les prestations sont:

  • c'est intégré, donc c'est plus rapide.
  • exécute la commande avec tous les noms de fichiers en une seule fois, donc c'est plus rapide.
  • vous pouvez ajouter d'autres arguments aux noms des fichiers. Pour cp , vous pouvez aussi:

    find . -name '*FooBar*' -exec cp -t ~/foobar -- {} +
    

    toutefois, certaines commandes n'ont pas cette fonctionnalité.

inconvénients:

  • peut-être pas bien échelle s'il ya trop de noms de fichiers. (La limite? Je ne sais pas, mais j'ai testé avec un fichier de liste de 10 Mo qui inclut 10000 + noms de fichiers sans problème, sous Debian)

bien... qui sait si Bash est disponible sur OS X?

-1
répondu Xiè Jìléi 2018-07-27 14:23:18