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.)
C'est ce que fait xargs
.
... | xargs command
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
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
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.
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.
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
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.$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.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.
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.
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:
-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.-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.
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.
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