Usando o comando grep com um conjunto de palavras

Sep 10 2020

Eu tenho um arquivo de texto com algumas linhas e salvei o arquivo de texto em uma matriz usando

readarray -t array < Textfile

O arquivo de texto contém, por exemplo, o seguinte:

123
456
789

Agora eu gostaria de usar o comando grep para procurar os resultados em outro arquivo de texto usando o array, então imprima as linhas onde "123", "456" ou "789" aparecem. Para testar o array, tentei fazer o grep procurar por "123", "456" e "789" o mesmo arquivo de texto e gerar as linhas correspondentes, se pelo menos uma das três séries de caracteres aparecer.

eu tentei

grep "${array[*]}" Textfile

mas isso não mostra nenhum resultado. O que estou fazendo errado?

Respostas

4 terdon Sep 10 2020 at 13:43

Em primeiro lugar, você pode usar o próprio arquivo. Isso é muito mais simples do que tentar usar uma matriz de shell:

grep -f file1.txt file2.txt

Isso imprimirá quaisquer linhas file2.txtque correspondam a quaisquer linhas em file1.txt.

Se você tiver que usar um array por algum motivo, as coisas são mais complicadas. Você não pode simplesmente fazer grep "${array[*]}" Textfileporque "${array[*]}"irá expandir para a lista de elementos na matriz separados por um espaço:

$ array=("foo" "bar" "baz") $ echo "${array[*]}"
foo bar baz

O que significa que seu grepcomando se tornaria:

grep 'foo bar baz' file

E isso significa "grep for foo bar bazin the file file". O que você quer fazer é usar grep para foo, ou bar , ou baz . Isso pode ser feito usando a opção -E grepe juntando os padrões que você deseja pesquisar com |:

grep -E 'foo|bar|baz' file

Para fazer isso, você precisará fazer algo complicado como:

grep -E "$(printf '%s|' "${array[@]}" | sed 's/|$//')" file

Ou talvez:

grep -E "$(export IFS="|"; echo "${array[*]}")" file

No geral, é simplesmente melhor, mais rápido e fácil usar o arquivo e esquecer o array.

StéphaneChazelas Sep 10 2020 at 17:18
grep "${array[*]}" Textfile

Funcionará contanto que você defina IFScomo nova linha (ou qualquer coisa que comece com nova linha) e use --ou -epara garantir que ainda funcione mesmo se o primeiro elemento começar com -.

"${array[*]}"em shells semelhantes ao Korn, como "$*"em shells POSIX, expande-se para a lista de elementos unidos com o primeiro caractere de $IFS. O valor padrão de $IFSé <SPC><TAB><NL>( <SPC><TAB><NL><NUL>in zsh), portanto, por padrão, você obteria o elemento unido com caracteres SPC. Pois grep, você precisa que as diferentes expressões regulares sejam separadas por nova linha para grepfazer um loop para cada uma delas.

IFS=$'\n'
grep -e "${array[*]}" file

Em zsh, fazer grep -e "${(pj:\n:)array}" file(explicitamente joining com nova linha em vez de modificar $IFSglobalmente), ou grep -e$^array file(que expande o array no estilo fish/ rc, onde isso se torna grep -efirst -esecond fileoutra forma de fornecer vários padrões grep) seria mais limpo.

Outra opção é fazer:

printf '%s\n' "${array[@]}" | grep -f - file

Desta vez, passando a lista de padrões delimitada por nova linha sobre grepo stdin de, em vez de por meio de um argumento.

Essas abordagens funcionariam se seus padrões fossem strings fixas (com -F), expressões regulares estendidas (com -E) ou expressões regulares básicas (padrão).

Você pode querer ter certeza de que a lista de padrões não está vazia:

(( ${#array[@]} > 0 )) && grep ...

Chamar grepcom um padrão vazio produz resultados diferentes dependendo da grepimplementação e geralmente não do que você deseja.