Comment utiliser> dans une commande xargs?

160

Je veux trouver une commande bash qui me permettra de grep chaque fichier dans un répertoire et d'écrire la sortie de ce grep dans un fichier séparé. Ma supposition aurait été de faire quelque chose comme ça

ls -1 | xargs -I{} "grep ABC '{}' > '{}'.out"

mais, pour autant que je sache, xargs n'aime pas les guillemets. Cependant, si je supprime les guillemets doubles, la commande redirige la sortie de la commande entière vers un seul fichier appelé '{}'. Out au lieu de vers une série de fichiers individuels.

Quelqu'un connaît-il un moyen de le faire en utilisant xargs? Je viens d'utiliser ce scénario grep comme exemple pour illustrer mon problème avec xargs, donc toutes les solutions qui n'utilisent pas xargs ne sont pas aussi applicables pour moi.

Jesse Shieh
la source

Réponses:

201

Ne faites pas l'erreur de faire ceci:

sh -c "grep ABC {} > {}.out"

Cela se cassera dans de nombreuses conditions, y compris les noms de fichiers géniaux et il est impossible de citer correctement. Votre {}doit toujours être un seul argument complètement séparé de la commande pour éviter les bogues d'injection de code. Ce que vous devez faire, c'est ceci:

xargs -I{} sh -c 'grep ABC "$1" > "$1.out"' -- {}

S'applique à xargsainsi que find.

À propos, n'utilisez jamais xargs sans l' -0option (sauf pour une utilisation interactive ponctuelle très rare et contrôlée où vous ne craignez pas de détruire vos données).

N'analyse pas non plus ls. Déjà. Utilisez le globbing ou à la findplace:http://mywiki.wooledge.org/ParsingLs

À utiliser findpour tout ce qui nécessite une récursivité et une simple boucle avec un glob pour tout le reste:

find /foo -exec sh -c 'grep "$1" > "$1.out"' -- {} \;

ou non récursif:

for file in *; do grep "$file" > "$file.out"; done

Notez l'utilisation correcte des guillemets.

lhunath
la source
Vote positif mais un regd de doute. ne pas utiliser xargssans -0: cela ne s'applique que lorsque vous canalisez findla sortie avec xargs, non? quand je fais xargs -a <input_file>comment utiliserais-je cela? La plupart des commandes aiment les grepsorties avec \net non. \0.Le seul moyen auquel je pense pour contourner ce trproblème est de l'utiliser à nouveau pour résoudre ce problème peut-être. Mais pourquoi est-il important de l'utiliser uniquement avec -0?
legends2k
3
@ legends2k parce que lorsque vous ne l'utilisez pas -0, xargsprendra vos noms de fichiers et cassera tous les espaces, guillemets et barres obliques inverses qu'ils contiennent. Vous devriez simplement oublier en xargstant qu'outil. Si vous avez des lignes, utilisez une boucle bash pour itérer les lignes while read line; do <command> "$REPLY"; done < file-with-linescommand | while ...
:,
1
Wow, je ne savais pas à ce sujet, merci pour le détail! Donc, pour la portabilité (puisque tous ne xargssont pas GNU), il xargsfaut éviter à moins que l'on puisse l'utiliser avec -0. Je vous remercie.
legends2k
1
Bien que j'apprécie l'explication détaillée de ce cas d'utilisation spécifique, la question concerne la redirection de la sortie de xargs, ce qui n'implique pas toujours l'analyse lsou l'utilisation sh -c. Cela ne répond pas du tout à la question, mais c'est le premier résultat Google pour la question, ne faisant qu'ajouter à la confusion.
pandasauce
1
@Ihunath, Salut, votre réponse fonctionne bien pour moi. Mais pourriez-vous donner des explications détaillées ou des liens sur xargs -I{} sh -c 'grep ABC "$1" > "$1.out"' -- {}? Surtout, les règles des guillemets intégrés (doubles) et le symbole "-" à la fin. Merci
Scott Yang
40

Une solution sans xargsest la suivante:

find . -mindepth 1 -maxdepth 1 -type f -exec sh -c "grep ABC '{}' > '{}.out'" \;

... et la même chose peut être faite avec xargs , il s'avère:

ls -1 | xargs -I {} sh -c "grep ABC '{}' > '{}.out'"

Edit : guillemets simples ajoutés après la remarque de lhunath .

Stephan202
la source
Il a dit qu'il voulait utiliser des xargs. J'ai posté une solution sans elle aussi, mais supprimée une fois que j'ai vu qu'il avait besoin de xargs.
Zifre
Vous avez raison. La raison pour laquelle j'ai publié ma réponse est qu'il vaut mieux avoir une solution alternative pour faire le travail que rien du tout. Il s'avère que cela m'a mis sur la bonne voie pour trouver la réponse désirée (c'est-à-dire l'astuce sh -c).
Stephan202
14

Je suppose que votre exemple n'est qu'un exemple et que vous pourriez avoir besoin> pour d'autres choses. GNU Parallel http://www.gnu.org/software/parallel/ peut être votre secours. Il ne nécessite pas de guillemets supplémentaires tant que vos noms de fichiers ne contiennent pas \ n:

ls | parallel "grep ABC {} > {}.out"

Si vous avez des noms de fichiers avec \ n:

find . -print0 | parallel -0 "grep ABC {} > {}.out"

En prime, vous obtenez les travaux exécutés en parallèle.

Regardez les vidéos d'introduction pour en savoir plus: http://pi.dk/1

L'installation de 10 secondes essaiera de faire une installation complète; si cela échoue, une installation personnelle; si cela échoue, une installation minimale:

$ (wget -O - pi.dk/3 || lynx -source pi.dk/3 || curl pi.dk/3/ || \
   fetch -o - http://pi.dk/3 ) > install.sh
$ sha1sum install.sh | grep 3374ec53bacb199b245af2dda86df6c9
12345678 3374ec53 bacb199b 245af2dd a86df6c9
$ md5sum install.sh | grep 029a9ac06e8b5bc6052eac57b2c3c9ca
029a9ac0 6e8b5bc6 052eac57 b2c3c9ca
$ sha512sum install.sh | grep f517006d9897747bed8a4694b1acba1b
40f53af6 9e20dae5 713ba06c f517006d 9897747b ed8a4694 b1acba1b 1464beb4
60055629 3f2356f3 3e9c4e3c 76e3f3af a9db4b32 bd33322b 975696fc e6b23cfb
$ bash install.sh

Si vous devez le déplacer vers un serveur sur lequel GNU Parallel n'est pas installé, essayez parallel --embed.

Ole Tange
la source