Como você suspeita, o comportamento exato é dependente do shell, mas um nível de linha de base de funcionalidade é especificado pelo POSIX.
Comando de pesquisa e execução para a linguagem de comando shell padrão (que a maioria dos shells implementa um superconjunto de) tem muitos casos, mas estamos interessados apenas no momento em que PATH
é usado. Nesse caso:
the command shall be searched for using the PATH environment variable as described in XBD Environment Variables
e
If the search is successful:
[...]
the shell executes the utility in a separate utility environment with actions equivalent to calling the
execl()
function [...] with the path argument set to the pathname resulting from the search.
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 função execvp
, em particular. Todas as funções exec*
aceitam o nome do arquivo de um programa a ser executado, uma sequência de argumentos (que será o argv
do programa) e talvez um conjunto de variáveis de ambiente. Para as versões que usam PATH
lookup, POSIX define que :
The argument file is used to construct a pathname that identifies the new process image file [...] the path prefix for this file is obtained by a search of the directories passed as the environment variable PATH
O comportamento do PATH é definido em outro lugar como:
This variable shall represent the sequence of path prefixes that certain functions and utilities apply in searching for an executable file known only by a filename. The prefixes shall be separated by a <colon> ( ':' ). When a non-zero-length prefix is applied to this filename, a <slash> shall be inserted between the prefix and the filename if the prefix did not end in . A zero-length prefix is a legacy feature that indicates the current working directory. It appears as two adjacent characters ( "::" ), as an initial <colon> preceding the rest of the list, or as a trailing <colon> following the rest of the list. A strictly conforming application shall use an actual pathname (such as .) to represent the current working directory in PATH. The list shall be searched from beginning to end, applying the filename to each prefix, until an executable file with the specified name and appropriate execution permissions is found. If the pathname being sought contains a <slash>, the search through the path prefixes shall not be performed. If the pathname begins with a <slash>, the specified path is resolved (see Pathname Resolution). If PATH is unset or is set to null, the path search is implementation-defined.
Isso é um pouco denso, então um resumo:
- Se o nome do programa tiver um
/
(barra, U + 002F SOLIDUS), trate-o como um caminho em a moda usual , e pule o restante deste processo. Para o shell, este caso tecnicamente não surge (porque as regras do shell já terão lidado com ele). - 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). - 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 executar (+ x) válidas também serão verificadas . Se uma dessas verificações falhar, o processo passa para o próximo componente. Caso contrário, o comando é resolvido para esse caminho e a pesquisa é feita. - Se você ficar sem componentes, a pesquisa falhará.
- Se não houver nada em
PATH
ou não existir, faça o que quiser.
Os shells reais terão comandos incorporados, que são encontrados antes dessa consulta e geralmente usam aliases e funções também. Aqueles não interagem com PATH
. POSIX define alguns comportamentos em torno deles , e seu shell pode ter muito mais.
Embora seja possível confiar em exec*
para fazer a maior parte disso para você, o shell na prática pode implementar essa consulta propriamente dita, principalmente para fins de armazenamento em cache, mas o comportamento do cache vazio deve ser semelhante. Os shells têm uma latitude bastante ampla e têm comportamentos sutilmente diferentes nos casos de canto.
Como você encontrou, Bash usa uma tabela de hash para lembrar os caminhos completos dos comandos vistos antes, e essa tabela pode ser acessada com a função hash
. A primeira vez que você executa um comando, ele pesquisa e, quando um resultado é encontrado, ele é adicionado à tabela, portanto, não é necessário incomodar a aparência da próxima vez que você tentar.
No zsh, por outro lado, o PATH
completo é geralmente pesquisado quando o shell é iniciado. Uma tabela de consulta é pré-preenchida com todos os nomes de comandos descobertos, de forma que as consultas de tempo de execução geralmente não sejam necessárias (a menos que um novo comando seja adicionado). Você pode perceber que isso acontece quando você tenta tabular um comando que não existia antes.
Shells muito leves, como dash
, tendem a delegar o máximo de comportamento possível à biblioteca do sistema e não se preocupam em lembrar os caminhos de comando anteriores.