awk ou comando sed para corresponder a regex na linha específica, sair verdadeiro se for bem sucedido, caso contrário falso

5

Eu preciso determinar se um arquivo contém um determinado regex em uma determinada linha e retornar true (exit 0) se encontrado, e caso contrário, false. Talvez eu esteja pensando demais nisso, mas minhas tentativas se mostraram um pouco difíceis. Eu tenho uma solução, mas estou procurando talvez outras que eu não tenha pensado. Eu poderia usar o perl, mas espero manter esse "peso leve" o mais possível, pois ele é executado durante um ciclo de execução de marionetes.

O problema é bastante comum: no RHEL6, a tela era empacotada de uma maneira que limitava a largura do terminal a 80 caracteres, a menos que você não comentasse a linha em 132. Esse comando verifica se essa linha já foi corrigida:

 awk 'NR==132 && /^#termcapinfo[[:space:]]*xterm Z0=/ {x=1;nextfile} END {exit 1-x}' /etc/screenrc

Nota: se o arquivo tiver menos que 132 linhas, ele deve sair com false.

Eu achei que sed seria de ajuda aqui, mas aparentemente você tem que fazer truques estranhos como substituições nulas e ramificações. Ainda assim, gostaria de ver uma solução sed apenas para aprender. E talvez haja algo mais que eu ignorei.

EDIT 1: Adicionado nextfile à minha solução awk

EDIT 2: Benchmarks EDIT 3: host diferente (ocioso). EDIT 4: erroneamente usado o tempo de awk do Gile para execução otimizada por. EDIT 5: nova bancada

Referências

Primeiro, observe: wc -l /etc/screenrc é 216 . 50k iterações quando a linha não está presente, medida em tempo de parede:

  • Opção nula: 0,545s
  • Minha solução original do awk: 58.417
  • Minha solução awk editada (com nextfile): 58.364s
  • Solução awk de Giles: 57.578s
  • Solução otimizada de perl 90.352s Doh!
  • Sed 132{p;q}|grep -q ... solution: 61.259s
  • Cuonglm's tail | head | grep -q : 70.418s Ouch!
  • head -nX |head -n1|grep -q : 116,9s de Don_chrissti Brrrrp!
  • Solução double-grep de Terdon: 65.127s
  • solução sed de John1024: 45.764s

Obrigado João e obrigado sed! Eu estou honestamente surpreso que o perl estava por aqui. O Perl carrega um monte de bibliotecas compartilhadas na inicialização, mas desde que o sistema operacional esteja armazenando em cache todas elas, tudo se resume ao analisador e ao codificador de bytes. No passado distante (perl 5.2?) Eu achei que era mais lento em 20%. Perl era mais lento como eu esperava, mas parecia ser melhor devido a um erro de copiar / colar da minha parte.

Referências Parte 2

O maior arquivo de configuração que tem valor prático é /etc/services . Então eu re-executei estes bancos para este arquivo e onde a linha a ser alterada é 2 / 3rds no arquivo. Total de linhas é 1100, então eu peguei 7220 e modifiquei o regex de acordo (de modo que em um caso ele falha, em outro ele é bem-sucedido; para o banco sempre falha).

  • solução de sed de John: 121,4s
  • {head;head}|grep da solução de Chrissti: 138.341s
  • tail|head|grep solution da Counglm: 77.948s
  • Minha solução do awk: 175.5s
por Otheus 03.09.2015 / 11:00

8 respostas

14

Com o GNU sed:

sed -n '132 {/^#termcapinfo[[:space:]]*xterm Z0=/q}; $q1'

