Question

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 & nbsp; Système d'exploitation X 10.5.3 (Leopard) avec code BSD < > xargs .

Était-ce utile?

La solution

Vous pouvez combiner tout cela en une seule commande find :

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

Ceci 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 - transmis à cp l'empêche de traiter les fichiers commençant par - en tant qu'options.

Autres conseils

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

Je ne sais pas si grep prend en charge - null , ni si xargs prend en charge -0 , sur Léopard, mais sur GNU, tout va bien.

Le moyen le plus simple de réaliser ce que souhaite l'affiche originale consiste à remplacer le délimiteur de tout espace par un caractère de fin de ligne comme suit:

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

Cette option est plus efficace car elle ne lance pas " cp " plusieurs fois:

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

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. A partir de la page de manuel sed , " ...". Une esperluette ("` & amp; '' ") apparaissant dans la substitution est remplacée par la chaîne correspondant à RE ... " - dans ce cas, . * , la ligne entière.

Ceci résout l'erreur xargs: une citation non terminée .

Cette méthode fonctionne sur le Mac & nbsp; OS X & nbsp; v10.7.5 (Lion):

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

J'ai également testé la syntaxe exacte que vous avez postée. Cela a également bien fonctionné le 10.7.5.

N'utilisez pas xargs . C'est un programme soigné, mais cela ne va pas avec find face à des cas non triviaux.

Voici une solution portable (POSIX), c'est-à-dire qui ne nécessite pas find , xargs ou cp Extensions spécifiques à 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 la boîte à outils GNU.

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

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

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

Cherchez à utiliser l'option de ligne de commande --null pour xargs avec l'option -print0 dans find.

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
find | perl -lne 'print quotemeta' | xargs ls -d

Je pense que cela fonctionnera de manière fiable pour tous les caractères sauf le saut de ligne (et je soupçonne que si vous avez des sauts de ligne dans vos noms de fichiers, vous avez des problèmes encore plus graves). Il n’exige pas de GNU findutils, mais seulement de Perl, il devrait donc fonctionner à peu près partout.

J'ai constaté 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é dans "/ usr / pcapps".

La ligne de démarcation Perl entre " find " et " xargs " échappe / cite chaque espace afin "xargs". transmet tous les noms de fichiers contenant des espaces vides à & l; & l; " en argument unique.

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 POSIX spécification du comportement xargs 'standard'.

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

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, vu 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 être digne d’être publié - d’autant plus que GNU dispose d’installations au moins aussi performantes.

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)

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/

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 extrêmement attrayant. :

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

Ici, sed veillera à échapper les espaces et les guillemets pour xargs.

Testé sous AIX 5.3

J'ai créé un petit script portable appelé "xargsL". autour de " xargs " qui aborde 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.

Aucun guillemet n'est autorisé ou pris en charge dans la liste de fichiers - vos noms de fichier peuvent contenir toutes sortes d'espaces, de barres obliques inverses, de backticks, de caractères génériques de shell et autres, xargsL les traitera comme des caractères littéraux, aucun dommage.

En guise de bonus, xargsL n’exécutera pas la commande une fois s’il n’ya 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 " xargsL " Script shell 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 "<*> failed!" >& 2' 0

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

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

$ chmod + x xargsL

le script est là pour le rendre exécutable.

La version Perl de bill_starr ne fonctionnera pas bien pour les retours à la ligne incorporés (ne gère que les espaces). Pour ceux sur par exemple Si vous ne disposez pas des outils GNU sous Solaris, une version plus complète pourrait être utilisée (avec sed) ...

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

Ajustez les arguments find et grep ou les autres commandes selon vos besoins, mais sed corrigera vos nouvelles lignes / espaces / onglets incorporés.

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

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

Ceci 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 dans laquelle je me dirigeais pourrait toutefois comporter le signe "-", mais pas les 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.

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

Premièrement, avoir environ 80 lignes de code pour tout le processus signifie qu'il est facile de comprendre ce qui se passe et si un comportement différent est requis, vous pouvez simplement le transformer en un nouveau script en moins de temps que nécessaire. prend pour obtenir 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

pour copier 203 fichiers à la fois. (Ici, 203 n’est bien entendu qu’un espace réservé, et utiliser un nombre étrange tel que 203 montre 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-les en C ++ ou en C.

Vous devrez peut-être utiliser le répertoire grep Foobar comme suit:

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

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

Le comment par user80168 décrit une méthode permettant de le faire directement avec cp, sans appeler cp pour chaque fichier:

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

Cela fonctionne car:

  • le drapeau cp -t permet d'indiquer 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, de sorte que les fichiers commençant par - ou - ne confondez pas cp ; vous en avez toujours besoin, car les caractères - / - sont interprétés par cp , alors 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. A partir 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, ceci évite le recours à un canal ou à une invocation de shell, ce qui vous évite d'avoir à vous soucier des caractères désagréables dans les noms de fichiers.

Si vous utilisez Bash, vous pouvez convertir stdout en un tableau de lignes à l'aide de mapfile :

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

Les avantages sont les suivants:

  • C'est intégré, donc c'est plus rapide.
  • Exécuter la commande avec tous les noms de fichiers en une fois, donc c'est plus rapide.
  • Vous pouvez ajouter d'autres arguments aux noms de fichier. Pour cp , vous pouvez également:

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

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

Les inconvénients:

  • Peut-être ne convient-il pas correctement s'il y a trop de noms de fichiers. (La limite? Je ne sais pas, mais j’avais testé avec un fichier de liste de 10 & nbsp; MB comprenant plus de 10000 noms de fichiers sans aucun problème, sous Debian.

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

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top