Como grep uma linha com número desconhecido de suas linhas recuadas

4

Costumo precisar de uma linha com suas seções recuadas. Por exemplo, se o número da linha recuada puder ser conhecido, atingiríamos essa meta com o seguinte:

$ lspci -vq | grep -i wireless -B 1 -A 5

    02:00.0 Network controller: Intel Corporation Wireless 7260 (rev 73)
        Subsystem: Intel Corporation Wireless-N 7260
        Flags: bus master, fast devsel, latency 0, IRQ 64
        Memory at c0600000 (64-bit, non-prefetchable) [size=8K]
        Capabilities: <access denied>

Por exemplo, a saída do comando a seguir pode conter ou não todas as informações de lan sem fio:

$ sudo iwlist wlan0 scan | grep -i "cell 13" -A 34
          Cell 13 - Address: 00:1A:2B:93:A7:9C
                    Channel:6
                    Frequency:2.437 GHz (Channel 6)
                    Quality=20/70  Signal level=-90 dBm  
                    Encryption key:on
                    ESSID:"NetMASTER Uydunet-E445"
                    Bit Rates:1 Mb/s; 2 Mb/s; 5.5 Mb/s; 11 Mb/s; 18 Mb/s
                              24 Mb/s; 36 Mb/s; 54 Mb/s
                    Bit Rates:6 Mb/s; 9 Mb/s; 12 Mb/s; 48 Mb/s
                    Mode:Master
                    Extra:tsf=0000005b923c8227
                    Extra: Last beacon: 4648ms ago
                    IE: Unknown: 00164E65744D415354455220557964756E65742D45343435
                    IE: Unknown: 010882848B962430486C
                    IE: Unknown: 030106
                    IE: Unknown: 050400010000
                    IE: Unknown: 2A0100
                    IE: Unknown: 2F0100
                    IE: Unknown: 32040C121860
                    IE: Unknown: 2D1A2C181BFF00000000000000000000000000000000000000000000
                    IE: Unknown: 3D1606080400000000000000000000000000000000000000
                    IE: Unknown: DD090010180201F00C0000
                    IE: WPA Version 1
                        Group Cipher : CCMP
                        Pairwise Ciphers (1) : CCMP
                        Authentication Suites (1) : PSK
                    IE: Unknown: DD180050F2020101800003A4000027A4000042435E0062322F00
          Cell 14 - Address: 90:F6:52:90:C2:2F
                    Channel:6
                    Frequency:2.437 GHz (Channel 6)
                    Quality=21/70  Signal level=-89 dBm  
                    Encryption key:on
                    ESSID:"selma"
                    Bit Rates:1 Mb/s; 2 Mb/s; 5.5 Mb/s; 11 Mb/s; 6 Mb/s
                              9 Mb/s; 12 Mb/s; 18 Mb/s

Como posso fazer com que essa saída seja cortada para conter apenas uma informação Cell ?

Editar

O script de trabalho é mantido no link

    
por ceremcem 12.02.2015 / 00:28

6 respostas

1

Se você deseja produzir uma correspondência REGEXP e todas as linhas recuadas que estão logo após a correspondência:

command | perl -ne '/^( *)/; $i = length $1; $j && $i >= $j and print, next; $j = 0; /REGEXP/ and $j = $i + 1, print'

substituindo REGEXP pela sua expressão regular.

Por exemplo,

$ perl -ne '/^( *)/; $i = length $1; $j && $i >= $j and print, next; $j = 0; /a/ and $j = $i + 1, print' <<EOF
a
b
  c
  a
    b
    a
    c
      b
    d
  e
    b
  a
    c
e
EOF

saídas:

a
  a
    b
    a
    c
      b
    d
  a
    c
    
por 12.02.2015 / 00:47
2

Solução baseada em cabeçalhos de seção correspondentes

Para imprimir o texto começando com Cell 13 , mas parando antes de Cell 14 , use:

sudo iwlist wlan0 scan | awk '/Cell 13/{f=1} /^ *Cell 14/{f=0} f'

Solução baseada no monitoramento do nível de recuo

Isso imprimirá todas as linhas que começam com a que contém Cell 13 e continua com todas as linhas seguintes que têm um nível de recuo maior :

sudo iwlist wlan0 scan | awk '/Cell 13/ && !f{f=1;x=$0;sub(/[^ ].*/,"",x);x=x" ";print;next} f {if (substr($0,1,length(x))==x)print; else f=0}'

Como funciona:

O código usa duas variáveis. f é um sinalizador que é 1 se estivermos na seção que queremos imprimir e 0 caso contrário. A string x está definida para um espaço maior que o recuo no início da seção que queremos imprimir.

  • /Cell 13/ && !f{f=1;x=$0;sub(/[^ ].*/,"",x);x=x" ";print;next}

    Isso procura linhas que contenham Cell 13 (ou o que quer que signifique o início da seção. Se uma linha corresponder a essa e se ainda não estivermos nessa seção, então:

    1. f está definido como 1.

    2. A string x é definida como o recuo que inicia a linha.

    3. Esta linha é impressa.

    4. Os demais comandos são pulados e saltamos para a linha next .

  • f {if (substr($0,1,length(x))==x)print; else f=0}

    Se estivermos na seção a ser impressa ( f é diferente de zero), verifique o nível de recuo. Se o recuo for pelo menos x , imprima esta linha. Caso contrário, sinalize que chegamos ao final da seção definindo f=0 .

por 12.02.2015 / 00:45
1

Aqui está um exemplo de como isso pode ser feito com sed :

srch_rng(){                      
        set     '[:blank:]' "$1" '\
';      sed -n " /^\(.*\n\)*[$1]*$2/,\$!{N
                s/^\(\([$1]*\)[$1]*[^$1].*\n\)\([^$1]\)//
                        s/^/$3/;D           
        };       /./!p;//!d;H;x
                 /^\n[$1]*/!h;s///p;t       
                s/\([$1]*\).*\n//;/^[$1]/p
                 //!q;x;s/\n.*//;h"
}

