Comment effectuer un grep multi-lignes

15

Comment feriez-vous un grep pour du texte qui apparaît sur deux lignes?

Par exemple:

pbsnodes est une commande que j'utilise qui renvoie l'utilisation d'un cluster Linux

root$ pbsnodes
node1
    state = free
    procs = 2
    bar = foobar

node2
    state = free
    procs = 4
    bar = foobar

node3
    state = busy
    procs = 8
    bar = foobar

Je veux déterminer le nombre de procs qui correspondent aux noeuds qui sont à l'état «libre». Jusqu'à présent, j'ai pu déterminer le "nombre de procs" et "les nœuds à l'état libre", mais je veux les combiner en une seule commande qui montre tous les procs libres.

Dans l'exemple ci-dessus, la bonne réponse serait 6 (2 + 4).

Ce que j'ai

root$ NUMBEROFNODES=`pbsnodes|grep 'state = free'|wc -l`
root$ echo $NUMBEROFNODES
2

root$ NUMBEROFPROCS=`pbsnodes |grep "procs = "|awk  '{ print $3 }' | awk '{ sum+=$1 } END { print sum }'`
root$ echo $NUMBEROFPROCS
14

Comment puis-je rechercher chaque ligne qui lit «procs = x», mais uniquement si la ligne au-dessus indique «state = free»?

spuder
la source

Réponses:

12

Si les données sont toujours dans ce format, vous pouvez simplement les écrire:

awk -vRS= '$4 == "free" {n+=$7}; END {print n}'

( RS=signifie que les enregistrements sont des paragraphes ).

Ou:

awk -vRS= '/state *= *free/ && match($0, "procs *=") {
  n += substr($0,RSTART+RLENGTH)}; END {print n}'
Stéphane Chazelas
la source
5
$ pbsnodes
node1
    state = free
    procs = 2
    bar = foobar

node2
    state = free
    procs = 4
    bar = foobar

node3
    state = busy
    procs = 8
    bar = foobar
$ pbsnodes | grep -A 1 free
    state = free
    procs = 2
--
    state = free
    procs = 4
$ pbsnodes | grep -A 1 free | grep procs | awk '{print $3}'
2
4
$ pbsnodes | grep -A 1 free | grep procs | awk '{print $3}' | paste -sd+ 
2+4
$ pbsnodes | grep -A 1 free | grep procs | awk '{print $3}' | paste -sd+ | bc 
6

https://en.wikipedia.org/wiki/Pipeline_(Unix)

apex_predator
la source
4

Voici une façon de le faire en utilisant pcregrep.

$ pbsnodes | pcregrep -Mo 'state = free\n\s*procs = \K\d+'
2
4

Exemple

$ pbsnodes | \
    pcregrep -Mo 'state = free\n\s*procs = \K\d+' | \
    awk '{ sum+=$1 }; END { print sum }'
6
slm
la source
3

Votre format de sortie est amorcé pour le slurp de paragraphe de Perl:

pbsnodes|perl -n00le 'BEGIN{ $sum = 0 }
                 m{
                   state \s* = \s* free \s* \n 
                   procs \s* = \s* ([0-9]+)
                 }x 
                    and $sum += $1;
                 END{ print $sum }'

Remarque

Cela ne fonctionne que parce que l'idée de Perl d'un "paragraphe" est un bloc de lignes non vides séparées par une ou plusieurs lignes vides. Si vous n'aviez pas de lignes vides entre les nodesections, cela n'aurait pas fonctionné.

Voir également

Joseph R.
la source
3

Si vous avez des données de longueur fixe (longueur fixe faisant référence au nombre de lignes dans un enregistrement), sedvous pouvez utiliser la Ncommande (plusieurs fois), qui joint la ligne suivante à l'espace de motif:

sed -n '/^node/{N;N;N;s/\n */;/g;p;}'

devrait vous donner une sortie comme:

node1;state = free;procs = 2;bar = foobar
node2;state = free;procs = 4;bar = foobar
node3;state = busy;procs = 8;bar = foobar

Pour une composition d'enregistrement variable (par exemple avec une ligne de séparation vide), vous pouvez utiliser des commandes de branchement tet b, mais awkvous y arriverez probablement de manière plus confortable.

peterph
la source
3

L'implémentation GNU de grepvient avec deux arguments pour afficher également les lignes avant ( -B) et après ( -A) une correspondance. Extrait de la page de manuel:

   -A NUM, --after-context=NUM
          Print NUM lines of trailing context after matching lines.  Places a line containing  a  group  separator  (--)  between  contiguous  groups  of  matches.   With  the  -o  or
          --only-matching option, this has no effect and a warning is given.

   -B NUM, --before-context=NUM
          Print  NUM  lines  of  leading  context  before  matching  lines.   Places  a  line  containing  a group separator (--) between contiguous groups of matches.  With the -o or
          --only-matching option, this has no effect and a warning is given.

Donc, dans votre cas, vous devrez rechercher state = freeet également imprimer la ligne suivante. En combinant cela avec les extraits de votre question, vous arriverez à quelque chose comme ça:

usr@srv % pbsnodes | grep -A 1 'state = free' | grep "procs = " | awk  '{ print $3 }' | awk '{ sum+=$1 } END { print sum }'
6

et un peu plus court:

usr@srv % pbsnodes | grep -A 1 'state = free' | awk '{ sum+=$3 } END { print sum }'
6
binfalse
la source
awkfait la correspondance des motifs; vous n'avez pas besoin grep: voir la réponse de Stéphane
jasonwryan
Eh bien, sedla correspondance des motifs est-elle également possible. Vous pouvez également utiliser perl, ou php, ou la langue que vous préférez. Mais au moins le titre de la question demandait une grep multi-lignes ... ;-)
binfalse
Oui, mais en voyant que vous utilisiez de awktoute façon ... :)
jasonwryan
0

... et voici une solution Perl:

pbsnodes | perl -lne 'if (/^\S+/) { $node = $& } elsif ( /state = free/ ) { print $node }'
reinierpost
la source
0

Vous pouvez utiliser la awk getlinecommande:

$ pbsnodes | awk 'BEGIN { freeprocs = 0 } \
                  $1=="state" && $3=="free" { getline; freeprocs+=$3 } \
                  END { print freeprocs }'

De man awk :

   getline               Set $0 from next input record; set NF, NR, FNR.

   getline <file         Set $0 from next record of file; set NF.

   getline var           Set var from next input record; set NR, FNR.

   getline var <file     Set var from next record of file.

   command | getline [var]
                         Run command piping the output either into $0 or var, as above.

   command |& getline [var]
                         Run  command  as a co-process piping the output either into $0 or var, as above.  Co-processes are a
                         gawk extension.
Skippy le Grand Gourou
la source