Exécuter une commande une fois par ligne d’entrée piped?

162

Je veux exécuter une commande java une fois pour chaque match de ls | grep pattern -. Dans ce cas, je pense que je pourrais le faire, find pattern -exec java MyProg '{}' \;mais je suis curieux à propos du cas général. Existe-t-il un moyen facile de dire "exécuter une commande une fois pour chaque ligne d'entrée standard"? (En poisson ou bash.)

Xodarap
la source

Réponses:

92

C'est ce que xargsfait.

... | xargs command
Keith
la source
25
Pas assez. printf "foo bar\nbaz bat" | xargs echo wheeva céder whee foo bar baz bat. Peut-être ajouter les options -Lou -n?
Jander
3
@ Jander La question était plutôt générale, alors j'ai donné l'outil général. Certes, vous devrez ajuster son comportement avec des options en fonction des circonstances.
Keith
4
... | tr '\ n' '\ 0' | xargs -0
vrdhn
7
comme "les circonstances spécifiques qui donnent la bonne réponse à la question". :)
mattdm le
7
Si vous voulez savoir comment faire cela avec xargs, voyez ma réponse ci-dessous.
Michael Goldshteyn
167

La réponse acceptée a la bonne idée, mais la clé est de passer xargsle -n1commutateur, ce qui signifie "Exécuter la commande une fois par ligne de sortie:"

cat file... | xargs -n1 command

Ou, pour un seul fichier d'entrée, vous pouvez éviter le tuyau de cattout et aller simplement avec:

<file xargs -n1 command
Michael Goldshteyn
la source
1
Il est également intéressant de la capacité xargsà ne pas fonctionner si stdinest vide: --no-run-if-empty -r: Si l'entrée standard ne contient pas de Non vides, ne pas courir la commande. Normalement, la commande est exécutée une fois, même s'il n'y a pas d'entrée. Cette option est une extension GNU.
Ronan Jouchet
4
Comment pouvez-vous accéder à la ligne à l'intérieur command?
BT le
C'est l'utilisation correcte de xargs. Sans -n1, cela ne fonctionne que sur les commandes qui traitent les listes de paramètres comme des invocations multiples, ce que tous ne font pas.
masterxilo
3
printf "foo bar \ nbaz bat" | xargs -n1 echo whee se divise en mots et non en lignes
Gismo Ranas
112

Dans Bash ou tout autre shell de style Bourne (ash, ksh, zsh,…):

while read -r line; do command "$line"; done

read -rlit une seule ligne à partir de l'entrée standard ( readsans -rinterpréter les barres obliques inverses, vous ne voulez pas cela). Ainsi, vous pouvez effectuer l'une des opérations suivantes:

$ command | while read -r line; do command "$line"; done  

$ while read -r line; do command "$line"; done <file
Steven D
la source
6
Quand j'ai essayé, tail -f syslog | grep -e something -e somethingelse| while read line; do echo $line; doneça n'a pas marché. Cela fonctionnait avec un fichier inséré dans la whileboucle, uniquement tail -favec grep, mais avec, mais pas avec les deux tuyaux. Donner la grepla --line-bufferedpossibilité fait le travail
Cela fonctionne aussi quand chaque ligne doit être envoyée à stdin:command | while read -r line; do echo "$line" | command ; done
Den
21

Je suis d'accord avec Keith, xargs est l'outil le plus général du travail.

J'utilise généralement une approche en 3 étapes.

  • faire les choses de base jusqu'à ce que vous ayez quelque chose avec lequel vous voudriez travailler
  • prépare la ligne avec awk pour obtenir la syntaxe correcte
  • puis laissez xargs l'exécuter, peut-être avec l'aide de bash.

Il existe des moyens plus petits et plus rapides, mais ces moyens fonctionnent presque toujours.

Un exemple simple:

ls | 
grep xls | 
awk '{print "MyJavaProg --arg1 42 --arg2 "$1"\0"}' | 
xargs -0 bash -c

les 2 premières lignes sélectionnent certains fichiers avec lesquels travailler, puis awk prépare une belle chaîne avec une commande à exécuter et quelques arguments, et $ 1 est la première colonne entrée dans le tube. Et enfin, je m'assure que xargs envoie cette chaîne à bash qui vient de l'exécuter.

C'est un peu exagéré, mais cette recette m'a aidé dans beaucoup d'endroits, car elle est très flexible.

Johan
la source
6
Notez que xargs -0l’octet nul sert de séparateur d’enregistrement. Votre déclaration empreinte awk devrait donc êtreprintf("MyJavaProg --args \"%s\"\0",$1)
glenn jackman le
@glenn: Oublié le caractère nul, mettra à jour la réponse
Johan
@Johan Ce n'est pas grave, mais si vous l'utilisez, awkvous pouvez le faire correspondre au modèle et ignorer grep , par exemple,ls | awk '/xls/ {print...
Eric Renouf
15

GNU Parallel est conçu pour ce type de tâches. L'utilisation la plus simple est:

cat stuff | grep pattern | parallel java MyProg

Regardez la vidéo d'introduction pour en savoir plus: http://www.youtube.com/watch?v=OpaiGYxkSuQ

Ole Tange
la source
1
Pas vraiment besoin de l' catici puisqu'on greppeut directement lire le fichier
Eric Renouf
1
Merci pour le lien, je ne suis pas forcément d'accord pour dire que c'est plus facile à lire, mais bon de savoir que cela a été envisagé malgré tout. Je voudrais seulement maintenant contester un peu que le lien ne s’applique pas réellement ici puisque l’alternative n’est pas vraiment < stuff grep patternmais est grep pattern stuffsans redirection ni cat requise du tout. Néanmoins, cela ne change pas matériellement votre argument et si vous pensez qu'il est plus clair de toujours utiliser les éléments d'un tuyau qui commence par cat, puis vous donner le pouvoir
Eric Renouf
8

