Grep uma string com espaços de um arquivo

0
#!/bin/bash
LIST=/errors_exception.txt
cd /test
for PATTERN in 'cat $LIST'
do
        for FILE in $(ls)
        do
        if zcat $FILE | grep -Fxq "$PATTERN"; then
        echo "$PATTERN found pattern in $FILE" >> output
        fi
done
done

Estou tentando varrer vários arquivos de log compactados (.gz) e verificar se o padrão que estou procurando ainda existe nesses registros.

Por exemplo, no meu código acima, digamos, errors_exception.txt contém abaixo

one 
one two three
four five
six

/test - dir contém arquivos de log

Por que quando eu executo o script, ele não lê a segunda linha "um dois três" como uma única linha?

Quando eu executo o bash -x test.sh (nome do script), ele lê a segunda linha como se houvesse mais 3 linhas, onde no arquivo de texto aparece "um dois três" como uma única linha.

    
por nolram16 03.09.2014 / 05:08

1 resposta

3

list=/errors_exception.txt
cd /test
while IFS= read -r pattern ; do
    for file in * ; do
        if zcat < "$file" | grep -Fxq "$pattern"; then
            echo "$pattern found pattern in $file"
        fi
    done
done <"$list" > output

Notas:

  • Nenhuma das duas linhas abaixo fará o que você espera:

    for PATTERN in 'cat $LIST'
    
    for FILE in $(ls)
    

    Em ambos os casos, o shell faz a divisão de palavras que você não deseja. O código sugerido acima evita isso.

  • O arquivo errors_exception.txt está realmente no diretório raiz?

  • Eu converti as variáveis para minúsculas. Essa é a convenção para variáveis criadas pelo usuário. Esta convenção impedirá que você sobrescreva acidentalmente alguns parâmetros críticos do shell.

Mais sobre a divisão de palavras

Quando o shell é executado:

for PATTERN in 'cat $LIST'

ele executa cat $LIST . Quando isso acontece, espaços, tabulações e retornos de carro são tratados como a mesma coisa: uma quebra de palavra. Então, efetivamente, após a divisão da palavra, essa linha se torna:

for PATTERN in one one two three four five six

e, conforme o loop for é executado, PATTERN é atribuído sequencialmente a um, um, dois, três, quatro, cinco e seis.

O que você realmente quer é que cada linha seja tratada como uma linha. É por isso que a construção while read.... done<"$list" é usada: em cada loop, lê-se uma linha inteira.

O mesmo problema aconteceria com esta linha se qualquer nome de arquivo tiver espaços:

for FILE in $(ls)

Os resultados de ls são substituídos na linha e, se houver nomes de arquivos com espaços, tabulações ou retornos de carro neles (todos esses são caracteres legais), os nomes serão divididos em partes. Por exemplo, em um diretório vazio, crie um arquivo:

$ touch "a b c"

Agora, execute um loop for :

$ for file in $(ls); do echo $file; done
a
b
c

O for loops é executado três vezes, embora haja apenas um arquivo. Isso ocorre porque o nome do arquivo tem espaços e, após a divisão de palavras, o for loop obtém três argumentos: a, b e c.

Isso é facilmente evitado. Use em vez disso:

for file in *

O shell é inteligente o suficiente para manter cada nome de arquivo aqui intacto, independentemente de quais caracteres estão em seu nome.

Pesquisa recursiva

Se também quisermos procurar subdiretórios para arquivos gzipados, então podemos usar o recurso globstar do bash da seguinte forma:

list=/errors_exception.txt
cd /test
shopt -s globstar
while IFS= read -r pattern ; do
    for file in **/*.gz ; do
        if zcat < "$file" | grep -Fxq "$pattern"; then
            echo "$pattern found pattern in $file"
        fi
    done
done <"$list" > output

Isso requer bash .

    
por 03.09.2014 / 05:32

Tags