Como um shell (bash, por exemplo) expande os padrões curinga?

9

Suponha que um diretório tenha 100 arquivos começando com a letra 'a'.

Se eu fizer um a grep <some string> a*partir do terminal, como o shell lidará com isso?

Ele expandirá a expressão regular, obterá uma lista de todos os arquivos começando com ae grep em cada um deles sequencialmente? Ou existe alguma outra maneira?

Suponha que eu tenho uma matriz dos nomes de arquivos acima que começam com 'a'. Levará mais / menos tempo se eu escrever um loop for e fizer a iteração em um shell script ou programa ac?

harithski
fonte
7
BTW, não é uma globexpressão regular. Grande diferença.
Aaron D. Marasco

Respostas:

8

Primeiro, um nitpick: uma string como a*na sintaxe normal do shell é uma glob, que funciona de maneira diferente das expressões regulares.

Em uma visão geral de alto nível, o interpretador de shell (ou seja, bash) expande a string a*para uma lista de todos os nomes de arquivos que correspondem ao padrão a*. Eles então se tornam parte dos parâmetros da linha de comando em uma única instância de grep(para os programadores, todas as palavras expandidas passam como strings separadas para o argvargumento de main). Esse grepcomando único analisa os argumentos da maneira que escolher, e cabe grepa interpretá-los como nomes de arquivo, opções, argumentos de opção, expressões regulares etc. e executar as ações apropriadas. Tudo ocorre sequencialmente (AFAIK nenhuma grepimplementação usa vários threads).

Se você implementar um loop em um script de shell para fazer a mesma coisa, é quase garantido que será mais lento que o processo acima, pelos seguintes motivos. Se você gerar um novo processo grep para cada arquivo, certamente será mais lento devido ao aumento da sobrecarga da criação do processo desnecessariamente. Se você construiu a lista de argumentos no script do shell e usou uma única instância de grep, qualquer coisa que você faça no shell ainda será mais lenta porque os comandos do shell precisam ser interpretados (por bash), o que adiciona uma camada extra de código, e você basta reimplementar o que o bash já estava fazendo mais rápido internamente no código compilado.

Quanto a escrever você mesmo em C, provavelmente é possível obter facilmente um desempenho comparável ao processo descrito no primeiro parágrafo, mas é improvável que você consiga obter um ganho de desempenho suficiente nas implementações grep / bash atuais para justificar o tempo gastos sem investigar otimizações de desempenho específicas da máquina ou sacrificar a portabilidade. Talvez você possa tentar criar uma versão arbitrariamente paralelamente agradável de grep, mas mesmo isso pode não ajudar, pois é mais provável que você esteja vinculado à E / S do que à CPU. A expansão de globos e grep já são "rápidos o suficiente" para a maioria dos propósitos "normais".

jw013
fonte
Obrigado pela resposta muito detalhada. Na verdade, eu preciso grep arquivos gzipped (alguns GB cada). Eu tenho uma lista desses arquivos. Agora tenho a opção de criar um regex (complicado) para corresponder a esses arquivos ou iterar na lista conhecida e executar grep em cada um deles (fácil). Daí a preocupação com o desempenho.
harithski
tente zcate zgrep; não há necessidade de descompactá-los um por um
jw013
Sim, claro. Estou usando o zgrep.
harithski
6

Sim, ele será expandido para uma lista de arquivos e alimentará a lista resultante para o grepprograma. Pelo menos é o que man bashdiz na subseção Expansão do nome do caminho .

Há outra maneira de usar a expansão em casos simples, como você menciona: escreva grep <some_string> ae antes de pressionar* , pressione ESC. Isso expandirá a lista de arquivos correspondentes diretamente na linha de comando, para que você possa verificar se a lista está OK antes de pressionar Enter.

Quanto à segunda parte da sua pergunta, isso depende. Se você deseja escrever um loop for que execute grep em cada um dos arquivos por sua vez, seria definitivamente mais lento, porque o programa grep será executado não uma vez, mas uma vez por arquivo. No entanto, o que é importante ter em mente é que existe um certo limite no comprimento expandido dos argumentos da linha de comando que você pode usar, embora normalmente seja bastante alto. Para ver isso, você pode tentar grep adasdsadf /usr/*/*/* >/dev/null.

rozcietrzewiacz
fonte
2
ESC+*não é exatamente o mesmo que permitir que o bash se expanda * porque ESC+*inserirá arquivos de ponto (nomes que começam com a .), enquanto a expansão de *depende da dotglob shoptconfiguração. A sequência de teclas para expandir e inserir globs é C-x *por padrão e mapeia para o comando readline glob-expand-word.
Jw013 3/08
1
@ jw013 Obrigado pela informação! Parece não mudar o caso da a*expansão, mas certamente é importante em um escopo mais amplo.
rozcietrzewiacz
2
zshnota: apenas pressionar a tecla tab nos parâmetros expansíveis (padrões glob, expansão de chaves, substituição de comandos, ...) os expandirá.
Stéphane Gimenez
@ jw013 Na verdade, acabei de testar o C-xatalho e ele não expande a lista de arquivos no meu sistema (usando o bash).
rozcietrzewiacz
1
@roz Certo - eu quase nunca uso de qualquer maneira, só queria apontar a diferença (bastante nitpicky) :). C-x *apenas faz globs que apenas fazem nomes de arquivos, mas Esc *na verdade fazem muito mais insert-completions, como em todas as conclusões possíveis. Isso significa que usar Esc *uma linha de comando vazia irá inserir o nome de cada arquivo executável no seu $PATH, por exemplo.
Jw013 3/08