Jak wykonać grep wieloliniowy

15

Jak wykonasz grep dla tekstu, który pojawia się w dwóch liniach?

Na przykład:

pbsnodes to polecenie, którego używam, które zwraca wykorzystanie klastra linuksowego

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

node2
    state = free
    procs = 4
    bar = foobar

node3
    state = busy
    procs = 8
    bar = foobar

Chcę określić liczbę procesów pasujących do węzłów, które są w stanie „wolne”. Do tej pory byłem w stanie określić „liczbę procesorów” i „węzły w stanie wolnym”, ale chcę je połączyć w jedno polecenie, które pokazuje wszystkie wolne procesy.

W powyższym przykładzie poprawna odpowiedź to 6 (2 + 4).

Co ja mam

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

Jak mogę wyszukać każdą linię, która brzmi „procs = x”, ale tylko wtedy, gdy linia nad nią brzmi „stan = wolny”?

spuder
źródło

Odpowiedzi:

12

Jeśli dane są zawsze w tym formacie, możesz po prostu je zapisać:

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

( RS=oznacza, że zapisy są akapitami ).

Lub:

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

drapieżnik Alfa
źródło
4

Oto jeden ze sposobów, aby to zrobić za pomocą pcregrep.

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

Przykład

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

Twój format wyjściowy jest przygotowany na slurp akapitowy Perla:

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 }'

Uwaga

Działa to tylko dlatego, że pomysł Perla na „akapit” jest kawałkiem niepustych linii oddzielonych jedną lub więcej pustymi liniami. Gdybyś nie miał pustych linii między nodesekcjami, to by nie zadziałało.

Zobacz też

Joseph R.
źródło
3

Jeśli masz dane o stałej długości (stała długość odnosząca się do liczby linii w rekordzie), sedmożesz użyć Npolecenia (kilka razy), które łączy następny wiersz z przestrzenią wzorów:

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

powinien dać ci dane wyjściowe takie jak:

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

Dla zmiennym składzie (np rekord z pustą linią separatora), można skorzystać z poleceń rozgałęziających ti b, ale awkjest prawdopodobne, aby dostać się tam w bardziej wygodny sposób.

Peter
źródło
3

Implementacja GNU grepzawiera dwa argumenty do wydrukowania wierszy przed dopasowaniem ( -B) i po ( -A). Fragment ze strony podręcznika 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.

Więc w twoim przypadku musisz grepować, state = freea także wydrukować następujący wiersz. Łącząc to z fragmentami twojego pytania, dojdziesz do czegoś takiego:

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

i nieco krótszy:

usr@srv % pbsnodes | grep -A 1 'state = free' | awk '{ sum+=$3 } END { print sum }'
6
binfalse
źródło
awkdopasowuje wzór; nie potrzebujesz grep: patrz odpowiedź Stephane'a
jasonwryan
Cóż, sedrównież dopasowuje wzór. Można również użyć perl, lub php, lub cokolwiek język wolisz. Ale przynajmniej nagłówek pytania
zadanego
Tak: ale widząc, że i awktak używasz ... :)
jasonwryan
0

... a oto rozwiązanie Perla:

pbsnodes | perl -lne 'if (/^\S+/) { $node = $& } elsif ( /state = free/ ) { print $node }'
reinierpost
źródło
0

Możesz użyć awk getlinepolecenia:

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

Od 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
źródło