Lorsque vous recherchez le chemin vers un exécutable ou vérifiez ce qui se passerait si vous entrez un nom de commande dans un shell Unix, il existe une pléthore d'utilitaires différents (which
, type
, command
, whence
, where
, whereis
, whatis
, hash
, etc.).
On entend souvent que which
doit être évité. Pourquoi? Que devrions-nous utiliser à la place?
Voici tout ce que vous n'auriez jamais pensé que vous ne voudriez jamais savoir à ce sujet:
Pour obtenir le chemin d'accès d'un exécutable dans un script Shell de type Bourne (il y a quelques mises en garde; voir ci-dessous):
ls=$(command -v ls)
Pour savoir si une commande donnée existe:
if command -v given-command > /dev/null 2>&1; then
echo given-command is available
else
echo given-command is not available
fi
À l'invite d'un shell interactif de type Bourne:
type ls
La commande which
est un héritage rompu du C-Shell et il vaut mieux la laisser seule dans des shells de type Bourne.
Il y a une distinction entre rechercher ces informations dans le cadre d'un script ou interactivement à l'invite du shell.
À l'invite du shell, le cas d'utilisation typique est: cette commande se comporte bizarrement, est-ce que j'utilise la bonne? Que s'est-il passé exactement lorsque j'ai tapé mycmd
? Puis-je regarder plus loin ce que c'est?
Dans ce cas, vous voulez savoir ce que fait votre shell lorsque vous appelez la commande sans réellement invoquer la commande.
Dans les scripts Shell, cela a tendance à être assez différent. Dans un script Shell, il n'y a aucune raison pour que vous souhaitiez savoir où ou ce qu'est une commande si tout ce que vous voulez faire est de l'exécuter. Généralement, ce que vous voulez savoir, c'est le chemin de l'exécutable, vous pouvez donc en obtenir plus d'informations (comme le chemin vers un autre fichier par rapport à celui-ci, ou lire les informations du contenu du fichier exécutable sur ce chemin).
De manière interactive, vous voudrez peut-être connaître tous les commandes my-cmd
Disponibles sur le système, dans les scripts, rarement.
La plupart des outils disponibles (comme c'est souvent le cas) ont été conçus pour être utilisés de manière interactive.
Un peu d'histoire d'abord.
Les premiers obus Unix jusqu'à la fin des années 70 n'avaient pas de fonctions ni d'alias. Seule la recherche traditionnelle d'exécutables dans $PATH
. csh
a introduit des alias vers 1978 (bien que csh
ait été d'abord publié dans 2BSD
, En mai 1979), ainsi que le traitement d'un .cshrc
Pour que les utilisateurs personnalisent le Shell (chaque Shell, comme csh
lit .cshrc
Même lorsqu'il n'est pas interactif comme dans les scripts).
alors que le Bourne Shell a été publié pour la première fois dans Unix V7 plus tôt en 1979, le support des fonctions n'a été ajouté que beaucoup plus tard (1984 dans SVR2), et de toute façon, il n'a jamais eu de fichier rc
(le .profile
est pour configurer votre environnement, pas le Shell en soi).
csh
est devenu beaucoup plus populaire que le Bourne Shell car (bien qu'il ait une syntaxe bien pire que le Bourne Shell), il ajoutait beaucoup de fonctionnalités plus pratiques et agréables pour une utilisation interactive.
Dans 3BSD
(1980), un which
script csh a été ajouté pour les utilisateurs de csh
afin d'aider à identifier un exécutable, et il s'agit d'un script à peine différent. vous pouvez trouver en tant que which
sur de nombreux Unices commerciaux de nos jours (comme Solaris, HP/UX, AIX ou Tru64).
Ce script lit le ~/.cshrc
De l'utilisateur (comme tous les scripts csh
sauf s'il est appelé avec csh -f
), Et recherche le (s) nom (s) de commande fourni (s) dans la liste des alias et dans $path
(Le tableau que csh
maintient basé sur $PATH
).
Voilà, which
est venu en premier pour le Shell le plus populaire à l'époque (et csh
était encore populaire jusqu'au milieu des années 90), ce qui est la principale raison pour laquelle il a été documenté dans les livres et est encore largement utilisé.
Notez que, même pour un utilisateur csh
, ce script csh which
ne vous donne pas nécessairement les bonnes informations. Il obtient les alias définis dans ~/.cshrc
, Pas ceux que vous avez définis plus tard à l'invite ou par exemple en source
ing un autre fichier csh
, et (bien que cela ne être une bonne idée), PATH
pourrait être redéfini dans ~/.cshrc
.
L'exécution de cette commande which
à partir d'un Bourne Shell, rechercherait toujours les alias définis dans votre ~/.cshrc
, Mais si vous n'en avez pas parce que vous n'utilisez pas csh
, ce serait vous obtiendrez probablement toujours la bonne réponse.
Une fonctionnalité similaire n'a été ajoutée au Bourne Shell qu'en 1984 dans SVR2 avec la commande intégrée type
. Le fait qu'il soit intégré (par opposition à un script externe) signifie qu'il peut vous donne les bonnes informations (dans une certaine mesure) car il a accès aux internes du Shell.
La commande initiale type
souffrait d'un problème similaire à celui du script which
en ce qu'elle ne renvoyait pas un état de sortie d'échec si la commande n'était pas trouvée. En outre, pour les exécutables, contrairement à which
, il génère quelque chose comme ls is /bin/ls
Au lieu de simplement /bin/ls
, Ce qui le rend moins facile à utiliser dans les scripts.
Bourne Shell de Unix Version 8 (non publié dans la nature) a fait renommer son type
en whatis
. Et le Plan9 (le futur successeur d'Unix) Shell rc
(et ses dérivés comme akanga
et es
) ont également whatis
.
Le Korn Shell (un sous-ensemble sur lequel la définition POSIX sh est basée), développé au milieu des années 80 mais non largement disponible avant 1988, a ajouté de nombreuses fonctionnalités de csh
(éditeur de ligne, alias ...) sur le dessus du Bourne Shell. Il a ajouté sa propre fonction whence
(en plus de type
) qui a pris plusieurs options (-v
Pour fournir la sortie verbeuse de type type
, et -p
Pour rechercher uniquement les exécutables (pas les alias/fonctions ...)).
Par coïncidence avec la tourmente concernant les problèmes de droits d'auteur entre AT&T et Berkeley, quelques logiciels gratuits Les implémentations de Shell sont sorties à la fin des années 80 et au début des années 90. Tous les Shell Almquist (ash, pour remplacer le Bourne Shell dans les BSD), la mise en œuvre du domaine public de ksh (pdksh), bash
(parrainé par la FSF), zsh
sont sortis entre 1989 et 1991.
Ash, bien que destiné à remplacer le Bourne Shell, n'a pas eu de type
intégré beaucoup plus tard (dans NetBSD 1.3 et FreeBSD 2.3), bien qu'il ait eu hash -v
. OSF/1 /bin/sh
Avait une fonction intégrée type
qui renvoyait toujours 0 à OSF/1 v3.x. bash
n'a pas ajouté de whence
mais a ajouté une option -p
À type
pour imprimer le chemin (type -p
Serait comme whence -p
) Et -a
Pour signaler tous les commandes correspondantes. tcsh
a créé which
et a ajouté une commande where
agissant comme type -a
De bash
. zsh
les a tous.
Le shell fish
(2005) a une commande type
implémentée en tant que fonction.
Le script which
csh a quant à lui été supprimé de NetBSD (car il était intégré dans tcsh et peu utilisé dans d'autres shells), et la fonctionnalité ajoutée à whereis
(lorsqu'elle était invoquée en tant que which
, whereis
se comporte comme which
sauf qu'il ne recherche que les exécutables dans $PATH
). Dans OpenBSD et FreeBSD, which
a également été remplacé par un code écrit en C qui recherche les commandes dans $PATH
Uniquement.
Il existe des dizaines d'implémentations d'une commande which
sur divers Unices avec une syntaxe et un comportement différents.
Sous Linux (à côté de ceux intégrés dans tcsh
et zsh
), nous trouvons plusieurs implémentations. Sur les systèmes Debian récents par exemple, c'est un simple script POSIX Shell qui recherche les commandes dans $PATH
.
busybox
possède également une commande which
.
Il existe un GNU
which
qui est probablement le plus extravagant. Il essaie d'étendre ce que le script csh which
a fait à d'autres shells: vous pouvez lui dire quels sont vos alias et fonctions afin qu'il puisse vous donner une meilleure réponse (et je crois que certaines distributions Linux définissent des alias globaux autour que pour bash
faire cela).
zsh
a quelques opérateurs pour développer le chemin des exécutables: l'opérateur =
extension du nom de fichier et :c
Modificateur d'expansion de l'historique (ici appliqué à expansion des paramètres):
$ print -r -- =ls
/bin/ls
$ cmd=ls; print -r -- $cmd:c
/bin/ls
zsh
, dans le module zsh/parameters
Fait également de la table de hachage de commande le tableau associatif commands
:
$ print -r -- $commands[ls]
/bin/ls
L'utilitaire whatis
(à l'exception de celui dans Unix V8 Bourne Shell ou Plan 9 rc
/es
) n'est pas vraiment lié car c'est uniquement pour la documentation (greps la base de données whatis, c'est le synopsis de la page de manuel ').
whereis
a également été ajouté dans 3BSD
En même temps que which
bien qu'il ait été écrit en C
, et non csh
, et est utilisé pour recherche en même temps, l'exécutable, la page de manuel et la source mais non basés sur l'environnement actuel. Encore une fois, cela répond à un besoin différent.
Maintenant, sur le front standard, POSIX spécifie les commandes command -v
Et -V
(Qui étaient optionnelles jusqu'à POSIX.2008). UNIX spécifie la commande type
(aucune option). C'est tout (where
, which
, whence
ne sont spécifiés dans aucune norme)
Jusqu'à certaines versions, type
et command -v
Étaient facultatifs dans la spécification Linux Standard Base, ce qui explique pourquoi, par exemple, certaines anciennes versions de posh
(bien que basées sur pdksh
qui avait les deux) n'en avait pas non plus. command -v
A également été ajouté à certaines implémentations de Bourne Shell (comme sur Solaris).
Le statut actuel est que type
et command -v
Sont omniprésents dans tous les shells de type Bourne (bien que, comme l'a noté @jarno, notez la mise en garde/bug dans bash
quand ce n'est pas le cas). en mode POSIX ou certains descendants d'Almquist Shell ci-dessous dans les commentaires). tcsh
est le seul Shell où vous voudriez utiliser which
(car il n'y a pas de type
et which
est intégré).
Dans les shells autres que tcsh
et zsh
, which
peut vous indiquer le chemin de l'exécutable donné tant qu'il n'y a pas d'alias ou de fonction du même nom dans l'un de nos ~/.cshrc
, ~/.bashrc
Ou tout autre fichier de démarrage Shell et vous ne définissez pas $PATH
Dans votre ~/.cshrc
. Si vous avez un alias ou une fonction définie pour cela, il peut ou non vous en parler, ou vous dire la mauvaise chose.
Si vous voulez connaître toutes les commandes par un nom donné, il n'y a rien de portable. Vous utiliseriez where
dans tcsh
ou zsh
, type -a
Dans bash
ou zsh
, whence -a
dans ksh93 et dans d'autres shells, vous pouvez utiliser type
en combinaison avec which -a
qui peut fonctionner.
Maintenant, pour obtenir le chemin d'accès d'un exécutable dans un script, il y a quelques mises en garde:
ls=$(command -v ls)
serait la façon standard de le faire.
Il y a cependant quelques problèmes:
type
, which
, command -v
... tous utilisent l'heuristique pour trouver le chemin. Ils parcourent les composants $PATH
Et trouvent le premier fichier non-répertoire pour lequel vous avez l'autorisation d'exécution. Cependant, selon le Shell, quand il s'agit d'exécuter la commande, beaucoup d'entre eux (Bourne, AT&T ksh, zsh, ash ...) les exécuteront simplement dans l'ordre de $PATH
Jusqu'à ce que execve
ne renvoie pas d'erreur. Par exemple, si $PATH
Contient /foo:/bar
Et que vous souhaitez exécuter ls
, ils essaieront d'abord d'exécuter /foo/ls
Ou si cela échoue /bar/ls
. Maintenant, l'exécution de /foo/ls
Peut échouer car vous n'avez pas de permission d'exécution mais aussi pour de nombreuses autres raisons, comme ce n'est pas un exécutable valide. command -v ls
Rapporterait /foo/ls
Si vous avez l'autorisation d'exécution pour /foo/ls
, Mais l'exécution de ls
pourrait en fait exécuter /bar/ls
Si /foo/ls
n'est pas un exécutable valide.foo
est une fonction intégrée ou une fonction ou un alias, command -v foo
renvoie foo
. Avec certains shells comme ash
, pdksh
ou zsh
, il peut également retourner foo
si $PATH
Inclut la chaîne vide et qu'il y a un exécutable Fichier foo
dans le répertoire courant. Dans certaines circonstances, vous devrez peut-être en tenir compte. Gardez à l'esprit, par exemple, que la liste des buildins varie en fonction de l'implémentation du shell (par exemple, mount
est parfois intégré à busybox sh
), et par exemple bash
peut obtenir des fonctions de l'environnement.$PATH
contient des composants de chemin d'accès relatif (généralement .
ou la chaîne vide qui se réfèrent tous les deux au répertoire en cours mais peuvent être n'importe quoi), selon le shell, command -v cmd
peut ne pas sortir un chemin absolu. Ainsi, le chemin que vous obtenez au moment où vous exécutez command -v
Ne sera plus valide après avoir cd
ailleurs./opt/ast/bin
(Bien que ce chemin exact puisse varier selon les systèmes, je crois) est en vous $PATH
, Ksh93 mettra à disposition quelques extensions supplémentaires (chmod
, cmp
, cat
...), mais command -v chmod
Renverra /opt/ast/bin/chmod
Même si ce chemin n'existe pas.Pour savoir si une commande donnée existe en standard, vous pouvez faire:
if command -v given-command > /dev/null 2>&1; then
echo given-command is available
else
echo given-command is not available
fi
which
(t)csh
Dans csh
et tcsh
, vous n'avez pas beaucoup de choix. Dans tcsh
, c'est très bien car which
est intégré. Dans csh
, ce sera la commande système which
, qui peut ne pas faire ce que vous voulez dans quelques cas.
Un cas où il peut être judicieux d'utiliser which
est si vous voulez connaître le chemin d'une commande, en ignorant les fonctions ou fonctions internes potentielles de Shell dans bash
, csh
(pas tcsh
), dash
ou Bourne
Scripts de shell, c'est-à-dire des shells qui n'ont pas whence -p
(Comme ksh
ou zsh
), command -ev
(Comme yash
), whatis -p
(rc
, akanga
) ou une fonction intégrée which
(comme tcsh
ou zsh
) sur les systèmes où which
est disponible et n'est pas le script csh
.
Si ces conditions sont remplies, alors:
echo=$(which echo)
vous donnerait le chemin de la première echo
dans $PATH
(sauf dans les cas d'angle), indépendamment du fait que echo
se trouve également être une fonction/alias/fonction intégrée de Shell ou non .
Dans d'autres coquilles, vous préférez:
echo==echo
ou echo=$commands[echo]
ou echo=${${:-echo}:c}
echo=$(whence -p echo)
echo=$(command -ev echo)
echo=`whatis -p echo`
(attention aux chemins avec des espaces)set echo (type -fp echo)
Notez que si tout ce que vous voulez faire est exécuter cette commande echo
, vous n'avez pas besoin d'obtenir son chemin, vous pouvez simplement faire:
env echo this is not echoed by the builtin echo
Par exemple, avec tcsh
, pour empêcher l'utilisation du which
intégré:
set Echo = "`env which echo`"
Un autre cas où vous voudrez peut-être utiliser which
est lorsque vous avez réellement besoin une commande externe. POSIX nécessite que toutes les commandes internes de Shell (comme command
) soient également disponibles en tant que commandes externes, mais ce n'est malheureusement pas le cas pour command
sur de nombreux systèmes. Par exemple, il est rare de trouver une commande command
sur les systèmes d'exploitation Linux alors que la plupart d'entre eux ont une commande which
(bien que différentes avec des options et des comportements différents).
Les cas où vous voudrez peut-être une commande externe se trouveront partout où vous exécuterez une commande sans appeler un shell POSIX.
Les fonctions system("some command line")
, popen()
... de C ou de divers langages invoquent un Shell pour analyser cette ligne de commande, donc system("command -v my-cmd")
y fonctionne. Une exception à cela serait Perl
qui optimise le Shell s'il ne voit aucun caractère spécial du Shell (autre que l'espace). Cela vaut également pour son opérateur de backtick:
$ Perl -le 'print system "command -v emacs"'
-1
$ Perl -le 'print system ":;command -v emacs"'
/usr/bin/emacs
0
$ Perl -e 'print `command -v emacs`'
$ Perl -e 'print `:;command -v emacs`'
/usr/bin/emacs
L'ajout de ce :;
Ci-dessus force Perl
à y invoquer un Shell. En utilisant which
, vous n'auriez pas à utiliser cette astuce.
Les raisons pour lesquelles on peut ne pas vouloir utiliser which
ont déjà été expliquées, mais voici quelques exemples sur quelques systèmes où which
échoue réellement.
Sur les shells de type Bourne, nous comparons la sortie de which
avec la sortie de type
(type
étant un shell intégré, c'est censé être la vérité fondamentale, comme c'est le Shell qui nous dit comment il invoquerait une commande).
De nombreux cas sont des cas de coin , mais gardez à l'esprit que which
/type
sont souvent utilisés dans les cas de coin (pour trouver la réponse à un comportement inattendu comme: pourquoi diable cette commande se comporte-t-elle ainsi, laquelle est-ce que j'appelle? ).
Le cas le plus évident concerne les fonctions:
$ type ls
ls is a function
ls ()
{
[ -t 1 ] && set -- -F "$@";
command ls "$@"
}
$ which ls
/bin/ls
La raison étant que which
ne rend compte que des exécutables, et parfois des alias (mais pas toujours ceux de votre Shell), pas des fonctions .
L'exemple GNU dont la page de manuel a un cassé (car ils ont oublié de citer $@
) Exemple sur la façon de l'utiliser pour signaler des fonctions également, mais comme pour les alias, n'implémente pas d'analyseur de syntaxe Shell, il est facilement dupe:
$ which() { (alias; declare -f) | /usr/bin/which --tty-only --read-alias --read-functions --show-tilde --show-dot "$@";}
$ f() { echo $'\n}\ng ()\n{ echo bar;\n}\n' >> ~/foo; }
$ type f
f is a function
f ()
{
echo '
}
g ()
{ echo bar;
}
' >> ~/foo
}
$ type g
bash: type: g: not found
$ which f
f ()
{
echo '
}
$ which g
g ()
{ echo bar;
}
Un autre cas évident est les commandes internes ou les mots clés, car which
étant une commande externe n'a aucun moyen de savoir quelles commandes internes votre shell possède (et certains shells comme zsh
, bash
ou ksh
peut charger les buildins dynamiquement):
$ type echo . time
echo is a Shell builtin
. is a Shell builtin
time is a Shell keyword
$ which echo . time
/bin/echo
which: no . in (/bin:/usr/bin)
/usr/bin/time
(cela ne s'applique pas à zsh
où which
est intégré)
$ csh
% which ls
ls: aliased to ls -F
% unalias ls
% which ls
ls: aliased to ls -F
% ksh
$ which ls
ls: aliased to ls -F
$ type ls
ls is a tracked alias for /usr/bin/ls
En effet, sur la plupart des Unices commerciaux, which
(comme dans l'implémentation d'origine sur 3BSD) est un script csh
qui lit ~/.cshrc
. Les alias qu'il rapportera sont ceux qui y sont définis quels que soient les alias que vous avez actuellement définis et quel que soit le shell que vous utilisez réellement.
Dans HP/UX ou Tru64:
% echo 'setenv PATH /bin:/usr/bin' >> ~/.cshrc
% setenv PATH ~/bin:/bin:/usr/bin
% ln -s /bin/ls ~/bin/
% which ls
/bin/ls
(les versions Solaris et AIX ont corrigé ce problème en enregistrant $path
avant de lire le ~/.cshrc
et de le restaurer avant de rechercher la ou les commandes)
$ type 'a b'
a b is /home/stephane/bin/a b
$ which 'a b'
no a in /usr/sbin /usr/bin
no b in /usr/sbin /usr/bin
Ou:
$ d="$HOME/my bin"
$ mkdir "$d"; PATH=$PATH:$d
$ ln -s /bin/ls "$d/myls"
$ type myls
myls is /home/stephane/my bin/myls
$ which myls
no myls in /usr/sbin /usr/bin /home/stephane/my bin
(bien sûr, étant un script csh
, vous ne pouvez pas vous attendre à ce qu'il fonctionne avec des arguments contenant des espaces ...)
$ type which
which is aliased to `alias | /usr/bin/which --tty-only --read-alias --show-dot --show-tilde'
$ alias foo=': "|test|"'
$ which foo
alias foo=': "|test|"'
/usr/bin/test
$ alias $'foo=\nalias bar='
$ unalias bar
-bash: unalias: bar: not found
$ which bar
alias bar='
Sur ce système, il existe un alias défini à l'échelle du système qui encapsule la commande GNU which
.
La sortie fausse est parce que which
lit la sortie de bash
alias
mais ne sait pas comment l'analyser correctement et utilise l'heuristique (un alias par ligne, recherche la première commande trouvée après un |
, ;
, &
...)
Le pire sur CentOS est que zsh
a une commande intégrée which
parfaitement fine mais CentOS a réussi à la casser en la remplaçant par un alias non fonctionnel GNU = which
.
(mais s'applique à la plupart des systèmes avec de nombreux shells)
$ unset PATH
$ which which
/usr/local/bin/which
$ type which
which is a tracked alias for /bin/which
Sur Debian, /bin/which
Est un script /bin/sh
. Dans mon cas, sh
étant dash
mais c'est la même chose quand c'est bash
.
Un PATH
non défini ne doit pas désactiver la recherche PATH
, mais signifie utiliser le CHEMIN par défaut du système qui malheureusement sur Debian, personne n'est d'accord (dash
et bash
ont /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
, zsh
a /bin:/usr/bin:/usr/ucb:/usr/local/bin
, ksh93
a /bin:/usr/bin
, mksh
a /usr/bin:/bin
($(getconf PATH)
), execvp()
(comme dans env
) a :/bin:/usr/bin
(oui, regarde d'abord dans le répertoire courant!)).
C'est pourquoi which
se trompe ci-dessus car il utilise dash
par défaut PATH
qui est différent de ksh93
Ce n'est pas mieux avec GNU which
qui rapporte:
which: no which in ((null))
(fait intéressant, il y a en effet un /usr/local/bin/which
sur mon système qui est en fait un script akanga
fourni avec akanga
(un rc
dérivé du shell où la valeur par défaut PATH
est /usr/ucb:/usr/bin:/bin:.
))
Celui auquel Chris fait référence dans sa réponse :
$ PATH=$HOME/bin:/bin
$ ls /dev/null
/dev/null
$ cp /bin/ls bin
$ type ls
ls is hashed (/bin/ls)
$ command -v ls
/bin/ls
$ which ls
/home/chazelas/bin/ls
De même, après avoir appelé hash
manuellement:
$ type -a which
which is /usr/local/bin/which
which is /usr/bin/which
which is /bin/which
$ hash -p /bin/which which
$ which which
/usr/local/bin/which
$ type which
which is hashed (/bin/which)
which
et parfois type
échouent:$ mkdir a b
$ echo '#!/bin/echo' > a/foo
$ echo '#!/' > b/foo
$ chmod +x a/foo b/foo
$ PATH=b:a:$PATH
$ which foo
b/foo
$ type foo
foo is b/foo
Maintenant, avec quelques obus:
$ foo
bash: ./b/foo: /: bad interpreter: Permission denied
Avec les autres:
$ foo
a/foo
Ni which
ni type
ne peuvent savoir à l'avance que b/foo
Ne peut pas être exécuté. Certains shells comme bash
, ksh
ou yash
, lors de l'appel de foo
tenteront en effet d'exécuter b/foo
Et de signaler une erreur, tandis que d'autres (comme zsh
, ash
, csh
, Bourne
, tcsh
) s'exécutera a/foo
en cas d'échec du execve()
appel système sur b/foo
.
Une chose qui (d'après mon survol rapide) semble ne pas avoir été mentionnée par Stéphane est que which
n'a aucune idée de la table de hachage du chemin de votre Shell. Cela a pour effet qu'il peut retourner un résultat qui n'est pas représentatif de ce qui est réellement exécuté, ce qui le rend inefficace dans le débogage.
Dans l'esprit UNIX: Faites bien à chaque programme une chose.
Si vous devez répondre:
Cela correspondra à la première phrase de votre question:
Lorsque vous recherchez le chemin vers un exécutable……
Ceci est répondu en regardant le système de fichiers pour un nom de fichier exécutable correspondant à commandName. C'est exactement ce que fait un which
externe (et certains éléments intégrés de Shell qui le font également). Il recherche dans le CHEMIN le premier nom de fichier qui correspond à commandName. Pour le faire correctement, il doit regarder les informations externes (au Shell) et ne pas être affecté par d'autres informations internes au Shell.
Mais à ce stade, nous tombons dans la question de:
Si c'est aujourd'hui (2019) Debian a fourni quel exécutable vous pouvez faire:
env which which
pour obtenir le chemin d'accès complet à l'exécutable fourni par PATH
. La commande env
garantit que peu importe le Shell que vous utilisez l'exécutable externe (vers le Shell) sera utilisé, puis en utilisant cet exécutable externe which
, nous lui demandons de fournir le chemin vers le quel exécutable lui-même. Et, dans n'importe quel shell, si le nom de la commande est un chemin d'accès complet (avec au moins un /
) Seul cet exécutable sera appelé, pas autre chose. À cet égard, command -v
Échoue car il ne fournit qu'un nom.
$ command -v which which
Et une command=$(command -v which)
exécutera toujours une fonction intégrée lors de l'exécution:
% $command cd
cd: Shell built-in command
Si vous avez un système qui n'a pas d'exécutable appelé lequel (la plupart des systèmes Linux en ont un), vous pouvez en créer un dans ~/bin/which
Avant /bin/
Dans le CHEMIN, donc les exécutables personnels remplacent ceux du système comme celui au bas de ce post:
Cet exécutable listera (par défaut) tous les exécutables trouvés dans le PATH. Si seul le premier est requis, l'option -f
Est disponible.
Cela vient de votre deuxième phrase:
vérifier ce qui se passerait si vous entrez un nom de commande dans un shell Unix
Ce deuxième sujet tente de trouver une réponse absolue à une question à laquelle il est impossible de répondre. Les coquilles ont des vues divergentes, des cas d'angle et (au minimum) des interprétations différentes. Keep it Simple Sam (KISS).
il y a une pléthore d'utilitaires différents (qui, type, commande, d'où, où, où, quoi, hachage, etc.).
Bien sûr, tous tentent de nombreuses façons différentes de répondre à une question qui est intrinsèquement impossible de répondre correctement et dans tous les cas.
Nous entendons souvent ce qui devrait être évité.
Qui dit ça, c'est toi?
Dans l'esprit UNIX: Faites bien chaque programme.
Le programme externe which
fait une chose: trouver le premier exécutable sur le CHEMIN qui porte le même nom que le nom de la commande. Et le fait bien.
Je ne connais aucun autre programme ou utilitaire qui répond à cette question d'une manière plus fondamentale. En tant que tel, il est utile et pourrait être utilisé en cas de besoin.
L'alternative la plus proche semble être: command -pv commandName
, Mais cela rendra également compte des buildins et des alias. Pas une réponse claire.
Bien sûr, which
est limité, il ne répond pas à toutes les questions , aucun outil ne pourrait le faire (enfin, pas encore ...). Mais il est utile lorsqu'il est utilisé pour répondre à la question à laquelle il a été conçu pour répondre (celle juste au-dessus). Tout comme ed
était limité, puis sed
est apparu (ou vi
/vim
). Ou comme awk
était limité et faisait apparaître et étendre Perl. Pourtant, ed
, sed
ou/et awk
ont des cas d'utilisation spécifiques où vim
ou Perl
sont pas les meilleurs outils.
Pourquoi?
Probablement parce que which
ne répond qu'à une partie de la question qu'un utilisateur Shell pourrait poser:
Qu'est-ce qui est exécuté lorsque je tape un commandName?
Qui devrait être disponible (dans de nombreux systèmes) en tant qu'exécutable externe.
La seule façon sûre d'appeler cet outil externe est d'utiliser env pour sortir du shell et d'appeler ensuite which
(qui fonctionne dans tous les shells):
$ env which which
/usr/bin/which
Ou utilisez le chemin d'accès complet à which
(qui peut varier sur différents systèmes):
/usr/bin/which which
Pourquoi est-ce que hack
est nécessaire? Parce que le Shell (spécialement zsh) cache which
:
$ zsh -c 'which which'
which: Shell built-in command
L'utilisation de which
externe rendra le chemin complet vers le premier exécutable du nom donné qui pourrait être trouvé sur le CHEMIN:
$ env which kbd_mode
/bin/kbd_mode
$ env which Sudo
/usr/bin/Sudo
Être un outil externe (comme env
) explique parfaitement pourquoi il ne rapportera pas les informations internes de Shell. Comme les alias, les fonctions, les fonctions intégrées, les fonctions spéciales, les variables Shell (non exportées), etc.:
$ env which ls
/usr/bin/ls
$ env which ll # empty output
La sortie vide de ll
(un alias commun pour ll='ls -l'
) Indique que ll
n'est pas lié à un programme exécutable, ou du moins, qu'il n'y a pas de fichier exécutable nommé ll
dans le CHEMIN. L'utilisation de ll
devrait appeler quelque chose d'autre, dans ce cas, un alias:
$ type ll
ll is aliased to `ls -l'
Cela peut être considéré comme une limitation, mais je le trouve souvent comme une simplification utile: s'il ne trouve pas l'exécutable (ce qui est généralement le cas), la réponse se trouve ailleurs, 97% du temps type
est assez.
Pour le reste, 3% des problèmes pour trouver ce qui est exécuté, nous tombons dans la complexité interne which
:
C'est là que toute l'histoire et les complexités commencent.
Sans perdre de généralité, nous ne devrions considérer qu'une simple commande:
commandName args
Les commandes composées comme if, while, (…), list, etc., contiendront des commandes simples et leur jonction activera les commandes composées.
Ainsi, la question centrale pourrait être reformulée comme suit:
What will be executed when *commandName* is used as the first Word of a command line?*
Pourquoi le premier mot? Parce que le shell supprimera toutes les affectations de variables et les redirections jusqu'à ce qu'il trouve un premier mot :
Comme expliqué dans une description de SIMPLE COMMAND EXPANSION:
Les mots que l'analyseur a marqués comme affectations de variables (ceux précédant le nom de la commande) et les redirections sont enregistrés pour un traitement ultérieur.
Les mots qui ne sont pas des affectations de variables ou des redirections sont développés. S'il reste des mots après l'expansion, le premier mot est considéré comme le nom de la commande et les mots restants sont les arguments.
Si le nom de la commande ne contient aucune barre oblique, le shell tente de le localiser.
S'il n'est pas encore trouvé, une recherche complète des répertoires dans PATH est effectuée.
Si la recherche réussit ou si le nom de la commande contient une ou plusieurs barres obliques, le shell exécute le programme nommé dans un environnement d'exécution distinct.
Si cette exécution échoue parce que le fichier n'est pas au format exécutable et que le fichier n'est pas un répertoire, il est supposé être un script Shell, un fichier contenant des commandes Shell. Si le programme est un fichier commençant par # !, le reste de la première ligne spécifie un interpréteur pour le programme. Le shell exécute l'interpréteur spécifié sur les systèmes d'exploitation qui ne gèrent pas eux-mêmes ce format exécutable. Les arguments de l'interpréteur consistent en un seul argument facultatif suivant le nom de l'interpréteur sur la première ligne du programme, suivi du nom du programme, suivi des arguments de commande, le cas échéant.
Sinon, la commande se ferme.
Ainsi, le nom de l'exécutable peut changer dans 2
Par expansion des variables, dans 3.1
Par expansion d'alias, dans 3.2
À nouveau par expansion de variable, dans 3.3
par ce qui est à l'intérieur d'une fonction, dans 3.5
(dans de rares cas) par un nom différent dans la table de hachage et dans 6
parce qu'un Shebang de script Shell ou (s'il n'y a pas de Shebang) le la première ligne du script appelle un autre commandName.
Tout ce qui précède est fait par le Shell, c'est pourquoi la seule façon possible de savoir ce qui sera exécuté est de demander au Shell lui-même. Mais même le shell actuel pourrait ne pas savoir ce qui sera exécuté à l'avenir jusqu'à ce qu'un script externe soit chargé en modifiant les variables à développer ou en appelant un autre commandName.
En plus d'être assez complexe (6 endroits possibles pour changer le nom de la commande), il n'y a aucun moyen que tout Shell actuel sache ce qui se passera à l'avenir jusqu'à ce que la commande soit réellement tentée.
C'est pourquoi je dis qu'il est impossible de donner une réponse parfaitement correcte, seulement des approximations successives plus proches.
type
et command
Les commandes type
et command -v
Sont demandées par POSIX. Ils devraient fonctionner dans la plupart des coquilles, et ils le font, sauf dans csh, tcsh, fish et rc.
Les deux commandes peuvent être utilisées pour fournir un autre point de vue dont la commande sera exécutée.
whence
, where
, whereis
, whatis
, hash
Ensuite, il y a whence
, where
, whereis
, whatis
, hash
et quelques autres. Toutes les réponses différentes à la même question. Tous fonctionnent de différentes manières dans différentes coquilles. Probablement, whence
est le plus courant après type
. Les autres sont des solutions spéciales qui répondent à la même question de différentes manières.
Que devrions-nous utiliser à la place?
Probablement which
d'abord pour savoir s'il existe un exécutable du nom de commandName, puis type
et command
et ensuite, si le commandName n'a pas encore été trouvé: whence
, where
, whereis
, whatis
, hash
dans cet ordre.
which
.#! /bin/sh
set -ef; oldIFS=$IFS; IFS=:
say()( IFS=" "; printf "%s\n" "$*"; )
say "Simplified version of which."
usage(){ say Usage: "$0" [-f] args; }
if [ "$#" -eq 0 ]; then say Missing argument(s); usage; exit 2; fi
firstmatch=0
while getopts f whichopts; do
case "$whichopts" in
f) firstmatch=1 ;;
?) usage; exit 3 ;;
esac
done
[ "$OPTIND" -gt 1 ] && shift `expr "$OPTIND" - 1`
allret=0; [ "$#" -eq 0 ] && allret=1
for program in "$@"; do
ret=1
for element in $PATH''; do
case "$program" in
*/*) element="$program"; loop=0;;
*) element="${element:-.}/$program"; loop=1;;
esac
if [ -f "$element" ] && [ -x "$element" ]; then
say "$element"
ret=0
if [ "$firstmatch" -eq 1 ] || [ "$loop" -eq 0 ]; then break; fi
fi
done
[ "$ret" -eq 1 ] && allret=1
done
IFS="$oldIFS"
exit "$allret"
Nous entendons souvent ce qui devrait être évité. Pourquoi? Que devrions-nous utiliser à la place?
Je n'ai jamais entendu ça. Veuillez fournir des exemples spécifiques. Je m'inquiéterais de votre distribution Linux et des packages logiciels installés, car c'est de là que vient which
!
SLES 11,4 x86-64
dans la version 6.18.01 de tcsh:
> which which
which: Shell built-in command.
dans la version bash 3.2-147:
> which which
/usr/bin/which
> which -v
GNU which v2.19, Copyright (C) 1999 - 2008 Carlo Wood.
GNU which comes with ABSOLUTELY NO WARRANTY;
This program is free software; your freedom to use, change
and distribute this program is protected by the GPL.
which
fait partie de til-linux un package standard distribué par la Linux Kernel Organization pour une utilisation dans le cadre du système d'exploitation Linux. Il fournit également ces autres fichiers
/bin/dmesg
/bin/findmnt
/bin/logger
/bin/lsblk
/bin/more
/bin/mount
/bin/umount
/sbin/adjtimex
/sbin/agetty
/sbin/blkid
/sbin/blockdev
/sbin/cfdisk
/sbin/chcpu
/sbin/ctrlaltdel
/sbin/elvtune
/sbin/fdisk
/sbin/findfs
/sbin/fsck
/sbin/fsck.cramfs
/sbin/fsck.minix
/sbin/fsfreeze
/sbin/fstrim
/sbin/hwclock
/sbin/losetup
/sbin/mkfs
/sbin/mkfs.bfs
/sbin/mkfs.cramfs
/sbin/mkfs.minix
/sbin/mkswap
/sbin/nologin
/sbin/pivot_root
/sbin/raw
/sbin/sfdisk
/sbin/swaplabel
/sbin/swapoff
/sbin/swapon
/sbin/switch_root
/sbin/wipefs
/usr/bin/cal
/usr/bin/chrp-addnote
/usr/bin/chrt
/usr/bin/col
/usr/bin/colcrt
/usr/bin/colrm
/usr/bin/column
/usr/bin/cytune
/usr/bin/ddate
/usr/bin/fallocate
/usr/bin/flock
/usr/bin/getopt
/usr/bin/hexdump
/usr/bin/i386
/usr/bin/ionice
/usr/bin/ipcmk
/usr/bin/ipcrm
/usr/bin/ipcs
/usr/bin/isosize
/usr/bin/line
/usr/bin/linux32
/usr/bin/linux64
/usr/bin/look
/usr/bin/lscpu
/usr/bin/mcookie
/usr/bin/mesg
/usr/bin/mkzimage_cmdline
/usr/bin/namei
/usr/bin/rename
/usr/bin/renice
/usr/bin/rev
/usr/bin/script
/usr/bin/scriptreplay
/usr/bin/setarch
/usr/bin/setsid
/usr/bin/setterm
/usr/bin/tailf
/usr/bin/taskset
/usr/bin/time
/usr/bin/ul
/usr/bin/uname26
/usr/bin/unshare
/usr/bin/uuidgen
/usr/bin/wall
/usr/bin/whereis
/usr/bin/which
/usr/bin/write
/usr/bin/x86_64
/usr/sbin/addpart
/usr/sbin/delpart
/usr/sbin/fdformat
/usr/sbin/flushb
/usr/sbin/freeramdisk
/usr/sbin/klogconsole
/usr/sbin/ldattach
/usr/sbin/partx
/usr/sbin/rcraw
/usr/sbin/readprofile
/usr/sbin/rtcwake
/usr/sbin/setctsid
/usr/sbin/tunelp
mon util-linux
est la version 2.19. Les notes de version peuvent facilement être retrouvées à la v2.13 datée du (28 août 2007). Je ne sais pas quel était le but ou l'objectif de cela, il n'a certainement pas été répondu dans cette longue chose qui a été votée 331 fois.