Cómo realizar un grep multilínea

15

¿Cómo realizarías un grep para el texto que aparece en dos líneas?

Por ejemplo:

pbsnodes es un comando que uso que devuelve la utilización de un clúster de Linux

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

node2
    state = free
    procs = 4
    bar = foobar

node3
    state = busy
    procs = 8
    bar = foobar

Quiero determinar el número de procesos que coinciden con los nodos que están en estado 'libre'. Hasta ahora he podido determinar el "número de procesos" y "los nodos en estado libre", pero quiero combinarlos en un comando que muestre todos los procesos libres.

En el ejemplo anterior, la respuesta correcta sería 6 (2 + 4).

Lo que tengo

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

¿Cómo puedo buscar cada línea que lea 'procs = x', pero solo si la línea que está arriba dice 'estado = libre?

Spuder
fuente

Respuestas:

12

Si los datos siempre están en ese formato, simplemente podría escribirlos:

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

( RS=significa que los registros son párrafos ).

O:

awk -vRS= '/state *= *free/ && match($0, "procs *=") {
  n += substr($0,RSTART+RLENGTH)}; END {print n}'
Stéphane Chazelas
fuente
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)

ápice depredador
fuente
4

Aquí hay una forma de hacerlo usando pcregrep.

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

Ejemplo

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

Su formato de salida está preparado para el sorbo de párrafo 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 }'

Nota

Esto solo funciona porque la idea de Perl de un "párrafo" es un trozo de líneas no en blanco separadas por una o más líneas en blanco. Si no tuviera líneas en blanco entre las nodesecciones, esto no habría funcionado.

Ver también

Joseph R.
fuente
3

Si tiene datos de longitud fija (la longitud fija se refiere al número de líneas en un registro), sedpuede usar el Ncomando (varias veces), que une la siguiente línea al espacio del patrón:

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

debería darte resultados como:

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

Para la composición de registros variables (por ejemplo, con una línea de separación vacía), puede utilizar comandos de bifurcación ty b, pero awkes probable que lo lleve allí de una manera más cómoda.

Peterph
fuente
3

La implementación de GNU grepviene con dos argumentos para imprimir también las líneas antes ( -B) y después ( -A) de una coincidencia. Fragmento de la página de manual:

   -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.

Entonces, en su caso, tendría que buscar state = freey también imprimir la siguiente línea. Combinando eso con los fragmentos de tu pregunta, llegarás a algo así:

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

y un poco más corto:

usr@srv % pbsnodes | grep -A 1 'state = free' | awk '{ sum+=$3 } END { print sum }'
6
binfalse
fuente
awkhace coincidencia de patrones; no necesitas grep: ver la respuesta de Stephane
jasonwryan
Bueno, también sedcoincide el patrón. También puede usar perl, o php, o el idioma que prefiera. Pero al menos el titular de la pregunta solicitó grep multilínea ... ;-)
binfalse
Sí: pero viendo que estabas usando de awktodos modos ... :)
jasonwryan
0

... y aquí hay una solución Perl:

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

Puedes usar el awk getlinecomando:

$ 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
fuente