En outre, while readboucle dans la coquille de poisson (je suppose que vous voulez coquille de poisson, compte tenu que vous avez utilisé étiquette de ).

command | while read line
    command $line
end

Peu de points à noter.

  • readne prend pas -rargument, et il n'interprète pas vos barres obliques inverses, afin de rendre l'utilisation la plus courante possible.
  • Vous n'avez pas besoin de citer $line, car contrairement à bash, le poisson ne sépare pas les variables par des espaces.
  • commandpar lui-même est une erreur de syntaxe (pour attraper une telle utilisation d'arguments fictifs). Remplacez-le par la commande réelle.
Konrad Borowski
la source
N'a pas whilebesoin d'être jumelé avec do& doneau lieu de end?
Aff
@aff Ceci concerne spécifiquement la coquille de poisson, qui a une syntaxe différente.
Konrad Borowski
Ah, c'est ce que le poisson signifie.
aff
6

Si vous devez contrôler exactement où l'argument d'entrée est inséré dans votre ligne de commande ou si vous devez le répéter plusieurs fois, utilisez-le xargs -I{}.

EXEMPLE 1

Créez une structure de dossier vide another_folderqui reflète les sous-dossiers du répertoire actuel:

    ls -1d ./*/ | xargs -I{} mkdir another_folder/{}
Exemple n ° 2

Appliquez une opération sur une liste de fichiers provenant de stdin, dans ce cas, effectuez une copie de chaque .htmlfichier en ajoutant une .bakextension:

    find . -iname "*.html" | xargs -I{} cp {} {}.bak

De la xargspage de manuel MacOS / BSD :

 -I replstr
         Execute utility for each input line, replacing one or more occurrences of
         replstr in up to replacements (or 5 if no -R flag is specified) arguments
         to utility with the entire line of input.  The resulting arguments, after
         replacement is done, will not be allowed to grow beyond 255 bytes; this is
         implemented by concatenating as much of the argument containing replstr as
         possible, to the constructed arguments to utility, up to 255 bytes.  The
         255 byte limit does not apply to arguments to utility which do not contain
         replstr, and furthermore, no replacement will be done on utility itself.
         Implies -x.

xargsPage de manuel Linux :

   -I replace-str
          Replace  occurrences of replace-str in the initial-
          arguments with names read from standard input.  Al
          so,  unquoted  blanks do not terminate input items;
          instead the separator  is  the  newline  character.
          Implies -x and -L 1.
ccpizza
la source
1

Lorsque je traite des entrées potentiellement non normalisées, j'aime voir le travail entier «épelé» ligne par ligne pour l'inspection visuelle avant de l'exécuter (en particulier lorsqu'il s'agit de quelque chose de destructeur comme le nettoyage de la boîte aux lettres des personnes).

Donc, ce que je fais est de générer une liste de paramètres (c'est-à-dire des noms d'utilisateurs), de le nourrir dans un fichier à la manière d'un enregistrement par ligne, comme ceci:

johndoe  
jamessmith  
janebrown  

Ensuite, j'ouvre la liste vimet la sélectionne avec des expressions de recherche et de remplacement jusqu'à obtenir une liste des commandes complètes devant être exécutées, comme ceci:

/bin/rm -fr /home/johndoe  
/bin/rm -fr /home/jamessmith 

Ainsi, si votre expression rationnelle est incomplète, vous verrez dans quelle commande auront des problèmes potentiels (c'est-à-dire /bin/rm -fr johnnyo connor). De cette façon, vous pouvez annuler votre expression rationnelle et l'essayer à nouveau avec une version plus fiable. Name mangling est connu pour cela, car il est difficile de prendre en charge tous les cas délicats tels que Van Gogh, O'Connors, St. Clair, Smith-Wesson.

Avoir set hlsearchest utile pour cela vim, car cela mettra en évidence tous les matchs, de sorte que vous puissiez facilement le repérer s'il ne correspond pas, ou les matchs de manière non intentionnelle.

Une fois que votre expression rationnelle est parfaite et qu'elle capture tous les cas que vous pouvez tester / penser, je la convertis généralement en une expression sed afin qu'elle puisse être entièrement automatisée pour une autre exécution.

Dans les cas où le nombre de lignes d’entrée vous empêche de procéder à une inspection visuelle, il est vivement recommandé d’envoyer la commande à l’écran (ou, mieux encore, un journal) avant son exécution. Par conséquent, s’il se produit une erreur, vous savez exactement quelle commande a été causée. ça échoue. Ensuite, vous pouvez revenir à votre expression rationnelle originale et ajuster une fois de plus.

Marcin
la source
0

Si un programme ignore le canal mais accepte les fichiers comme arguments, vous pouvez simplement le pointer vers le fichier spécial /dev/stdin.

Je ne connais pas bien Java, mais voici un exemple de la façon dont vous le feriez pour bash:

$ echo $'pwd \n cd / \n pwd' |bash /dev/stdin
/home/rolf
/

Le $ est nécessaire pour que bash se traduise \nen nouvelles lignes. Je ne sais pas pourquoi.

Rolf
la source
0

Je préfère ceci - permettant des commandes sur plusieurs lignes et un code clair

find -type f -name filenam-pattern* | while read -r F
do
  echo $F
  cat $F | grep 'some text'
done

ref https://stackoverflow.com/a/3891678/248616

Nam G VU
la source
0

Ici, un copypaste que vous pouvez utiliser immédiatement:

cat list.txt | xargs -I{} command parameter {} parameter

L'élément de la liste sera placé là où se trouve {} et le reste de la commande et des paramètres seront utilisés tels quels.

DustWolf
la source