Como funciona

  • 132 {/^#termcapinfo[[:space:]]*xterm Z0=/q}

    Na linha 132, verifique o regex ^#termcapinfo[[:space:]]*xterm Z0= . Se for encontrado, q , com o código de saída padrão de 0. O restante do arquivo será ignorado.

  • $q1

    Se chegarmos à última linha, $ , saia com o código de saída 1: q1 .

Eficiência

Como não é necessário ler além da 132ª linha do arquivo, essa versão é encerrada assim que alcançamos a 132ª linha ou o final do arquivo, o que ocorrer primeiro:

sed -n '132 {/^#termcapinfo[[:space:]]*xterm Z0=/q; q1}; $q1'

Manipulando arquivos vazios

A versão acima retornará true para arquivos vazios. Isso porque, se o arquivo estiver vazio, nenhum comando será executado e o sed sairá com o código de saída padrão 0. Para evitar isso:

! sed -n '132 {/^#termcapinfo[[:space:]]*xterm Z0=/q1; q}'

Aqui, o comando sed sai com o código 0, a menos que a string desejada seja encontrada, caso em que saia com o código 1. O! anterior diz ao shell para inverter esse código para voltar ao código que queremos. O modificador ! é suportado por todos os shells do POSIX. Esta versão funcionará mesmo para arquivos vazios. (Dica do chapéu: G-Man)

    
por 03.09.2015 / 11:09
5

Com o toolchest POSIX:

tail -n +132 </etc/screenrc | head -n 1 | grep -q pattern
    
por 03.09.2015 / 11:11
3

Você pode fazer isso de maneira mais eficiente no awk: exit assim que atingir a linha relevante.

awk 'NR==132 {if (/^#termcapinfo[[:space:]]*xterm Z0=/) found=1; exit}
     END {exit !found}' /etc/screenrc

Alternativamente, você pode usar o GNU sed (mas o sed portátil não permite especificar o código de saída).

Como alternativa, você pode usar a filosofia Unix de combinar ferramentas: extraia a linha desejada com head e tail e passe para grep .

</etc/screenrc tail -n +132 | head -n 1 |
grep -q '^#termcapinfo[[:space:]]*xterm Z0='

Ou você pode usar sed para extrair a linha desejada:

</etc/screenrc sed -n '32 {p; q;}' |
grep -q '^#termcapinfo[[:space:]]*xterm Z0='

(Ambos dependem do fato de que você deseja o mesmo resultado para uma linha vazia e para um arquivo que seja muito curto.)

Para um arquivo tão pequeno, a abordagem mais rápida provavelmente será aquela que usa uma única ferramenta, já que a sobrecarga de lançamento de vários programas será maior do que o ganho de desempenho do uso de ferramentas de finalidade especial, como head , tail e sed . Se você quisesse a linha 132000000, começar com tail -n +132000000 provavelmente seria mais rápido que qualquer outra coisa.

    
por 03.09.2015 / 14:01
2

Algumas alternativas com ed :

ed -s infile <<\IN
132s/^#termcapinfo[[:space:]]*xterm Z0=/&/
q
IN

ou sed + grep :

sed '132!d;q' infile | grep -q '^#termcapinfo[[:space:]]*xterm Z0='

Em ambos os casos, se infile tiver menos de 132 linhas ou se a linha 132 não corresponder ao padrão, o código de saída será 1 . Ambos devem ser bastante portáveis, ed lerá todo o arquivo na memória embora ...

Se você estiver trabalhando com arquivos grandes, head poderá ser mais rápido do que sed , por exemplo:

{ head -n 131 >/dev/null; head -n 1; } <infile | grep -q '^#termcapinfo[[:space:]]*xterm Z0='
    
por 03.09.2015 / 18:49
1

Eu sei que você disse que não queria usar perl . Eu acho que você está operando sob um equívoco sobre o quão "leve" é.

Você pode fazer isso:

#!/usr/bin/env perl

use strict;
use warnings;

open ( my $input_fh, '<', "/etc/screenrc" ) or die $!; 
while ( <$input_fh> ) {
   if ( $. == 132 
   and m/^#termcapinfo[[:space:]]*xterm Z0=/ ) {
       exit 0; 
   }
}

exit 1;

Que você pode condensar em um único liner:

perl -ne 'exit 0 if $. == 132 and  m/^#termcapinfo[[:space:]]*xterm Z0=/ END { exit 1 }' 
    
por 03.09.2015 / 11:14
1

Você sempre pode usar alguns grep s:

grep -nm 1 "^#termcapinfo[[:space:]]*xterm Z0=" /etc/screenrc | grep -q '^132:'

O -n adiciona o número da linha a cada linha correspondente na saída do grep. Por exemplo:

$ seq 11 15 | grep -n 5
5:15

O -m 1 (que, diferentemente dos outros dois, não é definido pelo POSIX e pode não estar disponível na implementação grep ) faz grep sair após a primeira correspondência.

Portanto, o primeiro grep procura por linhas correspondentes à regex e as imprime junto com o número da linha. O segundo grep irá silenciosamente ( -q ) retornar true se uma linha de entrada começar com 132: , então só será verdadeira se a linha correspondente a regex 132.

Aqui está outra abordagem simples de Perl:

perl -ne '$.==132 && !/^#termcapinfo\s*xterm Z0=/ && exit(1);'

A idéia é sair com um status de 1 apenas se a linha 132 não corresponder à regex. Portanto, sairá com 0 caso contrário. Você poderia torná-lo um pouco mais eficiente (mas mais complexo) verificando apenas a linha relevante:

perl -ne '$.==132 && !/^#termcapinfo\s*xterm Z0=/ && exit(1); exit(1) if $.>132'

Você também pode simplificar seu awk original um pouco:

awk 'NR==132 && /^#termcapinfo[[:space:]]*xterm Z0=/{exit 0} NR>132{exit 1}'
    
por 03.09.2015 / 15:12
0

Uma solução alternativa com head , tail , wc e grep . (se loop na sintaxe bash )

if [[ $(head -n 132 file | wc -l) -eq 123 &&\
    $( head -n 132 file | tail -n 1 |\
    grep '^#termcapinfo[[:space:]]*xterm Z0=') ]] ; then
  echo success
else
  echo fail
fi
    
por 03.09.2015 / 11:19
0

Para o bem da integralidade, a solução rubi:

 ruby -e 'while gets do;  if $.==132 ; exit(/^#termcapinfo[[:space:]]*xterm Z0=/?0:1); end; end; exit(1)' /etc/screenrc 

Eu não vejo uma maneira de usar ruby -n aqui sem chamar at_exit () em cada iteração da leitura da linha do arquivo.

A ressonância magnética (Matz Ruby Interpreter, 1.8.7) demora muito tempo em 139s.

    
por 03.09.2015 / 17:34