Como executar uma multi linha grep

15

Como você faria um grep para o texto que aparece em duas linhas?

Por exemplo:

pbsnodes é um comando que eu uso que retorna a utilização de um 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

Eu quero determinar o número de procs que correspondem aos nós que estão no estado 'free'. Até agora eu tenho sido capaz de determinar o "número de procs" e "os nós no estado livre", mas eu quero combiná-los em um comando que mostra todos os procs livres.

No exemplo acima, a resposta correta seria 6 (2 + 4).

O que eu tenho

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

Como posso pesquisar por todas as linhas que lêem "procs = x", mas somente se a linha acima dele for "state = free?

    
por spuder 30.09.2013 / 23:00

8 respostas

12

Se os dados estiverem sempre nesse formato, você pode simplesmente escrevê-los:

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

( RS= significa que registros são parágrafos ).

Ou:

awk -vRS= '/state *= *free/ && match($0, "procs *=") {
  n += substr($0,RSTART+RLENGTH)}; END {print n}'
    
por 30.09.2013 / 23:49
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

link

    
por 01.10.2013 / 08:11
4

Aqui está uma maneira de fazer isso usando pcregrep .

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

Exemplo

$ pbsnodes | \
    pcregrep -Mo 'state = free\n\s*procs = \K\d+' | \
    awk '{ sum+=$1 }; END { print sum }'
6
    
por 30.09.2013 / 23:14
3

Seu formato de saída é preparado para o slurp de parágrafo do 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

Isso só funciona porque a idéia de Perl de um "parágrafo" é um trecho de linhas não-brancas separadas por uma ou mais linhas em branco. Se você não tivesse linhas em branco entre as seções node , isso não funcionaria.

Veja também

por 30.09.2013 / 23:18
3

Se você tiver dados de comprimento fixo (comprimento fixo referente ao número de linhas em um registro), em sed você pode usar o comando N (várias vezes), que une a próxima linha ao espaço de padrão :

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

deve fornecer uma saída como:

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

Para a composição de registros variáveis (por exemplo, com uma linha separadora vazia), você pode usar os comandos de ramificação t e b , mas awk provavelmente chegará lá de uma maneira mais confortável.

    
por 30.09.2013 / 23:17
3

A implementação GNU de grep vem com dois argumentos para também imprimir as linhas antes ( -B ) e depois ( -A ) de uma correspondência. Snippet da página man:

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

Assim, no seu caso, você teria que grep para state = free e também imprimir a seguinte linha. Combinando isso com os snippets da sua pergunta, você chegará a algo assim:

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

e um pouco mais curto:

usr@srv % pbsnodes | grep -A 1 'state = free' | awk '{ sum+=$3 } END { print sum }'
6
    
por 30.09.2013 / 23:13
0

... e aqui está uma solução Perl:

pbsnodes | perl -lne 'if (/^\S+/) { $node = $& } elsif ( /state = free/ ) { print $node }'
    
por 01.10.2013 / 11:16
0

Você pode usar o comando awk getline :

$ 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.
    
por 01.10.2013 / 17:04