Come elaborare ogni riga ricevuta a seguito del comando grep


152

Ho un numero di righe recuperate da un file dopo aver eseguito il comando grep come segue:

var=`grep xyz abc.txt`

Diciamo che ho ottenuto 10 righe che consiste di xyz come risultato.

Ora ho bisogno di elaborare ogni riga che ho ottenuto come risultato del comando grep. Come procedo per questo?


6
Nessuna delle risposte qui menziona il potere di grep -oquesto genere di cose. Il -oflag restituirà solo il testo corrispondente, con una corrispondenza per riga di output. (Non è esaustivo, quindi echo aaa |grep 'a*'ti dà solo "aaa" e omette le tre partite parziali "", "a" e "aa")
Adam Katz,

Risposte:


269

Uno dei modi più semplici non è quello di memorizzare l'output in una variabile, ma di iterarlo direttamente con un ciclo while / read.

Qualcosa di simile a:

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

Ci sono variazioni su questo schema a seconda di cosa stai cercando.

Se è necessario modificare le variabili all'interno del ciclo (e fare in modo che tale modifica sia visibile all'esterno di esso), è possibile utilizzare la sostituzione del processo come indicato nella risposta di fedorqui :

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

se non c'è linea con xyz?
XYZ_Linux

Quindi non succede nulla, il ciclo non viene eseguito.
Mat

13
Il problema con questo metodo è che (a causa della pipe) tutto all'interno del loop è in una subshell, quindi l'impostazione delle variabili definite al di fuori del loop durante il loop non rende i loro valori disponibili dopo il loop!
David Doria,

3
@ David: ha fornito un'alternativa per rispondere alle tue preoccupazioni. (Fedorqui lo aveva già affrontato.)
Mat,

Per i comandi la cui ultima riga di output non è terminata con una nuova riga, è necessario: while read p || [[ -n $p ]]; do ...(preso in prestito da stackoverflow.com/questions/1521462/… )
Ohad Schneider,

21

Puoi fare il while readciclo seguente , che sarà alimentato dal risultato del grepcomando usando la cosiddetta sostituzione di processo:

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

In questo modo, non è necessario archiviare il risultato in una variabile, ma "iniettare" direttamente il suo output nel loop.


Notare l'utilizzo IFS=e in read -rbase alle raccomandazioni contenute in BashFAQ / 001: Come posso leggere un file (flusso di dati, variabile) riga per riga (e / o campo per campo)? :

L'opzione -r per la lettura impedisce l'interpretazione della barra rovesciata (di solito utilizzata come coppia di newline di barra rovesciata, per continuare su più righe o per sfuggire ai delimitatori). Senza questa opzione, qualsiasi barra rovesciata senza caratteri di input nell'input verrà eliminata. Dovresti quasi sempre usare l'opzione -r con read.

Nello scenario sopra IFS = impedisce il taglio degli spazi bianchi iniziali e finali. Rimuovilo se vuoi questo effetto.

Per quanto riguarda la sostituzione del processo, è spiegato nella pagina degli hacker di bash :

La sostituzione del processo è una forma di reindirizzamento in cui l'input o l'output di un processo (alcune sequenze di comandi) vengono visualizzati come file temporaneo.


OK, ho colpito la forversione. Ho provato a fare un ciclo "${$(grep xyz abc.txt)[@]}"come in stackoverflow.com/a/14588210/1983854 ma non ci sono riuscito. Quindi lascio solo la prima versione.
fedorqui "SO smettere di danneggiare",

1
Non è possibile applicare l'espansione dei parametri a una sostituzione di comando (a meno che non si stia utilizzando zsh, dove probabilmente funziona quel tipo di annidamento).
Chepner,

Un possibile problema con questo idioma è che se qualcosa all'interno del ciclo prova a leggere dall'input standard, otterrà parte del file. Per evitare questa possibilità, mi piace inviare il file tramite il descrittore di file 3 anziché tramite stdin. Basta usare while IFS= read -r result <&3edone 3< <(grep ...
Gordon Davisson il

8

Suggerirei di usare awk invece di grep + qualcos'altro qui.

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


1
Come faresti riferimento alla linea trovata completa in //your code goes here?
user857990

2
@ user857990 L'intera riga è rappresentata come $ 0 in AWK.
Julien Grenier,

2

Senza alcuna iterazione con l'opzione grep --line-buffered:

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

Esempio di vita reale con un comando di debug del router Symfony PHP Framework in uscita, per grep tutte le rotte correlate a "api":

php bin/console d:r | grep --line-buffered "api"

L'unica soluzione che funziona se your_command è di lunga durata (come tail -f some.log, nel mio caso) ...
Izkata,

0

Spesso l'ordine del trattamento non ha importanza. GNU Parallel è fatto per questa situazione:

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

Se l'elaborazione è più simile a:

grep xyz abc.txt | myprogram_reading_from_stdin

ed myprogramè lento quindi puoi eseguire:

grep xyz abc.txt | parallel --pipe myprogram_reading_from_stdin

0

Scorrere i risultati grep con un ciclo while / read. Piace:

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

0

Per chi cerca un one-liner:

grep xyz abc.txt | while read -r line; do echo "Processing $line"; done
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.