Comment trouver de manière fiable le chemin complet d'un programme sur le PATH?

12

J'ai besoin de trouver le chemin d'un programme donné sur l' PATHutilisation d'un script shell. Le chemin doit être le chemin complet réel du programme, qui peut être transmis plus tard à l'une des exec*fonctions, qui ne recherche pas PATHlui - même, par exemple execv.

Il existe des programmes comme kill, qui sont disponibles en tant que programme réel et un shell intégré en même temps. Si tel est le cas, j'ai besoin du chemin complet vers le programme réel.

Il existe plusieurs utilitaires qui peuvent trouver un programme sur le PATHcomme spécifié dans la Section 2.9.1.1, Recherche et exécution de commandes du standard POSIX .

Il y en a which, qui ne fait partie d'aucune norme. Il peut s'agir d'un programme régulier sur certains systèmes, tandis que certains shells fournissent qu'il s'agit d'un programme intégré. Il semble être disponible sur la plupart des systèmes et des shells, mais les shells avec une version intégrée retournent également le nom de la fonction intégrée au lieu du chemin d'accès à l'exécutable. De plus, il n'est en aucune façon standardisé et peut renvoyer n'importe quelle sortie et prendre différentes options.

bash# which kill
/usr/bin/kill
dash# which kill
/usr/bin/kill
fish# which kill
/usr/bin/kill
mksh# which kill
/usr/bin/kill
tcsh# which kill
kill: shell built-in command.
zsh# which kill
kill: shell built-in command

Il y whenceen a un qui est intégré à quelques obus. Mais non disponible sur de nombreux obus. Il renverra également le nom de la fonction intégrée au lieu du chemin d'accès au programme. Un -ppeut être transmis à d'où changer ce comportement.

bash# whence kill
bash: whence: command not found
dash# whence kill
dash: 1: whence: not found
fish# whence kill
fish: Unknown command 'whence'
mksh# whence kill
kill
mksh# whence -p kill
/usr/bin/kill
tcsh# whence kill
whence: Command not found.
zsh# whence kill
kill
zsh# whence -p kill
/usr/bin/kill

Il y a le commandbuiltin spécifié par POSIX: 2008 . Malheureusement, il recherche également des commandes régulières et intégrées et renverra le nom du intégré au lieu du chemin d'accès au programme masqué par un intégré du même nom. Certains vieux obus ne l'ont pas encore implémenté.

bash# command -v kill
kill
dash# command -v kill
kill
fish# command -v kill
/usr/bin/kill
mksh# command -v kill
kill
tcsh# command -v kill
command: Command not found.
zsh# command -v kill
kill
Sebastian Schrader
la source
Je ne peux pas savoir si enableest spécifié dans POSIX ou non, mais si c'est le cas, vous pouvez utiliser enable -n whichpour désactiver le shell intégré pour which.
Muzer
et il y arealpath
Ipor Sircer
@Muzer Sur les coquilles que j'ai à ma disposition, enableest uniquement fourni par bashetzsh
Sebastian Schrader
1
Vous avez besoin d'une méthode fiable pour le shell spécifique qui exécute votre script et non pour tous les shells. Les scripts ne sont pas exécutés par un shell aléatoire mais spécifiquement par le shell spécifié dans la ligne shebang. Cela étant dit, en bash ce serait type -p. Bash et dash vous permettent de dire commandcommande pour exécuter un exécutable réel même s'il existe une fonction ou une fonction intégrée portant le même nom.
AlexP
1
@AlexP commandignore les fonctions (et les alias) mais PAS les commandes internes, comme le Q le dit correctement. Et vous ne pouvez pas toujours utiliser un shebang car il n'y a aucun chemin qui obtient un shell donné, ou même un shell POSIX, sur tous les systèmes.
dave_thompson_085

Réponses:

11

Recherchez-le vous-même.

export IFS=":"
[ -z "${1}" ] && exit 1
for dir in $PATH
do if [ -x "${dir}/${1}" ]
   then echo "${dir}/${1}"
        exit 0
   fi
done
echo ${1} not found
exit 1

Testé bash, dash, ksh, mksh,zsh

Mise à jour

Ce qui précède est bien pour un script autonome, mais si vous prévoyez d'incorporer cela dans un script plus grand, vous voudrez peut-être utiliser quelque chose de plus semblable au suivant.

function find_path() {
   IFS_SAVE="${IFS}"
   export IFS=":"
   [ -z "${1}" ] && exit 1
   for dir in $PATH
   do if [ -x "${dir}/${1}" ]
      then echo "${dir}/${1}"
           export IFS="${IFS_SAVE}"
           return 0
      fi
   done
   export IFS="${IFS_SAVE}"
   echo ${1} not found
   return 1
}

Il en est ainsi que IFSest restaurée après avoir trouvé le match, aussi permuté exit« s avec return» s

Zachary Brady
la source
1
Peut-être -x au lieu de -f?
Jeff Schaller
@JeffSchaller bon point, aucune raison de choisir des fichiers non exécutables.
Zachary Brady
3
Si vous intégrez cela dans un script shell plus grand (au lieu de le faire dans un script à part entière, comme cela est vraisemblablement prévu), vous voudrez peut-être restaurer l'ancienne valeur d'IFS par la suite - sinon cela pourrait avoir beaucoup d'effets sur le reste du script ...
psmears
Pourquoi exporter la IFSvariable? N'est-ce pas suffisant d'avoir cet ensemble dans le shell local? En parlant de local, serait- local IFSil portable? Ce qui précède peut mal interagir si quelque chose d'autre enregistre IFS de la même manière. En regardant cette question sur SE , cela localpeut fonctionner pour la plupart des shells bien qu'il ne soit pas POSIX. Mettre la version originale dans un (…)sous-shell peut également fonctionner.
MvG