Como executar um grep de várias linhas

15

Como você executaria 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, consegui determinar o "número de procs" e "os nós no estado livre", mas quero combiná-los em um comando que mostre todos os procs gratuitos.

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 todas as linhas que lêem 'procs = x', mas apenas se a linha acima dela exibir 'state = free?

spuder
fonte

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}'
Stéphane Chazelas
fonte
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 predador
fonte
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
slm
fonte
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 funciona apenas porque a idéia de Perl de "parágrafo" é um pedaço de linhas não em branco separadas por uma ou mais linhas em branco. Se você não tivesse linhas em branco entre as nodeseções, isso não teria funcionado.

Veja também

Joseph R.
fonte
3

Se você tiver dados de comprimento fixo (comprimento fixo referente ao número de linhas em um registro), sedpoderá usar o Ncomando (várias vezes), que une a próxima linha ao espaço do 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 composição de registro variável (por exemplo, com uma linha separadora vazia), você pode usar comandos de ramificação te b, mas awkprovavelmente o levará até lá de uma maneira mais confortável.

peterph
fonte
3

A implementação do GNU grepvem com dois argumentos para também imprimir as linhas antes ( -B) e depois ( -A) de uma correspondência. Snippet da 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.

Portanto, no seu caso, você teria que grep state = freee também imprimir a seguinte linha. Combinando isso com os trechos 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
binfalse
fonte
awkfaz correspondência de padrões; Você não precisa grep: veja a resposta de Stephane
jasonwryan
Bem, seda correspondência de padrões também. Você também pode usar perl, ou php, ou o idioma que preferir. Mas, pelo menos, o título da pergunta feita por vários linha grep ... ;-)
binfalse
Sim: mas vendo que você estava usando awkde qualquer maneira ... :)
jasonwryan
0

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

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

Você pode usar o 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
fonte