limite localiza saída E evita sinal 13

6

Eu tenho um diretório com arquivos ~ 1M e preciso procurar padrões específicos. Eu sei como fazer isso para todos os arquivos:

find /path/ -exec grep -H -m 1 'pattern' \{\} \;

A saída completa não é desejada (muito lenta). Vários primeiros hits são OK, então tentei limitar o número das linhas:

find /path/ -exec grep -H -m 1 'pattern' \{\} \; | head -n 5

Isso resulta em 5 linhas seguidas por

find: 'grep' terminated by signal 13

e find continuam a funcionar. Isso é bem explicado aqui . Eu tentei quit action:

find /path/ -exec grep -H -m 1 'pattern' \{\} \; -quit

Isso gera apenas a primeira correspondência.

É possível limitar a busca de resultados com um número específico de resultados (como fornecer um argumento para quit semelhante a head -n )?

    
por Andrey 12.12.2016 / 11:37

3 respostas

4

Como você já está usando as extensões GNU ( -quit , -H , -m1 ), é melhor usar a opção grep do GNU -r , junto com --line-buffered , para gerar a saída corresponde assim que eles são encontrados, então é mais provável que seja morto de um SIGPIPE assim que ele escreve a 6ª linha:

grep -rHm1 --line-buffered pattern /path | head -n 5

Com find , você provavelmente precisará fazer algo como:

find /path -type f -exec sh -c '
  grep -Hm1 --line-buffered pattern "$@"
  [ "$(kill -l "$?")" = PIPE ] && kill -s PIPE "$PPID"
  ' sh {} + | head -n 5

Ou seja, envolva grep em sh (você ainda deseja executar o mínimo de invocações de grep , daí o {} + ), e tenha sh matar seu pai ( find ) quando grep morre de um SIGPIPE.

Outra abordagem poderia ser usar xargs como uma alternativa para -exec {} + . xargs sai imediatamente quando um comando que ele gera morre de um sinal assim:

 find . -type f -print0 |
   xargs -r0 grep -Hm1 --line-buffered pattern |
   head -n 5

( -r e -0 são extensões do GNU). Assim que grep escrever no canal quebrado, tanto grep como xargs sairão e find sairá também da próxima vez que imprimir algo depois disso. A execução de find em stdbuf -oL pode fazer com que isso aconteça mais cedo.

Uma versão POSIX poderia ser:

trap - PIPE # restore default SIGPIPE handler in case it was disabled
RE=pattern find /path -type f -exec sh -c '
  for file do
    awk '\''
      $0 ~ ENVIRON["RE"] {
        print FILENAME ": " $0
        exit
      }'\'' < "$file"
    if [ "$(kill -l "$?")" = PIPE ]; then
      kill -s PIPE "$PPID"
      exit
    fi
  done' sh {} + | head -n 5

Muito ineficiente, pois executa vários comandos para cada arquivo.

    
por 12.12.2016 / 18:15
2

Uma solução para evitar os erros pode ser esta:

find / -type f -print0 \
  | xargs -0 -L 1 grep -H -m 1 --line-buffered 2>/dev/null \
  | head -10

Neste exemplo, xargs irá parar quando o comando falhar, então haverá apenas um erro de pipe, que será filtrado pelo redirecionamento stderr.

    
por 12.12.2016 / 18:54
2

Você grep um arquivo de cada vez. Com o seu -quit , você pára o achado no primeiro grep de sucesso.

[update] Minha primeira solução foi juntar vários arquivos ao mesmo tempo:

find /path/ -type f -exec grep -H -m 1 'pattern' \{\} + -quit | head -n 5

(a mágica está no + no final do subcomando -exec . Adicionado -type f . Você pode querer remover a opção -H para grep se tiver certeza de que / path / contém vários arquivos)

O problema aqui, como relatado por @ StéphaneChazelas, é que o comando -exec é executado de forma assíncrona e retorna sempre true = > find sai no primeiro arquivo.

Se quisermos que find pare quando head terminar, find também deverá receber o SIGPIPE que está recebendo grep (sinal 13). Isso significa que find deve enviar algo pelo canal.

Aqui está um hack rápido e sujo, aprimorado com as sugestões de Stéphane:

find /path/ -type f -exec grep -H -m 1 --line-buffered 'pattern' {} + -printf '\r' | head -n 5

Com -printf '\r' forço find a produzir um caractere inofensivo que (esperançosamente) não alterará a saída de grep . Quando head tiver parado, find receberá um SIGPIPE e parará também.

[update2] Eu avisei que isso é um hack sujo. Aqui está uma solução melhor:

find /path/ -type f -exec grep --quiet 'pattern' {} ";" -print | head -n 5

Aqui, este não é mais o grep que imprime o nome do arquivo, mas find = > não há mais "grep terminado pelo sinal 13" e find pára com head . O problema é que as linhas correspondentes não são mais impressas por grep .

[update3] Finalmente, como sugerido por @Andrey, o comando descaradamente hediondo abaixo resolveria este último problema:

find /path/ -type f \
    -exec grep --quiet 'pattern' {} \; \
    -printf '%p:' \
    -exec grep -h -m 1 'pattern' {} \; \
| head -n 5'
    
por 12.12.2016 / 15:57