Como um comando (ou seja, grep) sabe quando é executado como parte da expansão glob?

4

Pelo que entendi, um curinga glob é interpretado pelo shell, que executa o comando especificado para cada nome de arquivo correspondente. Suponha que eu tenha arquivos: abc1, abc2, and abc3 no meu diretório atual. Então, por exemplo, echo abc* irá ecoar uma vez para cada nome de arquivo começando com 'abc'.

No entanto, se eu executar grep 'foo' abc* , imagino que isso deva ser executado:

grep 'foo' abc1
grep 'foo' abc2
grep 'foo' abc3

O que significa que eu deveria obter a seguinte saída (assumindo que todos os arquivos contenham uma linha que diga 'foo'):

foo
foo
foo

No entanto, em vez disso, obtenho:

abc1:foo
abc2:foo
abc3:foo

Então eu acho que existem duas explicações possíveis para isso. Primeiro, é de alguma forma que o grep pode detectar que ele foi usado com uma expressão glob e responde enviando os nomes dos arquivos antes das correspondências. Segundo, como você pode passar vários arquivos para o grep, o shell na verdade executa apenas 1 comando:

grep 'foo' abc1 abc2 abc3

No entanto, isso só funciona porque o grep aceita vários arquivos no final. É possível que outro comando permita apenas que 1 arquivo seja passado. Então, se você quisesse executar o comando para múltiplos arquivos correspondentes ao glob, não funcionaria se a globbing funcionasse através do segundo método descrito acima.

De qualquer forma, alguém pode lançar alguma luz sobre isso?

Obrigado!

    
por Cod3Citrus 18.03.2017 / 01:05

1 resposta

5

Esse é o truque: o comando não sabe, é o shell que faz o trabalho

Considere, por exemplo, grep 'abc' *.txt . Se executarmos rastros de chamadas do sistema, você verá algo assim:

bash-4.3$ strace -e trace=execve grep "abc" *.txt > /dev/null
execve("/bin/grep", ["grep", "abc", "ADDA_converters.txt", "after.txt", "altera_license.txt", "altera.txt", "ANALOG_DIGITAL_NOTES.txt", "androiddev.txt", "answer2.txt", "answer.txt", "ANSWER.txt", "ascii.txt", "askubuntu-profile.txt", "AskUbuntu_Translators.txt", "a.txt", "bash_result.txt", ...], [/* 80 vars */]) = 0
+++ exited with 0 +++

O shell expandiu *.txt em todos os nomes de arquivos no diretório atual que terminam com .txt extension. Então, efetivamente, seu shell traduz o comando grep 'abc' *.txt para grep 'abc' file1.txt file2.txt file3.txt . . . . Assim, sua segunda suposição está correta.

A primeira suposição não está correta - os programas não têm como detectar glob. É possível passar * como argumento string ao comando, mas é o trabalho do comando decidir o que fazer com ele. A expansão do nome de arquivo, no entanto, é propriedade da sua respectiva shell, como já mencionei.

  

No entanto, isso só funciona porque o grep aceita vários arquivos no final. É possível que outro comando permita apenas que 1 arquivo seja passado.

Exatamente certo! Os programas não limitam o número de argumentos de linha de comando aceitáveis (por exemplo, em C, é o array de strings const char *args[] e em python sys.argv[] ), mas eles podem detectar o comprimento desse array ou se algo inesperado está ou não na posição errada da matriz. grep não faz isso e aceita vários arquivos, o que é por design.

Em nota lateral, citações impróprias acopladas a globbing com grep às vezes podem ser um problema. Considere isto:

bash-4.3$ echo "one two" | strace -e trace=execve grep *est*
execve("/bin/grep", ["grep", "self_test.sh", "test.wxg"], [/* 80 vars */]) = 0
+++ exited with 1 +++

O usuário despreparado esperaria que o grep correspondesse a qualquer linha com est letras vindas do pipe, mas, em vez disso, a expansão do nome de arquivo do shell era distorcida. Eu vi isso acontecer muito com pessoas que fazem ps aux | grep shell_script_name.sh , e eles esperam encontrar o processo em execução, mas porque eles executaram o comando do mesmo diretório onde o script era , a expansão do nome de arquivo do shell tornou grep comando para parecer completamente diferente nos bastidores do que o usuário esperava.

A maneira correta seria usar aspas simples:

bash-4.3$ echo "one two" | strace -e trace=execve grep '*est*'
execve("/bin/grep", ["grep", "*est*"], [/* 80 vars */]) = 0
+++ exited with 1 +++
    
por Sergiy Kolodyazhnyy 18.03.2017 / 01:21