web-dev-qa-db-fra.com

Comment utiliser xargs pour copier des fichiers contenant des espaces et des guillemets?

J'essaie de copier un groupe de fichiers sous un répertoire et un certain nombre de fichiers ont des espaces et des guillemets dans leurs noms. Lorsque j'essaie d'enchaîner find et grep avec xargs, j'obtiens le message d'erreur suivant:

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. (Leopard) avec BSD xargs.

219
Drew Stephens

Vous pouvez combiner tout cela en une seule commande find:

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

Cela gérera les noms de fichiers et les répertoires contenant des espaces. Vous pouvez utiliser -name pour obtenir des résultats sensibles à la casse.

Remarque: L'indicateur -- passé à cp l'empêche de traiter les fichiers commençant par - en tant qu'options.

192
godbyk

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, tout va bien.

114
Chris Jester-Young

La manière la plus simple de faire ce que l'affiche originale veut est de changer le délimiteur de n'importe quel espace en juste le caractère de fin de ligne comme ceci:

find whatever ... | xargs -d "\n" cp -t /var/tmp
78
user87601

Ceci est plus efficace car il n’exécute pas "cp" plusieurs fois:

find -name '*FooBar*' -print0 | xargs -0 cp -t ~/foo/bar
70
Tometzky

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 entre guillemets. Dans la page de manuel sed, "... Une esperluette (" & '') apparaissant dans le remplacement est remplacée par la chaîne correspondant à RE ... "- dans cette cas, .*, la ligne entière.

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

57
oyouareatubeo

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ée. Cela a également bien fonctionné le 10.7.5.

51
the_minted

Juste n'utilisez pas xargs. C'est un programme soigné, mais il ne va pas bien avec find lorsqu'il est confronté à des cas non triviaux.

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

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

Notez la fin + au lieu du plus habituel ;.

Cette solution:

  • gère correctement les fichiers et les répertoires contenant des espaces, des nouvelles lignes ou tout autre caractère exotique.

  • fonctionne sur tous les systèmes Unix et Linux, même ceux ne fournissant pas le toolkit GNU.

  • n'utilise pas xargs qui est un programme agréable et utile, mais nécessite trop d'ajustements et de fonctionnalités non standard pour traiter correctement find la sortie.

  • est également plus efficace (lu plus rapide) que les réponses acceptées et la plupart sinon toutes.

Notez également que, malgré ce qui est indiqué dans d'autres réponses ou commentaires, la mention de {} est inutile (à moins que vous utilisiez l'exotique fishShell).

11
jlliagre

Examinez l'utilisation de l'option de ligne de commande --null pour xargs avec l'option -print0 dans find.

8
Shannon Nelson

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
8

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 recherche les 200 plus gros fichiers de plus de 1 000 000 octets dans le système de fichiers monté sur "/ usr/pcapps".

La ligne de commande Perl entre "find" et "xargs" échappe/cite chaque espace afin que "xargs" transmette tout nom de fichier avec des espaces incorporés à "ls" en tant qu'argument unique.

5
bill_starr
find | Perl -lne 'print quotemeta' | xargs ls -d

Je pense que cela fonctionnera de manière fiable pour tous les personnages sauf le saut de ligne (et je suppose que si vous avez des sauts de ligne dans vos noms de fichiers, vous avez des problèmes encore plus graves). Il ne nécessite pas de GNU findutils, mais uniquement de Perl, il devrait donc fonctionner pratiquement n'importe où.

5
mavit

Pour moi, j'essayais de faire quelque chose d'un peu différent. Je voulais copier mes fichiers .txt dans mon dossier tmp. Les noms de fichiers .txt contiennent des espaces et des caractères apostrophe. Cela a fonctionné sur mon Mac.

$ find . -type f -name '*.txt' | sed 's/'"'"'/\'"'"'/g' | sed 's/.*/"&"/'  | xargs -I{} cp -v {} ./tmp/
2
Moises

Avec Bash (pas POSIX), vous pouvez utiliser la substitution de processus pour obtenir la ligne courante dans une variable. Cela vous permet d'utiliser des guillemets pour échapper aux caractères spéciaux:

while read line ; do cp "$line" ~/bar ; done < <(find . | grep foo)
2
StackedCrooked

Sachez que la plupart des options évoquées dans d'autres réponses ne sont pas standard sur les plates-formes n'utilisant pas les utilitaires GNU (Solaris, AIX, HP-UX, par exemple). Voir la spécification POSIX pour le comportement 'standard' de xargs.

Je trouve également que le comportement de xargs dans lequel il exécute la commande au moins une fois, même sans entrée, est gênant.

J'ai écrit ma propre version privée de xargs (xargl) pour traiter les problèmes d'espaces dans les noms (seuls les nouvelles lignes séparent), bien que les combinaisons 'find ... -print0' et 'xargs -0' soient plutôt chouettes, car les noms de fichiers ne le peuvent pas. contient ASCII NUL '\ 0' caractères. Mon xargl n'est pas aussi complet qu'il devrait l'être pour être publié - d'autant plus que GNU dispose d'installations au moins aussi performantes. .

