web-dev-qa-db-fra.com

Exécuter une commande une fois par ligne d'entrée canalisée?

Je veux exécuter une commande Java une fois pour chaque correspondance de ls | grep pattern -. Dans ce cas, je pense que je pourrais faire find pattern -exec Java MyProg '{}' \; mais je suis curieux de connaître le cas général - existe-t-il un moyen simple de dire "exécuter une commande une fois pour chaque ligne d'entrée standard"? (En poisson ou en bash.)

176
Xodarap

C'est ce que fait xargs.

... | xargs command
98
Keith

La réponse acceptée a la bonne idée, mais la clé est de passer xargs le -n1 switch, 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 complètement le tube de cat et aller avec:

<file xargs -n1 command
193
Michael Goldshteyn

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

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

read -r lit une seule ligne à partir de l'entrée standard (read sans -r interprète les barres obliques inverses, vous ne voulez pas ça). 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
117
Steven D

Je suis d'accord avec Keith, xargs est l'outil le plus général pour le 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 aimeriez travailler
  • préparer 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 cela fonctionne 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 chaîne Nice avec une commande à exécuter et quelques arguments et $ 1 est la première entrée de colonne du 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.

22
Johan

GNU Parallel est fait pour ce genre 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

15
Ole Tange

Aussi, while read boucle dans la coquille de poisson (je suppose que vous voulez une coquille de poisson, étant donné que vous avez utilisé la balise fish ).

command | while read line
    command $line
end

Quelques points à noter.

  • read ne prend pas -r, et il n'interprète pas vos barres obliques inverses, afin de faciliter la plupart des cas d'utilisation courants.
  • Vous n'avez pas besoin de citer $line, contrairement à bash, fish ne sépare pas les variables par des espaces.
  • command est en soi une erreur de syntaxe (pour intercepter une telle utilisation d'arguments d'espace réservé). Remplacez-le par la vraie commande.
10
Konrad Borowski

Lorsque je traite des entrées potentiellement non désinfectées, j'aime voir le travail entier `` épelé '' ligne par ligne pour une inspection visuelle avant de l'exécuter (surtout quand c'est quelque chose de destructeur comme le nettoyage des boîtes aux lettres des gens).

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

johndoe  
jamessmith  
janebrown  

Ensuite, j'ouvre la liste dans vim, et la modifie avec des expressions de recherche et de remplacement jusqu'à ce que j'obtienne une liste de commandes complètes qui doivent être exécutées, comme ceci:

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

De cette façon, si votre regex est incomplète, vous verrez dans quelle commande auront des problèmes potentiels (ie. /bin/rm -fr johnnyo connor). De cette façon, vous pouvez annuler votre regex et réessayer avec une version plus fiable de celui-ci. Le changement de nom est connu pour cela, car il est difficile de s'occuper de tous les cas Edge comme Van Gogh, O'Connors, St. Clair, Smith-Wesson.

Ayant set hlsearch est utile pour le faire dans vim, car il mettra en évidence toutes les correspondances, de sorte que vous pouvez facilement repérer s'il ne correspond pas, ou correspond d'une manière non voulue.

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

Pour les cas où le nombre de lignes d'entrée vous empêche d'effectuer une inspection visuelle, je vous recommande fortement de faire écho de la commande à l'écran (ou mieux encore, un journal) avant qu'elle ne s'exécute, donc si elle fait une erreur, vous savez exactement quelle commande a causé échouer. Ensuite, vous pouvez revenir à votre regex d'origine et ajuster une fois de plus.

1
Marcin

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 le {} et le reste de la commande et des paramètres seront utilisés tels quels.

1
DustWolf

Ceci est possible avec xargs, comme d'autres réponses l'ont indiqué. Nous devons distinguer deux spécificités dans la partie "une fois par ligne" de la question:

  1. Une fois: utilisez -n 1, cela garantit que la commande est invoquée une seule fois pour chaque argument. Cependant, par défaut, xargs suppose que les arguments sont séparés par des espaces - cela se cassera une fois que les fichiers contiennent des espaces.
  2. Par ligne: utilisez -d '\n', ou prétraitez l'entrée avec tr '\n' '\0' et utilise -0. Cela rend la commande robuste contre les espaces en entrée.

La dernière ligne de commande devient alors:

.... | xargs -n 1 -d '\n' <command>

ou (avec tr)

.... | tr '\n' '\0' | xargs -n 1 -0 <command>

Si votre commande peut traiter plusieurs arguments à la fois (comme grep ou sed), vous pouvez omettre -n 1 pour accélérer l'opération dans de nombreux cas.

0
krlmlr

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 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 traduise \n dans de nouvelles lignes. Je ne sais pas pourquoi.

0
Rolf

Je préfère cela - permettant des commandes multi-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

0
Nam G VU