Pourquoi ne pas utiliser «qui»? Quoi utiliser alors?

329

Lorsque vous cherchez le chemin d'un exécutable ou de vérifier ce qui se passerait si vous entrez un nom de commande dans un shell Unix, il y a une pléthore de différents services publics ( which, type, command, whence, where, whereis, whatis, hash, etc.).

Nous entendons souvent que cela whichdevrait être évité. Pourquoi? Que devrions-nous utiliser à la place?

Stéphane Chazelas
la source
3
Je pense que la plupart des arguments contre l'utilisation whichsupposent un contexte de shell interactif. Cette question est étiquetée / portabilité. J'interprète donc la question dans ce contexte comme "quoi utiliser au lieu de whichtrouver le premier exécutable d'un nom donné dans le $PATH". La plupart des réponses et des raisons s'opposent whichau traitement des alias, des fonctions intégrées et des fonctions, qui dans la plupart des scripts shell portables du monde réel n'ont qu'un intérêt théorique. Les alias définis localement ne sont pas hérités lors de l'exécution d'un script shell (sauf si vous le sourcez .).
MattBianco
5
@MattBianco, oui, csh(et whichreste un cshscript sur la plupart des Unices commerciaux) lit ~/.cshrcquand il n'est pas interactif. C'est pourquoi vous remarquerez que les scripts csh commencent généralement par #! /bin/csh -f. whichne le fait pas parce qu'il vise à vous donner les alias, car il est conçu comme un outil pour les utilisateurs (interactifs) de csh. POSIX shell utilisateurs ont command -v.
Stéphane Chazelas
@rudimeier, alors la réponse serait toujours sauf si votre shell est (t)csh(ou ne vous dérange pas si elle ne vous donne pas le résultat correct), utilisez typeou command -vplutôt . Voir les réponses pour pourquoi .
Stéphane Chazelas
1
@rudimeier, ( stat $(which ls)est erroné pour plusieurs raisons (manquant --, guillemets manquants), et pas seulement pour l'utilisation de which). Vous utiliseriez stat -- "$(command -v ls)". Cela suppose qu’il lss’agit bien d’une commande trouvée sur le système de fichiers (et non d’une fonction intégrée de votre shell ou de la fonction d’un alias). whichpourrait vous donner le mauvais chemin (pas le chemin que votre shell exécuterait si vous êtes entré ls) ou vous donner un alias tel que défini dans la configuration de certains autres shell ...
Stéphane Chazelas
1
@rudimeier, encore une fois, il existe un certain nombre de conditions dans lesquelles de nombreuses whichimplémentations ne vous donneraient même pas ce lsqui pourrait être trouvé par une recherche de $PATH(indépendamment de ce qui lspeut être appelé dans votre shell). sh -c 'command -v ls', ou zsh -c 'rpm -q --whatprovides =ls'sont plus susceptibles de vous donner la bonne réponse. Le point ici est que whichc'est un héritage brisé de csh.
Stéphane Chazelas

Réponses:

367

Voici tout ce que vous n'auriez jamais pensé ne jamais vouloir savoir:

Sommaire

Pour obtenir le nom de fichier d’un exécutable dans un script shell de type Bourne (il existe quelques réserves; 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 whichcommande est un héritage brisé du C-Shell et il vaut mieux la laisser seule dans des coquilles à la Bourne.

Cas d'utilisation

Il y a une différence entre rechercher ces informations dans le cadre d'un script ou de manière interactive à l'invite du shell.

À l’invite du shell, le cas d’utilisation typique est le suivant: cette commande se comporte étrangement, est-ce que j’utilise la bonne? Qu'est-ce qui s'est passé exactement quand 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 l'invoquer.

Dans les scripts shell, cela a tendance à être très différent. Dans un script shell, il n'y a aucune raison pour laquelle vous voulez savoir où et quelle commande est 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 ainsi en extraire davantage d'informations (comme le chemin d'accès à un autre fichier par rapport à celui-ci ou lire des informations à partir du contenu du fichier exécutable situé sur ce chemin).

Vous voudrez peut-être connaître, de manière interactive, toutes les my-cmdcommandes disponibles sur le système, rarement dans les scripts.

La plupart des outils disponibles (comme c'est souvent le cas) ont été conçus pour être utilisés de manière interactive.

Histoire

Un peu d'histoire d'abord.

Les premiers shell Unix jusqu’à la fin des années 70 n’avaient aucune fonction ni pseudonyme. Seule la recherche traditionnelle des exécutables dans $PATH. cshalias introduits autour de 1978 (mais csha d' abord été publié en 2BSDen mai 1979), ainsi que le traitement d'un .cshrcpour les utilisateurs de personnaliser la coque (chaque coquille, comme cshlit .cshrcmême lorsqu'ils ne sont pas interactifs comme dans les scripts).

Bien que le shell Bourne ait é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 en SVR2), et de toute façon, il n’a jamais eu de rcfichier ( .profilec’est pour configurer votre environnement, pas le shell en tant que tel ).

csh est devenu beaucoup plus populaire que le shell Bourne car (même si sa syntaxe était bien pire que celle du shell Bourne), il ajoutait de nombreuses fonctionnalités plus pratiques et plus agréables pour une utilisation interactive.

En 3BSD(1980), un whichscript csh a été ajouté pour cshaider les utilisateurs à identifier un exécutable. C’est un script à peine différent que vous pouvez trouver comme whichsur de nombreux Unices commerciaux de nos jours (comme Solaris, HP / UX, AIX ou Tru64).

Ce script lit l'utilisateur ~/.cshrc(comme tous les cshscripts, à moins d'être appelé avec csh -f), et recherche le ou les noms de commande fournis dans la liste des alias et dans $path(le tableau sur lequel on se cshbase $PATH).

Voilà, whichc'est d'abord arrivé avec la coquille la plus populaire à l'époque (et elle l' cshétait toujours jusqu'au milieu des années 90), ce qui est la raison principale pour laquelle elle a été documentée dans des livres et est toujours largement utilisée.

Notez que, même pour un cshutilisateur, ce whichscript csh ne vous donne pas nécessairement les bonnes informations. Il obtient les alias définis dans ~/.cshrc, pas ceux que vous avez définis ultérieurement à l'invite ou par exemple en sourcecréant un autre cshfichier. (Bien que ce ne soit pas une bonne idée), ils PATHpourraient être redéfinis dans ~/.cshrc.

L'exécution de cette whichcommande à partir d'un shell Bourne continuerait à rechercher les alias définis dans votre ~/.cshrc, mais si vous n'en avez pas, car vous n'utiliseriez pas csh, vous obtiendrez probablement toujours la bonne réponse.

Une fonctionnalité similaire n'a été ajoutée au shell Bourne qu'en 1984 dans SVR2 avec la typecommande intégrée. Le fait qu'il soit intégré (par opposition à un script externe) signifie qu'il peut vous donner les bonnes informations (dans une certaine mesure) car il a accès aux éléments internes du shell.

La typecommande initiale whichprésentait un problème similaire à celui du script car elle ne renvoyait pas d'état de sortie en cas d'échec si la commande était introuvable. En outre, pour les exécutables, contrairement à whichcela, il génère quelque chose comme ls is /bin/lsau lieu de tout /bin/lsce qui le rend moins facile à utiliser dans les scripts.

Le shell Bourne de la version 8 de Unix (non publié à l’état sauvage) a typeété renommé en whatis. Et le shell Plan9 (l'ancien successeur d'Unix) rc(et ses dérivés comme akangaet es) ont whatisaussi.

Le shell Korn (un sous-ensemble sur lequel la définition de POSIX sh est basée), développé au milieu des années 80 mais pas encore largement disponible avant 1988, a ajouté de nombreuses cshfonctionnalités (éditeur de ligne, alias ...) au-dessus du shell Bourne. . Il a ajouté sa propre fonction whenceintégrée (en plus de type) qui a pris plusieurs options ( -vpour fournir une typesortie verbose semblable à celle-ci, et -pne rechercher que les exécutables (pas les alias / fonctions ...)).

Par coïncidence avec la tourmente liée aux problèmes de droits d'auteur entre AT & T et Berkeley, quelques implémentations de shell de logiciels libres sont apparues à la fin des années 80 et au début des années 90. L’ensemble du shell Almquist (cendres, remplaçant du shell Bourne dans les BSD), l’implémentation dans le domaine public de ksh (pdksh) bash(parrainé par la FSF), zshest sorti entre 1989 et 1991.

Ash, bien que censé remplacer le shell Bourne, n’a été typeintégré que bien plus tard (dans NetBSD 1.3 et FreeBSD 2.3), bien qu’il en soit ainsi hash -v. OSF / 1 /bin/shavait une fonction typeintégrée qui renvoyait toujours 0 à OSF / 1 v3.x. bashn'a pas ajouté whencemais ajouté une -poption pour typeimprimer le chemin (ce type -pserait comme whence -p) et -apour signaler toutes les commandes correspondantes. tcshfait whichbuiltin et a ajouté une wherecommande agissant comme bash« s type -a. zsha tous.

Le fishshell (2005) a une typecommande implémentée en tant que fonction.

Le whichscript csh a quant à lui été supprimé de NetBSD (car il était intégré à tcsh et peu utilisé dans d’autres shells), et la fonctionnalité ajoutée à whereis(lorsqu’elle est appelée which, whereisse comporte comme whichsi elle ne cherchait que les exécutables $PATH). Dans OpenBSD et FreeBSD, whicha également été remplacé par un script écrit en C qui ne recherche que les commandes $PATH.

Implémentations

Il existe des dizaines d'implémentations d'une whichcommande sur divers Unices avec une syntaxe et un comportement différents.

Sous Linux (à côté de ceux intégrés dans tcshet zsh), nous trouvons plusieurs implémentations. Sur les systèmes Debian récents, par exemple, il s’agit d’un simple script shell POSIX qui cherche des commandes dans $PATH.

busyboxa également une whichcommande.

Il y en a une GNU whichqui est probablement la plus extravagante. Il essaie d’étendre ce que le whichscript csh a fait à d’autres shells: vous pouvez lui indiquer quels sont vos alias et fonctions afin de vous donner une meilleure réponse (et je pense que certaines distributions Linux ont défini des alias mondiaux autour de cela pour bashle faire) .

zsha quelques opérateurs à développer dans le chemin des exécutables: l' opérateur de = développement de nom de fichier et le :cmodificateur de développement d'historique (appliqué ici à la dilatation de paramètre ):

$ print -r -- =ls
/bin/ls
$ cmd=ls; print -r -- $cmd:c
/bin/ls

zsh, dans le zsh/parametersmodule fait également la table de hachage de commande comme le commandstableau associatif:

$ print -r -- $commands[ls]
/bin/ls

L' whatisutilitaire (à l'exception de celui de Unix V8 Bourne shell ou de Plan 9 rc/ es) n'est pas vraiment lié car il est destiné uniquement à la documentation (insère la base de données whatis, c'est-à-dire le synopsis de la page de manuel).

whereisa également été ajouté en 3BSDmême temps que whichs'il était écrit C, cshmais qu'il est utilisé pour rechercher en même temps l'exécutable, la page de manuel et le source, mais non sur l'environnement actuel. Encore une fois, cela répond à un besoin différent.

Maintenant, sur le plan standard, POSIX spécifie les commandes command -vet -V(qui étaient facultatives jusqu’à POSIX.2008). UNIX spécifie la typecommande (pas d'option). C'est tout ( where, which, whencene sont pas spécifiées dans une norme)

Jusqu'à une certaine version, typeet command -vétaient facultatifs dans la spécification Linux Standard Base, ce qui explique pourquoi, par exemple, certaines anciennes versions de posh(bien qu'elles soient basées sur les pdkshdeux) n'en possédaient pas non plus. command -va également été ajouté à certaines implémentations du shell Bourne (comme sur Solaris).

Statut aujourd'hui

Le statut actuel est le même typeet il command -vest omniprésent dans tous les shells de type Bourne (bien que, comme l'a noté @jarno, notez l'avertissement / bogue en dehors du bashmode POSIX ou de certains descendants du shell Almquist ci-dessous dans les commentaires). tcshest le seul shell où vous voudriez utiliser which(car il n’y en a pas typeet whichest intégré).

Dans les shells autres que tcshet zsh, whichpeut vous indiquer le chemin de l’exécutable donné tant qu’il n’ya ni alias ni fonction du même nom dans aucun de nos fichiers ~/.cshrc, ~/.bashrcni aucun fichier de démarrage du shell, et que vous ne définissez pas $PATHdans votre ~/.cshrc. Si vous avez défini un alias ou une fonction, il peut vous en parler ou ne pas vous indiquer la mauvaise chose.

Si vous voulez connaître toutes les commandes portant un nom donné, rien n’est portable. Vous utiliseriez wheredans tcshou zsh, type -adans bashou zsh, whence -aen ksh93 et dans d' autres coquilles, vous pouvez utiliser typeen combinaison avec ce which -aqui peut fonctionner.

Recommandations

Obtenir le chemin d'accès à un exécutable

Maintenant, pour obtenir le chemin d’un exécutable dans un script, quelques mises en garde:

ls=$(command -v ls)

serait le moyen standard de le faire.

Il y a cependant quelques problèmes:

  • Il n'est pas possible de connaître le chemin de l'exécutable sans l'exécuter. Tous les type, which, command -v... tous utilisent des technologies heuristiques pour trouver le chemin. Ils parcourent les $PATHcomposants en boucle et recherchent le premier fichier non répertoire pour lequel vous disposez d'une 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 $PATHjusqu'à ce que l' execveappel système ne revienne pas avec une erreur. . Par exemple, si $PATHcontient /foo:/baret que vous voulez exécuter ls, ils vont d'abord essayer de s'exécuter /foo/lsou si cela échoue /bar/ls. Maintenant l'exécution de/foo/lspeut échouer car vous n’avez pas le droit d’exécution, mais aussi pour de nombreuses autres raisons, comme l’exécutable n’est pas valide. command -v lssignalerait /foo/lssi vous avez une autorisation d'exécution pour /foo/ls, mais l'exécution lspeut en réalité s'exécuter /bar/lssi ce /foo/lsn'est pas un exécutable valide.
  • Si fooest une fonction ou un alias intégré, command -v fooretourne foo. Avec certains shells tels que ash, pdkshou zsh, il peut également renvoyer foosi $PATHinclut la chaîne vide et s'il existe un foofichier exécutable dans le répertoire en cours. Dans certaines circonstances, vous devrez peut-être en tenir compte. Gardez à l’esprit, par exemple, que la liste des commandes intégrées varie en fonction de l’implémentation du shell (par exemple, elle mountest parfois intégrée à busybox sh) et bashpeut , par exemple, obtenir des fonctions de l’environnement.
  • Si $PATHcontient des composants de chemin relatif (généralement .ou la chaîne vide qui font tous les deux référence au répertoire en cours mais peut être n'importe quoi), selon le shell, command -v cmdpeut ne pas générer un chemin absolu. Ainsi, le chemin que vous obtenez au moment où vous courez command -vne sera plus valable après votre passage cdailleurs.
  • Anecdotique: avec le shell ksh93, si /opt/ast/bin(bien que cette voie exacte peut varier en fonction des différents systèmes , je crois) est en vous $PATH, ksh93 mettra à la disposition quelques builtins supplémentaires ( chmod, cmp, cat...), mais command -v chmodretournera /opt/ast/bin/chmodmême si ce chemin n » t existent.

Déterminer si une commande existe

Pour savoir si une commande donnée existe de manière 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

Où on pourrait vouloir utiliser which

(t)csh

En cshet tcsh, tu n'as pas beaucoup de choix. En tcsh, ça va comme whichc'est construit. Dans csh, ce sera la whichcommande système , qui peut ne pas faire ce que vous voulez dans quelques cas.

trouver des commandes uniquement dans certains coquillages

Un cas où il peut être judicieux d'utiliser whichest si vous voulez connaître le chemin d'une commande, en ignorant shell potentiels builtins ou fonctions bash, csh(non tcsh), dashou des Bournescripts shell, c'est - coquilles qui ne sont pas whence -p(comme kshou zsh) , command -ev(comme yash), whatis -p( rc, akanga) ou un élément intégré which(comme tcshou zsh) sur des systèmes où whichest disponible et n'est pas le cshscript.

Si ces conditions sont remplies, alors:

echo=$(which echo)

vous donnera le chemin du premier echodans $PATH(sauf dans les cas d'angle), que echose trouve être également une commande du shell / alias / fonction ou non.

Dans d'autres coquilles, vous préféreriez:

  • zsh : echo==echoou echo=$commands[echo]ouecho=${${:-echo}:c}
  • ksh , zsh :echo=$(whence -p echo)
  • yash :echo=$(command -ev echo)
  • rc , akanga : echo=`whatis -p echo`(méfiez-vous des chemins avec des espaces)
  • poissons :set echo (type -fp echo)

Notez que si tout ce que vous voulez faire est d’ exécuter cette echocommande, vous n’avez pas à 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 whichde la commande intégrée :

set Echo = "`env which echo`"

quand vous avez besoin d'une commande externe

Vous pouvez également utiliser un autre cas lorsque vous whichavez réellement besoin d' une commande externe. POSIX exige que toutes les fonctions intégrées dans le shell (comme command) soient également disponibles en tant que commandes externes, mais malheureusement, ce n'est pas le cas commandsur de nombreux systèmes. Par exemple, il est rare de trouver une commandcommande sur des systèmes d'exploitation basés sur Linux alors que la plupart d'entre eux ont une whichcommande (bien que différentes avec des options et des comportements différents).

Les cas dans lesquels vous souhaiterez peut-être une commande externe seraient ceux où vous exécuteriez 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; system("command -v my-cmd")travaillez-y donc. Une exception à cela serait d' perloptimiser le shell s'il ne voit aucun caractère spécial du shell (autre que l'espace). Cela s'applique également à 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 qui :;précède force perlà y invoquer un shell. En utilisant which, vous n'auriez pas à utiliser cette astuce.

Stéphane Chazelas
la source
24
@ Joe, whichest un cshscript sur de nombreux Unices commerciaux. La raison en est historique, c'est pourquoi j'ai donné l'historique, afin que les gens comprennent d'où il vient, pourquoi les gens s'habituaient à l'utiliser et pourquoi, en fait, il n'y a aucune raison pour que vous l'utilisiez. Et oui, certaines personnes utilisent (t) csh. Tout le monde n'utilise pas encore Linux
Stéphane Chazelas
12
Après avoir lu ce post, j'ai trouvé beaucoup de contexte pour la réponse, mais pas la réponse elle-même. Où dans ce post dit-il réellement pourquoi ne pas utiliser which, par opposition à des choses que vous pourriez essayer d'utiliser whichpour faire, l'historique which, les implémentations de which, d'autres commandes pour effectuer des tâches connexes, ou des raisons d'utiliser réellement which? Pourquoi les autres commandes sont-elles meilleures ? Que font-ils différemment which? Comment évitent-ils ses pièges? Cette réponse passe en réalité plus de mots sur les problèmes avec les alternatives que les problèmes avec which.
user62251
1
Contrairement à ce que prétend la réponse, command -vne vérifie pas l'autorisation d'exécution, du moins si vous l'appelez par un argument de nom de fichier pur sans chemin. J'ai testé par tiret 0.5.8 et GNU bash 4.3.48.
jarno
2
@ StéphaneChazelas Si je crée un nouveau fichier avant touch /usr/bin/mytestfilede l'exécuter command -v mytestfile, il donnera le chemin (alors que ce which mytestfilen'est pas le cas).
jarno
2
@ Jarno, oh oui, vous avez raison. bashse contentera d’un fichier non-exécutable s’il ne trouve pas de fichier exécutable, c’est donc "OK" (même si en pratique on préférerait command -v/ typerenvoyer une erreur) car c’est la commande qu’il essaierait d’exécuter lors de l’exécution mytestfile, mais le dashcomportement est buggy, comme si il y avait un non-exécutable cmdavant un exécutable, command -vrenvoie le non-exécutable lors de l'exécution cmdexécuterait l'exécutable (le mauvais est également haché). FreeBSD sh(également basé sur ash) a le même bogue. zsh, yash, ksh, mksh, bash comme sh sont OK.
Stéphane Chazelas
47

Les raisons pour lesquelles on peut ne pas vouloir utiliser whichont déjà été expliquées, mais voici quelques exemples sur quelques systèmes où la whichdéfaillance a effectivement eu lieu.

Sur les obus Bourne-like, nous comparons le résultat de ( whichavec le résultat de type( typeétant un shell intégré, il est censé être la vérité sur le terrain, car il nous dit comment invoquer une commande).

Beaucoup de cas sont des cas de coin , mais gardez à l'esprit que which/ typesont souvent utilisés dans des cas de coin (pour trouver la réponse à un comportement inattendu du type: pourquoi est-ce que cette commande se comporte comme ça, quelle est la personne que j'appelle? ).

La plupart des systèmes, la plupart des coques de type Bourne: fonctions

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 en est que whichseuls les rapports concernant les exécutables, et parfois les alias (bien que pas toujours ceux de votre shell), pas les fonctions.

La page de manuel GNU a un exemple cassé (car ils ont oublié de citer $@) sur son utilisation pour signaler des fonctions, mais tout comme pour les alias, car elle n’implémente pas d’analyseur de syntaxe de shell, elle est facilement trompée:

$ 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;
}

La plupart des systèmes, la plupart des coques de type Bourne: Intégrés

Un autre cas est évidente builtins ou des mots - clés, comme whichétant une commande externe n'a aucun moyen de savoir qui builtins votre shell ont (et quelques coquilles comme zsh, bashou kshpeut charger dynamiquement builtins):

$ 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 à l' zshendroit où whichest intégré)

Solaris 10, AIX 7.1, HP / UX 11i, Tru64 5.1 et de nombreux autres:

$ 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

C'est parce que sur la plupart des systèmes Unix commerciaux, which(comme dans la mise en œuvre originale sur 3BSD) est un cshscript qui lit ~/.cshrc. Les alias qu'il signalera sont ceux définis ici, quels que soient les alias que vous avez définis et quel que soit le shell utilisé.

Sous 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 de Solaris et AIX ont résolu ce problème en enregistrant $pathavant de lire ~/.cshrcet 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, en tant que cshscript, vous ne pouvez pas vous attendre à ce qu'il fonctionne avec des arguments contenant des espaces ...)

CentOS 6.4, bash

$ 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, un alias défini à l’échelle du système enveloppe la whichcommande GNU .

La sortie est fausse parce que whichlit la sortie de bash« s aliasmais ne sait pas comment l'analyser correctement et utilise heuristiques (un alias par ligne, recherche la première commande trouvée après |, ;, &...)

La pire chose sur CentOS est que sa commande intégrée soit zshparfaitement fine, whichmais CentOS a réussi à la rompre en la remplaçant par un alias non fonctionnel de GNU which.

Debian 7.0, ksh93:

(bien que cela s'applique à la plupart des systèmes avec beaucoup de coques)

$ unset PATH
$ which which
/usr/local/bin/which
$ type which
which is a tracked alias for /bin/which

Sur Debian, /bin/whichc'est un /bin/shscript. Dans mon cas, shêtre dashmais c'est pareil quand c'est bash.

Un non défini PATHne consiste pas à désactiver la PATHrecherche, mais à utiliser le PATH par défaut du système qui, malheureusement, sur Debian, personne n’accepte ( dashet basha /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin, zsha /bin:/usr/bin:/usr/ucb:/usr/local/bin, ksh93a /bin:/usr/bin, mksha /usr/bin:/bin( $(getconf PATH)), execvp()(comme dans env) a :/bin:/usr/bin(oui, regarde en premier dans le répertoire courant! )).

C’est pourquoi whichon se trompe ci-dessus car il utilise dashla valeur par défaut PATHqui est différente de celle ksh93de

Ce n'est pas mieux avec GNU whichqui rapporte:

which: no which in ((null))

(il est intéressant de noter qu’il existe effectivement /usr/local/bin/whichsur mon système un akangascript qui est venu avec akanga(un rcdérivé du shell où la valeur par défaut PATHest /usr/ucb:/usr/bin:/bin:.))

bash, n’importe quel système:

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

Aussi après avoir appelé hashmanuellement:

$ 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)

Maintenant, un cas où whichet parfois typeéchouer:

$ 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 coquilles:

$ foo
bash: ./b/foo: /: bad interpreter: Permission denied

Avec les autres:

$ foo
a/foo

Ni peut whichni typesavoir à l'avance qui b/foone peut pas être exécuté. Quelques coquilles comme bash, kshou yash, lors de l' appel foova essayer en effet d'exécuter b/fooet signaler une erreur, tandis que d' autres (comme zsh, ash, csh, Bourne, tcsh) se déroulera a/foosur l'échec de l' execve()appel système sur b/foo.

Stéphane Chazelas
la source
mkshutilise en réalité quelque chose de différent pour la valeur par défaut $PATH: premièrement, la constante de compilation du système d'exploitation _PATH_DEFPATHest utilisée (le plus souvent sur les BSD), ensuite, confstr(_CS_PATH, …)est utilisée (POSIX), et si les deux n'existent pas ou échouent, elle /bin:/usr/bin:/sbin:/usr/sbinest utilisée.
mirabilos
1
Dans votre 1er exemple, même si lsest une fonction qu’elle utilise lsdepuis PATH. Et whichest bien de vous dire lequel est utilisé /usr/bin/ls ou /usr/local/bin/ls. Je ne vois pas "Pourquoi ne pas utiliser lequel" ....
rudimeier
@rudimeier, Cela which lsme donnera /bin/lsindépendamment du fait que la lsfonction appelle /bin/lsou /opt/gnu/bin/lsou dirou rien du tout. IOW, which(celui dont les implémentations, IMMV) donne quelque chose de hors de propos
Stéphane Chazelas
1
@ StéphaneChazelas. Non non Non. Je sais déjà que mon lsest une fonction. Je sais que ma lsfonction appelle lsde PATH. Maintenant, whichme dit où se trouve le fichier. Vous ne voyez qu'un seul cas d'utilisation: "Que ferait mon shell avec cette commande". Pour ce cas d'utilisation whichest faux, correct. Mais il existe d'autres cas d'utilisation où (GNU) whichest exactement la bonne chose.
Rudimeier
@rudimeter, dépend de l' whichimplémentation. Certains vous diront que c'est un alias (si vous avez un alias configuré ou s'il y en a un ~/.cshrcdans votre maison qui en possède un), certains vous donneront un chemin, mais le mauvais dans certaines conditions. sh -c 'command -v ls'Bien que pas parfait, vous aurez toujours plus de chances de vous donner la bonne réponse à cette exigence différente (et également standard).
Stéphane Chazelas
21

Une chose qui (d'après mon rapide survol) il semble que Stéphane ne l'ait pas mentionné, c'est qu'elle whichn'a aucune idée de la table de hachage de votre shell. Cela a pour effet que le résultat obtenu risque de ne pas être représentatif de ce qui est réellement exécuté, ce qui le rend inefficace pour le débogage.

Chris Down
la source
6

Dans l’esprit UNIX: Faites que chaque programme fasse bien une chose.

Si le but est de répondre: quel exécutable existe avec ce nom?

Le programme exécutable fourni avec les systèmes Debian est une bonne réponse. Le qui fourni avec csh inclus des alias, c'est une source de problèmes. Les objectifs fournis par certains shells en tant que composants internes ont un objectif différent. Utilisez cet exécutable ou utilisez le script fourni à la fin de cette réponse.

Si ce script est utilisé, sa réponse est propre, simple et utile.

Cet objectif correspond à la première phrase de votre question:

Lorsque vous recherchez le chemin vers un exécutable……

Si vous avez un système qui ne contient pas d'exécutable appelé (la plupart des systèmes linux en ont un), vous pouvez en créer un ~/bin/whichauparavant /bin/dans PATH afin que les exécutables personnels remplacent ceux du système, comme celui qui se trouve au bas de cet article:

Ce exécutable liste (par défaut) tous les executables trouvés dans le PATH. Si seul le premier est requis, l'option -fest disponible.


À ce stade, nous tombons sur un objectif différent:

ce que le shell exécutera (après l'analyse)

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 bonne réponse à une question assez difficile à répondre. Les coquillages ont des points de vue divergents, des cas en coin et (au minimum) des interprétations différentes. Ajoutant à cela:

il y a une pléthore d'utilitaires différents (qui, type, commande, d'où, où, où est, quoi, hash, etc.).

Et bien sûr, toutes les tentatives correspondent à cet objectif.


Éviter lequel?

Nous entendons souvent ce qui devrait être évité.

Je me demande: pourquoi cela devrait-il être dit si cela whichfonctionne (au moins dans debian)?

Dans l’esprit UNIX: Faites que chaque programme fasse bien une chose.

Le programme externewhich fait une chose: recherchez le premier exécutable sur le chemin qui porte le même nom que le nom de la commande . Et le fait raisonnablement bien.

Je ne connais aucun autre programme ou utilitaire qui réponde à cette question de 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 rapportera également des noms intégrés et des alias. Pas la même réponse.

Bien sûr, whichest limité, il ne répond pas à toutes les questions, aucun outil ne peut le faire (enfin, pas encore ...). Mais il est utile lorsqu’il est utilisé pour répondre à la question à laquelle il a été conçu (celui ci-dessus). Un peu comme edétait limité et puis sedest apparu (ou vi/ vim). Ou comme si awkc'était limité et que Perl est apparu et étendu. Pourtant, ed, sedet / ou awkont des cas d'utilisation spécifiques où vimou perlsont pas les meilleurs outils.

Pourquoi?

Probablement parce que les whichréponses ne représentent qu'une partie de la question qu'un utilisateur du shell pourrait demander:

Qu'est-ce qui est en cours d'exécution lorsque je tape un nom de commande?


Externe qui

Ce qui devrait être disponible (dans de nombreux systèmes) en tant qu'exécutable externe.
Le seul moyen sûr d'appeler cet outil externe est d'utiliser env pour sortir du shell, puis d'appeler which(ce qui fonctionne dans tous les shells):

 $ env which which
 /usr/bin/which

Ou utilisez le chemin complet vers which(qui peut varier selon les systèmes):

 /usr/bin/which which 

Pourquoi est-ce hacknécessaire? Parce que certains coquillages (spécialement zsh) cachent which:

 $ zsh -c 'which which'
 which: shell built-in command

Être un outil externe (comme env) explique parfaitement pourquoi il ne rapportera pas d'informations internes au shell. Comme des alias, des fonctions, des éléments intégrés, des éléments spéciaux, des 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 qu'il lln'est pas lié à un programme exécutable, ou du moins qu'il n'y a pas de fichier exécutable nommé lldans le chemin PATH. L'utilisation de lldevrait appeler autre chose, dans ce cas, un alias:

 $ type ll
 ll is aliased to `ls -l'

type et command

Les commandes typeet command -vsont demandées par POSIX. On devrait s’attendre à ce qu’ils travaillent dans la plupart des coquillages, et ils le font, sauf dans csh, tcsh, fish et rc.

Les deux commandes pourraient être utilisées pour fournir un autre point de vue de la commande à exécuter.

whence, where, whereis, whatis,hash

Ensuite, il y a whence, where, whereis, whatis, hash, et quelques autres. Toutes les réponses différentes à des questions similaires. Tous travaillent de différentes manières dans différents coquillages. Probablement, whenceest le plus commun 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 d' whichabord savoir s'il existe un fichier exécutable par le nom du commandName , puis typeet commandpuis, si le commandName n'a pas encore été trouvée: whence, where, whereis, whatis, hashdans cet ordre.


Script shell pour fournir un whichexécutable.

#! /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"
Isaac
la source
0

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 de vos progiciels installés, car c'est de là que ça whichvient!

SLES 11.4 x86-64

dans tcsh version 6.18.01:

> which which

which: shell built-in command.

dans les versions 3.2-147 de bash:

> 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.

whichfait partie d' util-linux, un paquet standard distribué par l'organisation du noyau Linux à utiliser avec le 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-linuxest la version 2.19. Les notes de version se retrouvent facilement dans la v2.13 du 28 août 2007. Pas sûr du but ou de l’objectif de ceci, il n’a certainement pas été répondu 333 fois plus longtemps.

Ron
la source
2
Notez que la question ne fait aucune mention de l’Unix auquel elle fait référence. Linux n'est qu'un exemple parmi d'autres.
Kusalananda
2
En tant que votre which -vmontre, il s’agit de GNU (l’extravagant mentionné dans l’autre réponse et n’est en aucun cas spécifique à Linux), pas util-linux pour lequel AFAIK n’a jamais inclus d’ whichutilitaire. util-linux 2.19 est à partir de 2011, GNU qui est à 2,19 est de 2008.
Stéphane Chazelas