A solução óbvia seria usar a divisão da palavra shell, mas cuidado com algumas dicas:
IFS=:
set -f
for dir in $PATH; do
dir=${dir:-.}
[ -x "${dir%/}/$1" ] && printf "%s\n" "$dir"
done
Você precisa de set -f
porque quando uma variável é deixada sem aspas, tanto divisão de palavras como geração de nome de arquivo ( globbing ) são executadas nela e aqui você só quer dividir palavras (por exemplo, no caso improvável de que $PATH
contenha /usr/local/*bin*
, você quer procurar na pasta /usr/local/*bin*
, não em /usr/local/bin
e /usr/local/sbin
..., e se PATH
contiver /*/*/*/../../../*/*/*/*/../../../*/*/*/*
, você não quer que sua máquina caia)
Um componente vazio $PATH
significa o diretório atual ( .
), não /
. $dir/$1
não estaria correto nesse caso. A solução é escrever $dir${dir:+/}$1
ou alterar $dir
para .
nesse caso (o que fornece uma saída mais útil quando exibido com printf '%s\n' "$dir"
.
//foo
não é necessariamente igual a /foo
, portanto, se /
estiver em $PATH
, você não deseja $dir/$1
, que seria //$1
. Daí o ${dir%/}
para remover uma barra final.
Depois, há alguns outros problemas:
Para $PATH
, ":"
é um separador de campo enquanto para $IFS
, é um campo terminator (sim, eu sei, S
is para S eparator , culpe ksh e POSIX por padronizar o comportamento do ksh).
Portanto, se $PATH
for /usr/bin:/bin:
(que é uma prática ruim, mas ainda é comumente encontrada), isso significa "/usr/bin"
, "/bin"
e ""
(ou seja, o diretório atual), enquanto a divisão da palavra shell (todas as shells POSIX, exceto zsh
) dividirão isso em /usr/bin
e /bin
apenas.
Se $PATH
estiver definido, mas vazio, isso significa: "procure apenas no diretório atual" .
Enquanto os shells (incluindo aqueles que tratam $IFS
como separador) irão expandi-lo para uma lista vazia.
Por último, mas não menos importante. Se $PATH
não está definido, então isso tem um significado especial que é: procurar na lista de pesquisa padrão do sistema , que infelizmente significa algo diferente dependendo de quem (qual comando) você pergunta.
$ env -u PATH bash -c 'type usbipd'
usbipd is /usr/local/sbin/usbipd
$ env -u PATH ksh -c 'type usbipd'
ksh: whence: usbipd: not found
E, basicamente, no seu script, você teria que adivinhar qual é o caminho de pesquisa padrão no contexto que é importante para você.
Observe que o POSIX deixa o comportamento não especificado quando $PATH
não está definido ou está vazio, portanto, isso não o ajudará. Isso também significa que o que eu disse acima pode não se aplicar a alguns sistemas POSIX / Unix passados, atuais ou futuros.
Em suma, analisar $PATH
para tentar descobrir de onde um comando seria executado é um assunto complicado.
Existe um comando padrão para isso, que é command
:
ls_path=$(command -v ls)
Mas o que se pode perguntar é: por que você quer saber?
Agora para restaurar o IFS ao seu valor padrão:
oldIFS=$IFS
IFS=:
...
IFS=$oldIFS
funcionará na prática na maioria dos casos, mas não é garantido que funcione pelo POSIX.
A razão é que, se $IFS
foi anteriormente desfeito, o que significa comportamento de divisão padrão (que está em shells POSIX, divididos em espaço, tabulação ou nova linha), após esses comandos, set mas vazio (o que significa não dividir ).
Outro problema em potencial é se você generalizar essa abordagem e usá-la em muitas funções diferentes, então, se na parte ...
acima, você estiver chamando uma função que faz a mesma coisa (faz uma cópia de $IFS
em $oldIFS
), você perderá o $oldIFS
original e restaurará o $IFS
errado.
Em vez disso, você pode usar subshells quando possível:
(
IFS=:
...
)
# only the subshell's IFS was affected, the parent still has its own IFS
Minha abordagem é definir o $ IFS (e ativar ou desativar set -f
) todas as vezes Eu preciso da divisão de palavras (o que é raro) e não restauro o valor anterior. Obviamente, isso não funciona se o script chamar o código de outra pessoa que não segue essa prática e assume um comportamento padrão de divisão de palavras.