web-dev-qa-db-fra.com

Pourquoi ne pas utiliser "qui"? Que faut-il utiliser alors?

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?

357
Stéphane Chazelas

Voici tout ce que vous n'auriez jamais pensé que vous ne voudriez jamais savoir à ce sujet:

Sommaire

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.

Cas d'utilisation

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.

Histoire

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.

Implémentations

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

Statut aujourd'hui

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.

Recommandations

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

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:

  • 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 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.
  • si 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.
  • si $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.
  • Anecdotique: avec le shell ksh93, si /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.

Déterminer si une commande existe

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

Où l'on peut vouloir utiliser 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.

rechercher des commandes uniquement dans certains shells

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:

  • zsh : echo==echo ou echo=$commands[echo] ou echo=${${:-echo}:c}
  • ksh , zsh : echo=$(whence -p echo)
  • yash : echo=$(command -ev echo)
  • rc , akanga : echo=`whatis -p echo` (attention aux chemins avec des espaces)
  • poisson : 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`"

lorsque vous avez besoin d'une commande externe

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.

390
Stéphane Chazelas

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

La plupart des systèmes, la plupart des shells 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 é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;
}

La plupart des systèmes, la plupart des obus de type Bourne: intégrés

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 à zshwhich est intégré)

Solaris 10, AIX 7.1, HP/UX 11i, Tru64 5.1 et bien d'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

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

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

Debian 7.0, ksh93:

(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:.))

bash, tout 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

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)

Maintenant, un cas où 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.

49
Stéphane Chazelas

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.

20
Chris Down

Dans l'esprit UNIX: Faites bien à chaque programme une chose.

Quelle est la vraie question?

Si vous devez répondre:

1. Quel exécutable existe avec certains nom?

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:

lequel est utilisé?

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.


2. Essayer de répondre: ce que fera le shell (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 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.


Évitez qui?

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?



Externe qui

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:

Interne qui

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:

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

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

  3. Si le nom de la commande ne contient aucune barre oblique, le shell tente de le localiser.

    1. S'il existe un alias de ce nom, cet alias est substitué.
    2. L'alias substitué est développé. Le nom devient le premier mot résultant.
    3. S'il existe une fonction Shell portant le nom at, cette fonction est appelée.
    4. Si le nom ne correspond pas à une fonction, le shell le recherche dans la liste des commandes internes du shell. Si une correspondance est trouvée, cette fonction intégrée est invoquée.
    5. Si le nom n'est ni une fonction Shell ni une fonction intégrée, certains shells recherchent dans une table de hachage qui se souviennent des chemins d'accès complets des fichiers exécutables trouvés précédemment.
  4. S'il n'est pas encore trouvé, une recherche complète des répertoires dans PATH est effectuée.

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

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

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


Script shell pour fournir un exécutable 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"
6
Isaac

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.

0
ron