arquivos grep da lista

14

Estou tentando executar o grep em uma lista de algumas centenas de arquivos:

$ head -n 3 <(cat files.txt)
admin.php
ajax/accept.php
ajax/add_note.php

No entanto, mesmo que eu esteja procurando por uma sequência que eu sei que é encontrada nos arquivos, o seguinte não pesquisa os arquivos:

$ grep -i 'foo' <(cat files.txt)

$ grep -i 'foo' admin.php
The foo was found

Eu estou familiarizado com a -fbandeira que lerá os padrões de um arquivo. Mas como ler os arquivos de entrada ?

Eu tinha considerado a solução horrível de copiar os arquivos para um diretório temporário, pois cpparece suportar o <(cat files.txt)formato e, a partir daí, saudar os arquivos. Shirley, há uma maneira melhor.

dotancohen
fonte

Respostas:

22

Você parece estar cumprimentando a lista de nomes de arquivos, não os próprios arquivos. <(cat files.txt)apenas lista os arquivos. Tente <(cat $(cat files.txt))concatená-los e pesquisá-los como um único fluxo ou

grep -i 'foo' $(cat files.txt)

para dar grep todos os arquivos.

No entanto, se houver muitos arquivos na lista, você poderá ter problemas com o número de argumentos. Nesse caso, eu escreveria

while read filename; do grep -Hi 'foo' "$filename"; done < files.txt
orion
fonte
Obrigado! Eu não sabia que whilepoderia receber as linhas de file.txt como tal.
dotancohen
Você desejará desativar a parte glob desse operador split + glob aqui (a menos que o shell seja zsh).
Stéphane Chazelas
1
whilenão está recebendo exatamente as linhas do arquivo, readestá fazendo isso; whileapenas nos permite fazer isso em um loop. O loop termina quando readfalha (ou seja, retorna um código de retorno diferente de zero), normalmente devido ao término do arquivo.
PM 2Ring
1
Para ler uma linha (texto), a sintaxe é IFS= read -r filename, read filenameé outra coisa.
Stéphane Chazelas
1
Note que -Hé uma extensão GNU. Você está sentindo falta de alguns --.
Stéphane Chazelas
8
xargs grep -i -- foo /dev/null < files.txt

supondo que os arquivos estejam em branco ou delimitados por nova linha (onde aspas ou barras invertidas podem ser usadas para escapar desses separadores). Com o GNU, xargsvocê pode especificar o delimitador com -d(o que desabilita o tratamento de cotação).

(unset -v IFS; set -f; grep -i -- foo $(cat files.txt))

supondo que os arquivos estejam separados por espaço, tabulação ou nova linha (não há como escapar deles, embora você possa escolher um separador diferente atribuindo-o a IFS). Essa falha se a lista de arquivos for muito grande na maioria dos sistemas.

Eles também assumem que nenhum dos arquivos é chamado -.

Stéphane Chazelas
fonte
É melhor / mais rápido de usar em $(< file)vez de $(cat file), pelo menos em bashe zsh.
jimmij
7

Para ler uma lista de nomes de arquivos do stdin, você pode usar xargs. Por exemplo,

cat files.txt | xargs -d'\n' grep -i -- 'foo'

Por padrão, xargslê itens da entrada padrão, delimitada por espaços em branco. O -d'\n'comando diz para usar a nova linha como delimitador de argumento, para que ele possa lidar com nomes de arquivos contendo espaços em branco. (Como Stéphane Chazelas aponta, essa é uma extensão do GNU). No entanto, ele não lidará com nomes de arquivos contendo novas linhas; precisaríamos de uma abordagem um pouco mais complicada para lidar com isso.

FWIW, essa abordagem é um pouco mais rápida que um while readloop, pois o readcomando do bash é muito lento - ele lê seu caractere de dados por caractere, enquanto xargslê sua entrada com mais eficiência. Além disso, xargsapenas chama o grepcomando quantas vezes for necessário, com cada chamada recebendo vários nomes de arquivos, e isso é mais eficiente do que chamar grepindividualmente para cada nome de arquivo.

Consulte a página de manual do xargs e a página de informações do xargs para obter mais detalhes.

PM 2Ring
fonte
3

xargspode ler itens de um arquivo (como sua files.txtlista) com sua opção:

   --arg-file=file
   -a file
          Read items from file instead of standard input.  If you use this
          option, stdin remains unchanged when commands are  run.   Other
          wise, stdin is redirected from /dev/null.

Portanto, isso deve funcionar também:

xargs -a files.txt grep -i 'foo'

ou para espaços em nomes de arquivos

xargs -d'\n' -a files.txt grep -i 'foo'
xargs -I{} -a files.txt grep -i 'foo' {}
Xen2050
fonte
1

Você também pode fazer um for, mas o exemplo do Orion é o mais simples:

for i in $(cat files.txt); do grep -i 'foo' $i ; done

(Para cada arquivo listado nos arquivos.txt, execute o comando grep.)

Michael
fonte