Comment traiter chaque ligne reçue à la suite de la commande grep

152

J'ai un certain nombre de lignes récupérées à partir d'un fichier après avoir exécuté la commande grep comme suit:

var=`grep xyz abc.txt`

Disons que j'ai 10 lignes qui se composent de xyz en conséquence.

Maintenant, je dois traiter chaque ligne obtenue à la suite de la commande grep. Comment procéder pour cela?

XYZ_Linux
la source
6
Aucune des réponses ici ne mentionne le pouvoir de grep -oce genre de chose. Le -odrapeau ne rendra que le texte qui correspond, avec une correspondance par ligne de sortie. (Ce n'est pas exhaustif, donc echo aaa |grep 'a*'ne vous donne que "aaa" et omet les trois correspondances partielles "", "a" et "aa")
Adam Katz

Réponses:

269

L'un des moyens les plus simples est de ne pas stocker la sortie dans une variable, mais de la parcourir directement avec une boucle while / read.

Quelque chose comme:

grep xyz abc.txt | while read -r line ; do
    echo "Processing $line"
    # your code goes here
done

Il existe des variantes de ce schéma en fonction exactement de ce que vous recherchez.

Si vous avez besoin de changer des variables à l'intérieur de la boucle (et que ce changement soit visible à l'extérieur de celle-ci), vous pouvez utiliser la substitution de processus comme indiqué dans la réponse de fedorqui :

while read -r line ; do
    echo "Processing $line"
    # your code goes here
done < <(grep xyz abc.txt)
Tapis
la source
s'il n'y a pas de ligne avec xyz?
XYZ_Linux
Alors rien ne se passe, la boucle n'est pas exécutée.
Mat
13
Le problème avec cette méthode est que (à cause du tube) tout ce qui se trouve à l'intérieur de la boucle est dans un sous-shell, donc la définition de variables définies en dehors de la boucle pendant la boucle ne rend pas leurs valeurs disponibles après la boucle!
David Doria
3
@David: a fourni une alternative pour répondre à votre préoccupation. (fedorqui l'avait déjà abordé aussi.)
Mat
Pour les commandes dont la dernière ligne de sortie ne se termine pas par une nouvelle ligne, vous aurez besoin de: while read p || [[ -n $p ]]; do ...(emprunté à stackoverflow.com/questions/1521462/… )
Ohad Schneider
21

Vous pouvez faire la while readboucle suivante , qui sera alimentée par le résultat de la grepcommande en utilisant la soi-disant substitution de processus:

while IFS= read -r result
do
    #whatever with value $result
done < <(grep "xyz" abc.txt)

De cette façon, vous n'avez pas à stocker le résultat dans une variable, mais à "injecter" directement sa sortie dans la boucle.


Notez l'utilisation IFS=et read -rselon les recommandations de BashFAQ / 001: Comment puis-je lire un fichier (flux de données, variable) ligne par ligne (et / ou champ par champ)? :

L'option -r pour lire empêche l'interprétation de la barre oblique inverse (généralement utilisée comme paire de nouvelles lignes de barre oblique inverse, pour continuer sur plusieurs lignes ou pour échapper aux délimiteurs). Sans cette option, toutes les barres obliques inverses non échappées dans l'entrée seront supprimées. Vous devriez presque toujours utiliser l'option -r avec read.

Dans le scénario ci-dessus, IFS = empêche le découpage des espaces de début et de fin. Supprimez-le si vous voulez cet effet.

Concernant la substitution de processus, elle est expliquée dans la page bash hackers :

La substitution de processus est une forme de redirection où l'entrée ou la sortie d'un processus (une séquence de commandes) apparaît comme un fichier temporaire.

fedorqui 'Alors arrêtez de nuire'
la source
OK, j'ai frappé la forversion. J'ai essayé de faire une boucle "${$(grep xyz abc.txt)[@]}"comme dans stackoverflow.com/a/14588210/1983854 mais n'a pas pu. Alors je laisse juste la première version.
fedorqui 'SO arrêtez de nuire'
1
Vous ne pouvez pas appliquer l'expansion des paramètres à une substitution de commande (sauf si vous utilisez zsh, où ce type d'imbrication fonctionne probablement).
chepner
Un problème possible avec cet idiome est que si quelque chose dans la boucle essaie de lire à partir de l'entrée standard, il obtiendra une partie du fichier. Pour éviter cette possibilité, j'aime envoyer le fichier via le descripteur de fichier 3 plutôt que via stdin. Il suffit d'utiliser while IFS= read -r result <&3etdone 3< <(grep ...
Gordon Davisson
8

Je suggérerais d'utiliser awk au lieu de grep + autre chose ici.

awk '$0~/xyz/{ //your code goes here}' abc.txt

Julien Grenier
la source
1
Comment référeriez-vous la ligne trouvée complète //your code goes here?
user857990
2
@ user857990 La ligne entière est représentée par $ 0 dans AWK.
Julien Grenier
2

Sans aucune itération avec l'option grep --line-buffered:

your_command | grep --line-buffered "your search"

Exemple réel avec une sortie de commande de débogage de routeur Symfony PHP Framework, pour grep toutes les routes liées à "api":

php bin/console d:r | grep --line-buffered "api"
Nicolas Bonnici
la source
La seule solution qui fonctionne si your_command est de longue durée (comme tail -f some.log, dans mon cas) ...
Izkata
0

Souvent, l'ordre du traitement n'a pas d'importance. GNU Parallel est fait pour cette situation:

grep xyz abc.txt | parallel echo do stuff to {}

Si votre traitement ressemble plus à:

grep xyz abc.txt | myprogram_reading_from_stdin

et myprogramest lent alors vous pouvez exécuter:

grep xyz abc.txt | parallel --pipe myprogram_reading_from_stdin
Ole Tange
la source
0

Répétez les résultats de grep avec une boucle while / read. Comme:

grep pattern filename.txt | while read -r line ; do
    echo "Matched Line:  $line"
    # your code goes here
done
Km Abhi
la source
0

Pour ceux qui recherchent un monoplace:

grep xyz abc.txt | while read -r line; do echo "Processing $line"; done
Laurent
la source