2
Jonathan Leffler

J'ai créé un petit script portable appelé "xargsL" autour de "xargs" qui résout 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) nouvelle ligne ou octets NUL.

Aucune citation n'est autorisée ou prise en charge dans la liste de fichiers - vos noms de fichier peuvent contenir toutes sortes d'espaces, de barres obliques inverses, de barres obliques inversées, de caractères génériques Shell, etc. - xargsL les traitera comme des caractères littéraux, aucun dommage.

En tant que fonctionnalité supplémentaire, xargsL va pas exécuter la commande une fois s'il n'y a pas d'entrée!

Notez la différence:

$ true | xargs echo no data
no data

$ true | xargsL echo no data # No output

Tous les arguments donnés à xargsL seront transmis à xargs.

Voici le script POSIX Shell "xargsL":

#! /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 "$0 failed!" >& 2' 0

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

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

$ chmod +x xargsL

le script là-bas pour le rendre exécutable.

1

J'ai utilisé réponse de Bill Star légèrement modifié sous Solaris:

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

Cela mettra des guillemets autour de chaque ligne. Je n'ai pas utilisé l'option '-l' même si cela aiderait probablement.

La liste de fichiers que j'allais bien pourrait avoir "-", mais pas de nouvelles lignes. Je n'ai pas utilisé le fichier de sortie avec d'autres commandes, car je souhaite vérifier ce qui a été trouvé avant de commencer à les supprimer massivement via xargs.

1

J'ai un peu joué avec cela, commencé à envisager de modifier xargs et me suis rendu compte que, dans le cas d'utilisation dont nous parlons ici, une simple réimplémentation dans Python est une meilleure idée.

D'une part, avoir environ 80 lignes de code pour l'ensemble du projet signifie qu'il est facile de comprendre ce qui se passe et, si un comportement différent est requis, vous pouvez simplement l'intégrer dans un nouveau script en moins de temps que nécessaire. une réponse quelque part comme Stack Overflow.

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

faire la copie de 203 fichiers à la fois. (Ici, 203 n'est qu'un espace réservé, bien sûr, et l'utilisation d'un nombre étrange tel que 203 indique clairement que ce nombre n'a pas d'autre signification.)

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

1
John Allsup

version Perl de bill_starr ne fonctionnera pas bien pour les nouvelles lignes intégrées (ne gère que les espaces). Pour ceux sur par exemple Solaris où vous n'avez pas les outils GNU, une version plus complète pourrait être (avec 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 sed corrigera vos lignes/espaces/tabulations incorporés.

1
rjb1

Si les versions de find et xarg sur 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 d'échapper aux espaces et aux guillemets pour xargs.

Testé sous AIX 5.3

1
Jan Ptáčník

Frame challenge - vous demandez comment utiliser xargs. La réponse est: vous n'utilisez pas xargs, parce que vous n'en avez pas besoin.

Le commentaire de user80168 décrit un moyen de le faire directement avec cp, sans appeler cp pour chaque fichier:

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

Cela fonctionne parce que:

  • le drapeau cp -t permet de donner le répertoire cible au début de cp, plutôt que près de la fin. De man cp:
   -t, --target-directory=DIRECTORY
         copy all SOURCE arguments into DIRECTORY
  • L’indicateur -- indique à cp de tout interpréter après comme un nom de fichier, et non comme un indicateur; ainsi, les fichiers commençant par - ou -- ne confondent pas cp; vous en avez toujours besoin, car les caractères -/-- sont interprétés par cp, tandis que tous les autres caractères spéciaux sont interprétés par le shell.

  • La variante find -exec command {} + fait essentiellement la même chose que xargs. De man find:

   -exec command {} +                                                     
         This  variant  of the -exec action runs the specified command on
         the selected files, but the command line is built  by  appending
         each  selected file name at the end; the total number of invoca‐
         matched  files.   The command line is built in much the same way
         that xargs builds its command lines.  Only one instance of  `{}'
         is  allowed  within the command, and (when find is being invoked
         from a Shell) it should be quoted (for example, '{}') to protect
         it  from  interpretation  by shells.  The command is executed in
         the starting directory.  If any invocation  returns  a  non-zero
         value  as exit status, then find returns a non-zero exit status.
         If find encounters an error, this can sometimes cause an immedi‐
         ate  exit, so some pending commands may not be run at all.  This
         variant of -exec always returns true.

En utilisant ceci directement dans find, cela évite le recours à un tuyau ou à une invocation de shell, de sorte que vous n'avez pas à vous soucier des caractères vicieux dans les noms de fichiers.

0
gerrit

Vous aurez peut-être besoin d'un répertoire grep Foobar comme:

find . -name "file.ext"| grep "FooBar" | xargs -i cp -p "{}" .
0
fred