Comment utiliser xargs pour copier des fichiers comportant des espaces et des guillemets dans leurs noms?

J’essaie de copier un tas de fichiers sous un répertoire et un certain nombre de fichiers ont des espaces et des guillemets simples dans leurs noms. Lorsque j’essaie de créer une chaîne avec find et grep avec xargs , j’obtiens l’erreur suivante:

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

Des suggestions pour un usage plus robuste de xargs?

Ceci est sous Mac OS X 10.5.3 (Leopard) avec BSD xargs .

Vous pouvez combiner tout cela en une seule commande find :

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

Cela va 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.

Remarque: L’indicateur -- transmis à cp empêche de traiter les fichiers commençant par - comme options.

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 est bon.

Ceci est plus efficace car il ne lance pas “cp” plusieurs fois:

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

La manière la plus simple de faire ce que l’affiche originale veut, c’est de changer le délimiteur de n’importe quel espace en un caractère de fin de ligne comme celui-ci:

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

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 substituer chaque ligne d’entrée par la même ligne, mais entourée de guillemets doubles. A partir de la page de manuel sed , ” … Une esperluette (` `& ”) apparaissant dans le remplacement est remplacée par la chaîne correspondant au RE … ” – dans ce cas .* , La ligne entière.

Cela résout le xargs: unterminated quote erreur de xargs: unterminated quote .

Cette méthode fonctionne sous Mac OS X 10.7.5 (Lion):

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

J’ai également testé la syntaxe exacte que vous avez publiée. Cela a également bien fonctionné sur 10.7.5.

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

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

Il traitera correctement les fichiers et les répertoires avec des espaces incorporés, des nouvelles lignes ou autres, et sera plus efficace (c.-à-d. Plus rapide ) que les réponses acceptées et la plupart sinon toutes les autres.

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 la recherche, 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, à l’exception du saut de ligne (et je suppose que si vous avez des sauts de ligne dans vos noms de fichiers, vous rencontrerez des problèmes plus graves). Il ne nécessite pas GNU findutils, juste Perl, il devrait donc fonctionner pratiquement n’importe où.

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é dans “/ usr / pcapps”.

Le line-line de Perl entre “find” et “xargs” échappe / cite chaque espace vide, de sorte que “xargs” passe tout nom de fichier avec des blancs incorporés à “ls” en tant qu’argument unique.

Sachez que la plupart des options présentées dans d’autres réponses ne sont pas standard sur les plates-formes qui n’utilisent 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, qui exécute la commande au moins une fois, même sans aucune saisie, est une nuisance.

J’ai écrit ma propre version privée de xargs (xargl) pour traiter les problèmes d’espace dans les noms (seules les nouvelles lignes sont séparées – bien que la combinaison “find … -print0” et “xargs -0” soit plutôt soignée, car les noms de fichiers ne peuvent pas contient des caractères ASCII NUL ‘\ 0’ Mon xargl n’est pas aussi complet qu’il le faudrait pour être publié – d’autant plus que GNU possède des fonctionnalités au moins aussi bonnes.

Si les versions find et xarg de votre système ne prennent pas en charge les -print0 et -0 (par exemple, AIX find et xargs), vous pouvez utiliser ce code très intéressant:

  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é sur AIX 5.3

J’ai créé un petit script d’encapsulation portable appelé “xargsL” autour de “xargs” qui résout la plupart des problèmes.

Contrairement à xargs, xargsL accepte un chemin par ligne. Les chemins d’access peuvent contenir n’importe quel caractère sauf (évidemment) newline ou NUL bytes.

Aucun citation n’est autorisé ou pris en charge dans la liste des fichiers – vos noms de fichiers peuvent contenir toutes sortes d’espaces, des barres obliques inversées, des backticks, des caractères génériques de shell, etc. – xargsL les traitera en tant que caractères littéraux.

En tant que fonctionnalité supplémentaire, xargsL n’exécutera pas 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 

Tout argument donné à xargsL sera transmis à xargs.

Voici le script shell POSIX “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. # Dissortingbution 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 

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

$ chmod +x xargsL

le script pour le rendre exécutable.

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

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

ajustez les arguments find et grep ou d’autres commandes selon vos besoins, mais sed réglera vos nouvelles lignes / espaces / tabulations incorporées.

J’ai utilisé la réponse de Bill Star légèrement modifiée sur 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’, mais 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 veux revoir ce qui a été trouvé avant de commencer à les supprimer massivement via xargs.

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) 

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

D’une part, avoir environ 80 lignes de code pour tout cela signifie qu’il est facile de comprendre ce qui se passe, et si un comportement différent est requirejs, vous pouvez simplement le pirater dans un nouveau script en moins de temps que nécessaire. une réponse sur quelque chose 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 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 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.

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

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

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

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

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

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

Les avantages sont les suivants:

  • C’est intégré, donc c’est plus rapide.
  • Exécutez la commande avec tous les noms de fichiers en une seule fois pour accélérer la procédure.
  • Vous pouvez append d’autres arguments aux noms de fichiers. Pour cp , vous pouvez aussi:

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

    Cependant, certaines commandes ne disposent pas d’une telle fonctionnalité.

Les désavantages:

  • Peut-être ne pas bien évoluer s’il y a trop de noms de fichiers. (La limite? Je ne sais pas, mais j’avais testé avec un fichier liste de 10 Mo qui inclut plus de 10000 noms de fichiers sans problème, sous Debian)

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