Isso funciona em ambos os lados de um intervalo - até que sed corresponda ao padrão dado como seu primeiro argumento, a partir de stdin , ele reterá na memória até o recuo de nível superior encontrado. Depois de corresponder ao primeiro argumento, ele não armazena nada além do recuo.

Antes de fazer a correspondência, ele trabalha em um ciclo de extração da linha N ext, tentando s/// ubstitutar todo o espaço padrão se a última linha puxada for aberta com menos ou igual à mesma quantidade de espaço em branco como a primeira linha em seu buffer seguido imediatamente por um caractere não em branco, em seguida, retornando em loop para descobrir se ele fez uma correspondência para a linha mais recente ou se deve tentar novamente.

Depois de fazer a correspondência, ele primeiro imprime tudo o que reteve até o momento, apaga-a e, no próximo ciclo, e a partir dela, exclui todos, exceto o espaço em branco à direita. Durante o restante de seu tempo de execução, ele continua imprimindo cada linha à medida que a lê até encontrar outra linha que abre com um valor menor ou igual ao nível de recuo armazenado - momento em que sai completamente da entrada.

Dessa forma, ele mantém seus buffers como atuais (e como vazios) , pois pode e não desce a menos que haja trechos muito longos entre os recuos de nível superior antes que ele possa corresponder ao padrão. Ele também não se importa com linhas em branco intervenientes - a informação impressa pode abranger os limites de parágrafo, mas não abrangerá os recuos de nível superior.

Portanto, por exemplo, se eu fizer apenas srch_rng Cell , ela será impressa da linha Cell 13 para a próxima Cell . Ele também fará isso se eu especificar ESSID como o primeiro argumento, mas se eu fizer isso:

srch_rng '.*selma'

... imprime ...

Cell 14 - Address: 90:F6:52:90:C2:2F
                    Channel:6
                    Frequency:2.437 GHz (Channel 6)
                    Quality=21/70  Signal level=-89 dBm  
                    Encryption key:on
                    ESSID:"selma"
          Bit Rates:1 Mb/s; 2 Mb/s; 5.5 Mb/s; 11 Mb/s; 6 Mb/s
                    9 Mb/s; 12 Mb/s; 18 Mb/s%                

O .* é necessário porque o padrão é corresponder apenas aos primeiros caracteres não-brancos em uma linha. E, como você pode ver, para cada linha que imprime, ela remove apenas o espaço em branco que armazenou no buffer de cada uma delas - o que coloca a primeira linha impressa na extremidade esquerda e todas as outras uniformemente abaixo.

    
por 12.02.2015 / 10:57
0

Isso corresponderá de Cell 13 à próxima ocorrência de Cell

sudo iwlist wlan0 scan | awk '/Cell/{p=0} /Cell 13/{p=1} p'

Existem três comandos na declaração awk :

  1. /Cell/ {p=0} - se a palavra "Célula" existir na linha, pare de imprimir (se estivermos imprimindo anteriormente
  2. /Cell 13/ {p=1} - se a string "Cell 13" existir na linha, inicie a impressão
  3. p - imprime a linha inteira (isso é implícito por não haver {...} part) se p! = 0
por 12.02.2015 / 00:50
0

O comando grep processa o texto em linhas, linhas inteiras. Existem algumas opções que podem forçá-lo a corresponder apenas a parte da linha, ou (como no seu exemplo) span padrão para coincidir com mais de uma linha, mas geralmente se você precisar usar essas opções significa que outras ferramentas devem ser usadas no lugar.

Dito isso, prefiro usar awk em seu primeiro exemplo:

lspci -vq | awk 'BEGIN{RS="\n\n";IGNORECASE=1} /wireless/'

e pcregrep no segundo:

iwlist wlan0 scan | pcregrep -iMo 'cell 13(.|\n)*(?=cell 14)'
    
por 12.02.2015 / 01:08
0

A solução geral é usar sed com o endereço intervalo . Todos vocês precisam indicar de alguma forma as linhas por onde começar e parar. Pode ser palavra-chave (s) , nível de recuo , linha vazia ou outra coisa:

  • palavra-chave

    iwlist wlan0 scan | sed -n '/Cell 04/{:1;p;n;/Cell/!b1}'

  • nível de recuo

    iwlist wlan0 scan | sed -n '/Cell 04/{:1;p;n;/^\s\{1,18}\S/!b1}'

  • linha vazia

    lspci -vq | sed -n '/[Ww]ireless/{:1;p;n;/^$/!b1}'

por 12.02.2015 / 10:36

Tags