Como a pesquisa em $ PATH funciona sob o capô?


8

Existem muitos artigos / recursos na Web que ensinam às pessoas como definir a variável de ambiente PATHpara que possam usar a mão curta de javaou pythonetc em vez do caminho absoluto na interface da linha de comando.

O que eu estou interessado em saber é que o que está por trás da cena quando digitamos o comando e pressionamos enter (semelhante ao que acontece quando você digita uma URL no navegador ).

Aqui está o meu palpite:

  1. leia o comando (analise / pré-processe stdin para obter os argumentos corretos $@)
  2. pesquisa de comando
  3. execução de comando (programa iniciado, consumir memória, stdout / stderr para shell)
  4. re-render o emulador de ambiente variáveis relevantes (por exemplo $PS#, $PROMPT, etc)

A parte que eu mais quero descobrir é a pesquisa de comando. Obviamente, o $PATHé consumido por alguma função de segundo plano e separado por :/ ;como delimitadores, o que aconteceu? Usamos uma tabela de hash (chave: basename do arquivo, valor: dirname absoluto do arquivo) para armazenar os arquivos binários nesses PATHs ou em outros ganchos?

OBSERVAÇÃO: Originalmente, pensei que fosse uma tabela de hash, como posso usar [ -z hash [command] ]para verificar se um comando está disponível no ambiente atual, mas quando uso hash | grep pythonnão recebo nada da saída enquanto which pythontrabalha como esperado. (Acho que o mecanismo pode ser específico do shell, mas quero obter mais informações sobre ele.)

Respostas:


11

Como você suspeita, o comportamento exato depende do shell, mas um nível básico de funcionalidade é especificado pelo POSIX.

A pesquisa e execução de comandos para a linguagem de comando padrão do shell (na qual a maioria dos shells implementam um superconjunto) têm muitos casos, mas estamos interessados ​​apenas no momento no caso em que PATHé usado. Nesse caso:

o comando deve ser procurado usando a variável de ambiente PATH conforme descrito em Variáveis ​​de ambiente XBD

e

Se a pesquisa for bem sucedida:

[...]

o shell executa o utilitário em um ambiente de utilitário separado, com ações equivalentes a chamar a execl()função [...] com o argumento de caminho definido como o nome do caminho resultante da pesquisa.

No caso malsucedido, a execução falha e um código de saída 127 é retornado com uma mensagem de erro.

Esse comportamento é consistente com a execvpfunção, em particular. Todas as exec*funções aceitam o nome do arquivo de um programa a ser executado, uma sequência de argumentos (que será argvo programa) e talvez um conjunto de variáveis ​​de ambiente. Para as versões usando a PATHpesquisa, o POSIX define que :

O arquivo de argumento é usado para construir um nome de caminho que identifica o novo arquivo de imagem de processo. O [...] prefixo do caminho para esse arquivo é obtido por uma pesquisa nos diretórios passados ​​como a variável de ambiente PATH


O comportamento do PATH é definido em outro lugar como:

Essa variável deve representar a sequência de prefixos de caminho que certas funções e utilitários aplicam na busca de um arquivo executável conhecido apenas por um nome de arquivo. Os prefixos devem ser separados por um <colon> (':'). Quando um prefixo de tamanho diferente de zero é aplicado a esse nome de arquivo, um <slash> deve ser inserido entre o prefixo e o nome do arquivo se o prefixo não terminar. Um prefixo de comprimento zero é um recurso herdado que indica o diretório de trabalho atual. Ele aparece como dois caracteres adjacentes ("::"), como um <colon> inicial que precede o restante da lista ou como um <colon> à direita após o restante da lista. Um aplicativo estritamente conforme deve usar um nome de caminho real (como.) Para representar o diretório de trabalho atual no PATH.A lista deve ser pesquisada do início ao fim, aplicando o nome do arquivo a cada prefixo, até que um arquivo executável com o nome especificado e as permissões de execução apropriadas seja encontrado . Se o nome do caminho que está sendo procurado contiver um <slash>, a pesquisa pelos prefixos do caminho não deverá ser realizada. Se o nome do caminho começar com um <slash>, o caminho especificado será resolvido (consulte Resolução do nome do caminho ). Se PATH estiver desativado ou definido como nulo, a pesquisa de caminho será definida pela implementação.

Isso é um pouco denso, então um resumo:

  1. Se o nome do programa tiver um /(barra, U + 002F SOLIDUS), trate-o como um caminho da maneira usual e pule o restante deste processo. Para o shell, esse caso tecnicamente não surge (porque as regras do shell já terão lidado com ele).
  2. O valor de PATHé dividido em partes em cada dois pontos e, em seguida, cada componente é processado da esquerda para a direita. Como um caso especial (histórico), um componente vazio de uma variável não vazia é tratado como .(o diretório atual).
  3. Para cada componente, o nome do programa é anexado ao final com uma junção /e a existência de um arquivo com esse nome é verificada e, se existir, as permissões de execução (+ x) válidas também serão verificadas. Se uma dessas verificações falhar, o processo passará para o próximo componente. Caso contrário, o comando será resolvido para esse caminho e a pesquisa será concluída.
  4. Se você ficar sem componentes, a pesquisa falhará.
  5. Se não houver nada PATH, ou ele não existir, faça o que quiser.

Os shells reais terão comandos integrados, que são encontrados antes dessa pesquisa, e frequentemente aliases e funções também. Aqueles não interagem com PATH. O POSIX define algum comportamento em torno deles , e seu shell pode ter muito mais.


Embora seja possível confiar na exec*maior parte disso para você, o shell na prática pode implementar essa pesquisa em si, principalmente para fins de armazenamento em cache, mas o comportamento do cache vazio deve ser semelhante. Os shells têm latitude bastante ampla aqui e comportamentos sutilmente diferentes nos casos de canto.

Como você descobriu, o Bash usa uma tabela de hash para lembrar os caminhos completos dos comandos vistos anteriormente, e essa tabela pode ser acessada com a hashfunção Na primeira vez em que você executa um comando, ele pesquisa e, quando um resultado é encontrado, ele é adicionado à tabela, para que você não precise se preocupar em procurar na próxima vez que tentar.

No zsh, por outro lado, o total PATHgeralmente é pesquisado quando o shell é iniciado. Uma tabela de pesquisa é pré-preenchida com todos os nomes de comandos descobertos, de modo que as pesquisas em tempo de execução geralmente não são necessárias (a menos que um novo comando seja adicionado). Você pode perceber isso acontecendo ao tentar tab-complete um comando que não existia antes.

Cascas muito leves, por exemplo dash, tendem a delegar o máximo de comportamento possível à biblioteca do sistema e não se preocupam em se lembrar dos caminhos de comando anteriores.


Muito obrigado por uma explicação tão detalhada, isso realmente fornece insights profundos. Sua comparação PATHentre bashe zshme ajuda a resolver minha confusão!
X27:
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.