RHCSA – Use grep and regular expressions to analyze text

Queste sono alcune note sull’utilizzo di grep basate sulla pagina man e sull’esperienza personale. I test sono stati fatti su una Scientific Linux 6 (prima ancora che uscisse CentOS 6).

Sintassi di base (la riga che inizia per # è un commento):

# grep pattern file(s)
grep '127.0.0.1' /etc/*
/etc/hosts:127.0.0.1 localhost localhost.localdomain
[...]

Di default, grep assume stdin come input file, quindi è possibile usarlo in cascata sull’output di altri comandi:

# comando1 | grep pattern
ifconfig | grep 'inet addr'
  inet addr:127.0.0.1  Mask:255.0.0.0

Con l’opzione -e è possibile specificare uno o più pattern:

# grep -e pattern1 -e pattern2 file(s)
grep -e Linux -e '127.0.0.1' /etc/*
/etc/hosts:127.0.0.1   localhost localhost.localdomain
[...]
/etc/redhat-release:Scientific Linux release 6.0 (Carbon)

Con l’opzione -i si ignorano maiuscole e minuscole (rallenta di molto l’esecuzione):

# creo un file contenente due righe, la prima contiene 'Foo'
echo 'Foo' > casetest.txt
# la seconda contiene 'foo' (minuscolo)
echo 'foo' >> casetest.txt
# con un grep normale, usando 'foo' (minuscolo) come pattern ottengo
grep foo casetest.txt
foo
# usando l'opzione -i invece
grep -i foo casetest.txt
Foo
foo

Con l’opzione -v si inverte l’output (stampa le righe che non corrispondono al pattern):

# usiamo il file di prima, aggiungiamo 'Bar'
echo Bar >> casetest.txt
# grep -v pattern file(s)
grep -v foo casetest.txt
# attenzione: non abbiamo usato -i, quindi 'Foo' viene trattato diversamente
# da 'foo' e, non facendo match sul pattern, viene stampato
Foo
Bar

Con -c, invece di scrivere le righe che corrispondono al pattern, grep stampa un conteggio di quante volte il pattern è contenuto in uno o più file:

# grep -c pattern file(s)
grep -c Linux /etc/*
/etc/dhcp:0
[...]
/etc/redhat-release:1

Con le opzioni -l e -L si ottengono delle liste di file che contengono o non contengono un pattern:

# grep -l pattern file(s)
grep -l Linux /etc/*
/etc/grub.conf
/etc/issue
/etc/issue.net
[...]

# grep -L pattern file(s)
grep -L Linux /etc/*
/etc/dhcp
/etc/yum.conf
[...]

L’opzione -H dice di stampare sempre il nome del file di fianco all’eventuale match. E’ il comportamento di default quando ci sono molti files (vedere esempi precedenti).

L’opzione -n attiva la scrittura del numero di riga di fianco ad ogni match:

# grep -n pattern file(s)
grep -n Linux /etc/*
/etc/grub.conf:14:title Scientific Linux (2.6.32-71.18.2.el6.x86_64)
[...]

Con l’opzione -Z grep userà un null byte come separatore dei file. Utile con -l o -L e in combinazione con altri comandi che possono usare in ingresso una lista di nomi di file separati da null, ad esempio xargs -0. Serve per gestire nomi di file contenenti spazi o altri caratteri speciali.

# grep -lZ pattern file(s) | comando -opzione_per_input_null
grep -lZ Linux /etc/* | xargs -0 ls -lh
lrwxrwxrwx. 1 root root   22  4 feb 17:04 /etc/grub.conf -> ../boot/grub/grub.conf
-rw-r--r--. 1 root root   58 24 feb 21:12 /etc/issue
-rw-r--r--. 1 root root   57 24 feb 21:12 /etc/issue.net
-rw-r--r--. 1 root root 1,9K 23 nov 19:53 /etc/mail.rc
-rw-r--r--. 1 root root 4,9K 23 nov 22:37 /etc/oddjobd.conf
lrwxrwxrwx. 1 root root   15  4 feb 17:00 /etc/rc.sysinit -> rc.d/rc.sysinit
-rw-r--r--. 1 root root   38 24 feb 21:12 /etc/redhat-release
-rw-r--r--. 1 root root 6,4K 24 nov 07:52 /etc/smartd.conf
-rw-r--r--. 1 root root  822 24 nov 23:50 /etc/sysctl.conf
lrwxrwxrwx. 1 root root   14 21 mar 05:00 /etc/system-release -> redhat-release

Le opzioni di contesto servono a specificare quante righe di contesto estrarre oltre alla riga che fa il match del pattern. Con -A si indicano le righe dopo (After), con -B quelle prima (Before) e con -C un numero di righe simmetrico sia prima che dopo (Context).

# stampa due righe prima e 5 dopo il match
grep -B2 -A5 Linux /etc/*
[...]
--
/etc/smartd.conf-# the 3w-xxxx driver. Start long self-tests Sundays between 1-2, 2-3, 3-4,
/etc/smartd.conf-# and 4-5 am.
/etc/smartd.conf:# NOTE: starting with the Linux 2.6 kernel series, the /dev/sdX interface
/etc/smartd.conf-# is DEPRECATED.  Use the /dev/tweN character device interface instead.
/etc/smartd.conf-# For example /dev/twe0, /dev/twe1, and so on.
/etc/smartd.conf-#/dev/sdc -d 3ware,0 -a -s L/../../7/01
/etc/smartd.conf-#/dev/sdc -d 3ware,1 -a -s L/../../7/02
/etc/smartd.conf-#/dev/sdc -d 3ware,2 -a -s L/../../7/03
--
[...]

L’opzione -r (o -R) attiva la ricerca ricorsiva nei percorsi indicati:

# grep -r pattern path(s)
grep -r Linux /etc/*
[...]

L’opzione -z permette di gestire liste di file separate da byte null (vedi -Z).

Pattern e regular expressions

I pattern da utilizzare con grep possono essere semplici stringhe, oppure espressioni regolari che permettono di avere il match su più stringhe.

# grep -e pattern1 -e pattern2 file(s)
grep -e 'foo' -e 'Foo' casetest.txt
Foo
foo
grep -e '[Ff]oo' casetest.txt
Foo
foo
grep -e '.oo' casetest.txt
Foo
foo

Abbiamo visto due sintassi basate su regular expression: nella prima si utilizza una bracket expression, cioè una lista di caratteri contenuti tra parentesi quadre. Nella seconda, invece, abbiamo utilizzato il punto (.) che farà il match con qualsiasi carattere.

Nel caso delle bracket expression, potremo specificare tutte le lettere (maiuscole e minuscole) che potranno soddisfare l’espressione, oppure invertire il controllo usando il circonflesso come primo carattere della bracket expression:

echo aoo >> casetest.txt
grep -e '[^abc]' casetest.txt
Foo
foo
grep -e '.oo' casetest.txt
Foo
foo
aoo

Attenzione: se il circonflesso appare a inizio riga, ma non all’interno di parentesi quadre, indica che l’espressione deve cominciare a inizio riga.

echo snafooz >> casetest.txt
grep -e '^foo' casetest.txt
foo
grep -e 'foo' casetest.txt
foo
snafooz

L’opposto del circonflesso è il dollaro, per trovare stringhe a fine riga:

grep -e 'foo$' casetest.txt
foo
grep -e 'foo' casetest.txt
foo
snafooz

In questo caso, foo è l’unica stringa della riga, quindi è sia a inizio che a fine riga, per verificarlo possiamo unire i controlli:

grep -e '^foo$' casetest.txt
foo

Tornando alle bracket expression, è possibile utilizzare un segno meno (-) per creare un range, per esempio potremmo voler fare il match di tutti i numeri:

seq 100 | grep -e '[0-9]7'
17
27
37
47
57
67
77
87
97

In questo caso seq ha generato la lista dei numeri da 1 a 100. L’espressione regolare diceva di fare il match solo di quelli che contenevano un numero da 0 a 9 seguito dal numero 7. Di default seq ha generato i numeri da 1 a 10 senza mettere uno zero prima del numero, quindi il 7 è stato saltato da grep, mentre tutti gli altri soddisfano l’espressione regolare e sono stati riportati nell’output.

Esistono anche espressioni speciali che indicano particolari classi di caratteri:

  • [:alpha:] indica tutti i caratteri alfabetici (equivale al doppio range [A-Za-z], tranne per il fatto che quest’ultimo può essere influenzato dal “locale” impostato)
  • [:digit:] tutti i numeri (equivale al range [0-9])
  • [:lower:] e [:upper:] rispettivamente i caratteri lowercase ([a-z]) e uppercase ([A-Z])
  • [:space:] i caratteri vuoti (spazio, tab, a capo)

Per una lista completa si veda il manuale.

echo '1foo' | grep '[[:digit:]][[:alpha:]]'
1foo
echo '1foo' | grep '[[:digit:]][[:digit:]][[:alpha:]]'
echo '1foo' | grep '[[:digit:]][[:alpha:]][[:alpha:]]'
1foo

Attenzione: le parentesi quadre sono state raddoppiate perché quelle più esterne iniziano la bracket expression, mentre quelle interne fanno parte del simbolo che rappresenta la classe (es. [:digit:]).

Le espressioni regolari (singoli caratteri, bracket expression, carattere punto) possono essere seguite da operatori di ripetizione, che specificano quante volte si deve ripetere l’espressione che li precede.

Il primo operatore di ripetizione è il carattere punto interrogativo (?) che indica che l’espressione che lo precede dev’essere assente o presente una sola volta, ad esempio:

echo forever >> casetest.txt
grep -E '[Ff]?oo' casetest.txt
Foo
foo
aoo
snafooz

Come potete vedere, anche aoo fa il match, perché soddisfa le due o consecutive anche se non soddisfa la presenza (opzionale, grazie al punto interrogativo) della f. Da notare che abbiamo utilizzato l’opzione -E per attivare le regexp (regular expression) estese.

Il secondo operatore è l’asterisco (*), questo modifica l’espressione precedente indicando che può essere presente zero o più volte:

grep -E 'sn.*z' casetest.txt
snafooz

In questo caso abbiamo usato il carattere jolly . unito all’asterisco per specificare “qualsiasi carattere, ripetuto qualsiasi numero di volte”.

L’operatore più (+), invece, specifica che l’espressione compaia una o più volte:

grep -E 'o+' casetest.txt
Foo
foo
aoo
snafooz
forever

Come vedete, c’è stato il match sia di forever che di foo.

Esiste un altro operatore che permette di specificare in modo raffinato il numero di ripetizioni: tra parentesi graffe ({}) si può specificare il valore preciso {n} o un range {n,m} o un numero minimo {n,} o massimo {,m} di volte. Per esempio, proviamo a trovare tutti gli indirizzi IP in /etc/:

grep -E '([[:digit:]]+\.){3}[[:digit:]]+'
/etc/hosts:127.0.0.1   localhost localhost.localdomain
/etc/networks:default 0.0.0.0
/etc/networks:loopback 127.0.0.0
/etc/networks:link-local 169.254.0.0

Spieghiamo l’espressione regolare: cercherà uno o più numeri ([[:digit:]]+) seguiti da un punto (\.) e questo gruppo (raggruppato tra parentesi tonde) deve ripetersi tre volte ({3}) ed essere seguito da un altro gruppo di uno o più numeri.

È possibile anche creare alternative tra due espressioni, separandole con un pipe (|):

grep -E 'Foo|Bar' casetest.txt
Foo
Bar
Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s