¿Por qué no usar "which"? ¿Qué usar entonces?

329

Al buscar la ruta a un archivo ejecutable o comprobar lo que sucedería si se introduce un nombre de comando en un shell de Unix, hay una plétora de diferentes servicios públicos ( which, type, command, whence, where, whereis, whatis, hash, etc).

A menudo escuchamos que se whichdebe evitar. ¿Por qué? ¿Qué deberíamos usar en su lugar?

Stéphane Chazelas
fuente
3
Creo que la mayoría de los argumentos en contra del uso whichsuponen un contexto de shell interactivo. Esta pregunta está etiquetada / portabilidad. Así que interpreto la pregunta en este contexto como "qué usar en lugar de whichencontrar el primer ejecutable de un nombre dado en el $PATH". La mayoría de las respuestas y razones en contra de whichtratar con alias, funciones y funciones, que en la mayoría de los scripts de shell portátiles del mundo real son solo de interés académico. Los alias definidos localmente no se heredan cuando se ejecuta un script de shell (a menos que lo obtenga .).
MattBianco
55
@MattBianco, sí, csh(y whichsigue siendo un cshscript en la mayoría de los Unices comerciales) se lee ~/.cshrccuando no es interactivo. Es por eso que notarás que los scripts csh generalmente comienzan con #! /bin/csh -f. whichno porque tiene como objetivo darle los alias, porque está destinado a ser una herramienta para usuarios (interactivos) de csh. POSIX shells usuarios tienen command -v.
Stéphane Chazelas
@rudimeier, entonces la respuesta sería siempre, a menos que su shell sea (t)csh(o no le importe si no le da el resultado correcto), use typeo en su command -vlugar . Vea las respuestas de por qué .
Stéphane Chazelas
1
@rudimeier, ( stat $(which ls)está mal por varias razones (faltan --, faltan comillas), no solo el uso de which). Tendrá que utilizar stat -- "$(command -v ls)". Eso supone lsque, de hecho, es un comando que se encuentra en el sistema de archivos (no un componente de su shell o función de alias). whichpodría darle la ruta incorrecta (no la ruta que su shell ejecutaría si ingresara ls) o darle un alias como se define en la configuración de algunos otros shells ...
Stéphane Chazelas
1
@rudimeier, de nuevo, hay una serie de condiciones bajo las cuales muchas whichimplementaciones no le darían ni siquiera las lsque se encontrarían en una búsqueda $PATH(independientemente de lo que lspueda invocar en su shell). sh -c 'command -v ls', o zsh -c 'rpm -q --whatprovides =ls'es más probable que le den la respuesta correcta. El punto aquí es que whiches una herencia rota de csh.
Stéphane Chazelas

Respuestas:

367

Aquí está todo lo que nunca pensó que nunca querría saber al respecto:

Resumen

Para obtener el nombre de ruta de un ejecutable en un script de shell similar a Bourne (hay algunas advertencias; ver más abajo):

ls=$(command -v ls)

Para averiguar si existe un comando dado:

if command -v given-command > /dev/null 2>&1; then
  echo given-command is available
else
  echo given-command is not available
fi

En el indicador de un shell interactivo similar a Bourne:

type ls

El whichcomando es una herencia rota del C-Shell y es mejor dejarlo solo en los proyectiles tipo Bourne.

Casos de uso

Hay una distinción entre buscar esa información como parte de un script o interactivamente en el indicador de comandos de shell.

En el indicador de comandos de shell, el caso de uso típico es: este comando se comporta de manera extraña, ¿estoy usando el correcto? ¿Qué sucedió exactamente cuando escribí mycmd? ¿Puedo mirar más allá de lo que es?

En ese caso, desea saber qué hace su shell cuando invoca el comando sin invocarlo.

En los scripts de shell, tiende a ser bastante diferente. En un script de shell no hay ninguna razón por la que quieras saber dónde o qué es un comando si todo lo que quieres hacer es ejecutarlo. En general, lo que quiere saber es la ruta del archivo ejecutable, para que pueda obtener más información (como la ruta a otro archivo relativo a eso o leer información del contenido del archivo ejecutable en esa ruta).

Interactivamente, es posible que desee conocer todos los my-cmdcomandos disponibles en el sistema, en scripts, rara vez.

La mayoría de las herramientas disponibles (como suele ser el caso) han sido diseñadas para ser utilizadas de forma interactiva.

Historia

Un poco de historia primero.

Los primeros depósitos de Unix hasta finales de los 70 no tenían funciones ni alias. Solo la búsqueda tradicional de ejecutables en $PATH. cshintrodujo alias alrededor de 1978 (aunque cshse lanzó por primera vez en 2BSDmayo de 1979), y también el procesamiento de una .cshrcpara que los usuarios personalicen el shell (cada shell, como se cshlee .cshrcincluso cuando no es interactivo como en los scripts).

Si bien el shell Bourne se lanzó por primera vez en Unix V7 a principios de 1979, el soporte de funciones solo se agregó mucho más tarde (1984 en SVR2) y, de todos modos, nunca tuvo algún rcarchivo ( .profilees para configurar su entorno, no el shell per se ).

csh se hizo mucho más popular que el shell Bourne ya que (aunque tenía una sintaxis mucho peor que el shell Bourne) estaba agregando muchas más características convenientes y agradables para uso interactivo.

En 3BSD(1980), se agregó un whichscript csh para que los cshusuarios ayudaran a identificar un ejecutable, y es un script apenas diferente que se puede encontrar whichen muchos Unices comerciales hoy en día (como Solaris, HP / UX, AIX o Tru64).

Ese script lee al usuario ~/.cshrc(como lo hacen todos los cshscripts a menos que se invoque con csh -f), y busca los nombres de comando proporcionados en la lista de alias y en $path(la matriz que se cshbasa en $PATH).

Aquí tienes, whichllegó primero para el shell más popular en ese momento (y cshaún era popular hasta mediados de los 90), que es la razón principal por la que se documentó en los libros y todavía se usa ampliamente.

Tenga en cuenta que, incluso para un cshusuario, ese whichscript csh no necesariamente le brinda la información correcta. Obtiene los alias definidos ~/.cshrc, no los que haya definido más adelante en el indicador o, por ejemplo, al sourceingresar otro csharchivo y (aunque eso no sería una buena idea), PATHpodría redefinirse ~/.cshrc.

Ejecutar ese whichcomando desde un shell Bourne aún buscaría los alias definidos en su ~/.cshrc, pero si no tiene uno porque no lo usa csh, probablemente todavía obtendrá la respuesta correcta.

No se agregó una funcionalidad similar al shell Bourne hasta 1984 en SVR2 con el typecomando incorporado. El hecho de que esté integrado (a diferencia de un script externo) significa que puede brindarle la información correcta (hasta cierto punto) ya que tiene acceso a las partes internas del shell.

El typecomando inicial sufrió un problema similar al whichscript en que no devolvió un estado de salida de falla si no se encontró el comando. Además, para los ejecutables, al contrario which, genera algo así en ls is /bin/lslugar de solo lo /bin/lsque lo hace menos fácil de usar en los scripts.

El shell Bourne de Unix Versión 8 (no lanzado en la naturaleza) tenía su typenombre incorporado whatis. Y el shell Plan9 (el sucesor de Unix) rc(y sus derivados like akangay es) también lo tienen whatis.

El shell Korn (un subconjunto en el que se basa la definición POSIX sh), desarrollado a mediados de los años 80 pero no ampliamente disponible antes de 1988, agregó muchas de las cshcaracterísticas (editor de línea, alias ...) en la parte superior del shell Bourne . Agregó su propio whenceincorporado (además de type) que tomó varias opciones ( -vpara proporcionar una typesalida detallada similar, y -pbuscar solo ejecutables (no alias / funciones ...)).

Coincidiendo con la agitación con respecto a los problemas de derechos de autor entre AT&T y Berkeley, a finales de los 80 y principios de los 90 surgieron algunas implementaciones de software libre . Todo el shell Almquist (ash, que será el reemplazo del shell Bourne en BSD), la implementación de dominio público de ksh (pdksh), bash(patrocinado por la FSF), zshsalió entre 1989 y 1991.

Ash, aunque pretendía ser un reemplazo para el shell Bourne, no tuvo un typebuiltin incorporado hasta mucho más tarde (en NetBSD 1.3 y FreeBSD 2.3), aunque lo tenía hash -v. OSF / 1 /bin/shtenía un typeincorporado que siempre devolvía 0 hasta OSF / 1 v3.x. bashno agregó un whencepero agregó una -popción para typeimprimir la ruta ( type -psería como whence -p) e -ainformar todos los comandos coincidentes. tcshhecho whichincorporado y ha añadido un wherecomando de actuar como bash's type -a. zshlos tiene todos.

El fishshell (2005) tiene un typecomando implementado como una función.

Mientras whichtanto, el script csh se eliminó de NetBSD (ya que estaba incorporado en tcsh y no era de mucha utilidad en otros shells), y la funcionalidad añadida a whereis(cuando se invoca como which, se whereiscomporta como, whichexcepto que solo busca ejecutables $PATH). En OpenBSD y FreeBSD, whichtambién se cambió a uno escrito en C que solo busca comandos $PATH.

Implementaciones

Hay docenas de implementaciones de un whichcomando en varios Unices con diferente sintaxis y comportamiento.

En Linux (además de los incorporados en tcshy zsh) encontramos varias implementaciones. En los sistemas Debian recientes, por ejemplo, es un simple script de shell POSIX que busca comandos en $PATH.

busyboxTambién tiene un whichcomando.

Hay una GNU whichque probablemente sea la más extravagante. Intenta extender lo que el whichscript csh hizo a otros shells: puedes decirle cuáles son tus alias y funciones para que pueda darte una mejor respuesta (y creo que algunas distribuciones de Linux establecen algunos alias globales para bashhacerlo). .

zshtiene un par de operadores para expandir a la ruta de los ejecutables: el operador de = expansión de nombre de archivo y el :cmodificador de expansión de historial (aquí aplicado a la expansión de parámetros ):

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

zsh, en el zsh/parametersmódulo también crea la tabla hash de comandos como la commandsmatriz asociativa:

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

La whatisutilidad (excepto la de Unix V8 Bourne shell o Plan 9 rc/ es) no está realmente relacionada, ya que es solo para documentación (greps, la base de datos whatis, esa es la sinopsis de la página de manual).

whereistambién se agregó 3BSDal mismo tiempo como whichsi estuviera escrito C, no cshy se usa para buscar al mismo tiempo, el ejecutable, la página de manual y la fuente, pero no se basa en el entorno actual. Entonces, de nuevo, eso responde a una necesidad diferente.

Ahora, en el frente estándar, POSIX especifica los comandos command -vy -V(que solían ser opcionales hasta POSIX.2008). UNIX especifica el typecomando (sin opción). Eso es todo ( where, which, whenceno se especifican en cualquier punto de vista)

Hasta alguna versión, typey command -veran opcionales en la especificación de Linux Standard Base que explica por qué, por ejemplo, algunas versiones antiguas de posh(aunque basadas en las pdkshque tenían ambas) tampoco tenían. command -vtambién se agregó a algunas implementaciones de shell Bourne (como en Solaris).

Estado hoy

El estado actual es ese typey command -vestán en todas partes en todos los shells tipo Bourne (aunque, como lo señaló @jarno, tenga en cuenta la advertencia / error bashcuando no está en modo POSIX o algunos descendientes del shell Almquist a continuación en los comentarios). tcshes el único shell donde querrías usar which(ya que no hay typeallí y whichestá integrado).

En los shells que no sean tcshy zsh, es whichposible que le indiquen la ruta del ejecutable dado siempre que no haya un alias o función con ese mismo nombre en ninguno de nuestros archivos de inicio de shell ~/.cshrc, ~/.bashrco que no defina $PATHen su ~/.cshrc. Si tiene un alias o una función definidos, puede o no informarle al respecto, o decirle algo incorrecto.

Si desea conocer todos los comandos por un nombre de pila, no hay nada portátil. Lo usaría whereen tcsho zsh, type -aen basho zsh, whence -aen ksh93 y en otros shells, puede usarlo typeen combinación con lo which -aque puede funcionar.

Recomendaciones

Obtener el nombre de ruta a un ejecutable

Ahora, para obtener el nombre de ruta de un ejecutable en un script, hay algunas advertencias:

ls=$(command -v ls)

sería la forma estándar de hacerlo.

Sin embargo, hay algunos problemas:

  • No es posible conocer la ruta del ejecutable sin ejecutarlo. Todos los type, which, command -v... todos utilizan la heurística para averiguar la ruta. Recorren los $PATHcomponentes y encuentran el primer archivo que no es de directorio para el que tiene permiso de ejecución. Sin embargo, dependiendo del shell, cuando se trata de ejecutar el comando, muchos de ellos (Bourne, AT&T ksh, zsh, ash ...) simplemente los ejecutarán en el orden de $PATHhasta que la execvellamada al sistema no regrese con un error . Por ejemplo, si $PATHcontiene /foo:/bary desea ejecutar ls, primero intentarán ejecutar /foo/lso si eso falla /bar/ls. Ahora ejecución de/foo/lspuede fallar porque no tiene permiso de ejecución, pero también por muchas otras razones, como que no es un ejecutable válido. command -v lsinformaría /foo/lssi tiene permiso de ejecución /foo/ls, pero la ejecución lspodría ejecutarse /bar/lssi /foo/lsno es un ejecutable válido.
  • si fooes un incorporado o una función o alias, command -v foodevuelve foo. Con algunos shells como ash, pdksho zsh, también puede regresar foosi $PATHincluye la cadena vacía y hay un fooarchivo ejecutable en el directorio actual. Hay algunas circunstancias en las que es posible que deba tener eso en cuenta. Tenga en cuenta, por ejemplo, que la lista de funciones integradas varía con la implementación del shell (por ejemplo, a mountveces está integrada para busybox sh) y, por ejemplo, bashpuede obtener funciones del entorno.
  • si $PATHcontiene componentes de ruta relativos (normalmente .o la cadena vacía que se refieren al directorio actual pero podrían ser cualquier cosa), dependiendo del shell, command -v cmdpodría no generar una ruta absoluta. Por lo tanto, la ruta que obtienes en el momento en que corres command -vya no será válida después de que estés cden otro lugar.
  • Anecdótica: con la cáscara ksh93, si /opt/ast/bin(a pesar de que la ruta exacta puede variar en diferentes sistemas, creo que) está en ti $PATH, ksh93 hará unas pocas órdenes internas adicionales disponibles ( chmod, cmp, cat...), pero command -v chmodregresará /opt/ast/bin/chmodincluso si ese camino doesn' t existe.

Determinar si existe un comando

Para averiguar si un comando dado existe de manera estándar, puede hacer lo siguiente:

if command -v given-command > /dev/null 2>&1; then
  echo given-command is available
else
  echo given-command is not available
fi

Donde uno podría querer usar which

(t)csh

En cshy tcsh, no tienes mucha opción. En tcsh, eso está bien como whichestá incorporado. En csh, ese será el whichcomando del sistema , que puede no hacer lo que desea en algunos casos.

buscar comandos solo en algunos shells

Un caso en el que podría tener sentido usarlo whiches si desea conocer la ruta de un comando, ignorando las funciones o funciones potenciales de shell en bash, csh(no tcsh) dasho Bournescripts de shell, es decir, shells que no tienen whence -p(like ksho zsh) , command -ev(like yash), whatis -p( rc, akanga) o un builtin which(like tcsho zsh) en sistemas donde whichestá disponible y no es el cshscript.

Si se cumplen esas condiciones, entonces:

echo=$(which echo)

le daría la trayectoria de la primera echoen $PATH(excepto en casos de esquina), con independencia de que echotambién pasa a ser un / alias / función de línea de orden interna o no.

En otras conchas, preferirías:

  • zsh : echo==echoo echo=$commands[echo]oecho=${${:-echo}:c}
  • ksh , zsh :echo=$(whence -p echo)
  • yash :echo=$(command -ev echo)
  • rc , akanga : echo=`whatis -p echo`(cuidado con los caminos con espacios)
  • peces :set echo (type -fp echo)

Tenga en cuenta que si todo lo que quiere hacer es ejecutar ese echocomando, no tiene que obtener su ruta, simplemente puede hacer:

env echo this is not echoed by the builtin echo

Por ejemplo, con tcsh, para evitar que whichse use el incorporado :

set Echo = "`env which echo`"

cuando necesitas un comando externo

Otro caso en el que es posible que desee utilizar whiches cuando realmente necesita un comando externo. POSIX requiere que todos los componentes integrados de shell (como command) también estén disponibles como comandos externos, pero desafortunadamente ese no es el caso commanden muchos sistemas. Por ejemplo, es raro encontrar un commandcomando en sistemas operativos basados ​​en Linux, mientras que la mayoría de ellos tienen un whichcomando (aunque diferentes con diferentes opciones y comportamientos).

Los casos en los que es posible que desee un comando externo estarían donde ejecute un comando sin invocar un shell POSIX.

El system("some command line"), popen()... funciones de C o varias lenguas no invocan una cáscara de analizar esa línea de comandos, por lo que system("command -v my-cmd")hacen el trabajo en ellos. Una excepción a eso sería la perlque optimiza el shell si no ve ningún carácter especial del shell (que no sea espacio). Eso también se aplica a su operador 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

La adición de lo :;anterior obliga perla invocar un caparazón allí. Al usar which, no tendrías que usar ese truco.

Stéphane Chazelas
fuente
24
@ Joe, whiches un cshguión en muchos Unices comerciales. La razón es histórica, es por eso que di la historia, para que la gente entienda de dónde vino, por qué la gente se acostumbró a usarla y por qué en realidad no hay razón para que la uses. Y sí, algunas personas usan (t) csh. Todavía no todos usan Linux
Stéphane Chazelas
12
Después de leer esta publicación, he encontrado mucho contexto para la respuesta, pero no la respuesta en sí. ¿En qué parte de esta publicación dice realmente por qué no usar which, a diferencia de las cosas que podría estar tratando de usar which, el historial de which, implementaciones which, otros comandos para realizar tareas relacionadas o razones para usar realmente which? ¿Por qué son mejores los otros comandos ? ¿De qué hacen diferente which? ¿Cómo evitan sus trampas? Esta respuesta realmente gasta más palabras en los problemas con las alternativas que los problemas con which.
user62251
1
Al contrario de lo que dice la respuesta, command -vno verifica el permiso de ejecución, al menos si lo llama por un argumento de nombre de archivo puro sin ruta. Probé con dash 0.5.8 y GNU bash 4.3.48.
jarno
2
@ StéphaneChazelas Si creo un nuevo archivo touch /usr/bin/mytestfiley luego lo ejecuto command -v mytestfile, me dará la ruta (mientras which mytestfileque no).
jarno
2
@jarno, oh sí, tienes razón. bashse instalará en un archivo no ejecutable si no puede encontrar uno ejecutable, por lo que está "bien" (aunque en la práctica uno preferiría command -v/ typedevolvería un error) ya que ese es el comando que intentaría ejecutar cuando ejecuta mytestfile, pero el dashel comportamiento es defectuoso, ya que si hay un no ejecutable cmdantes que uno ejecutable, command -vdevuelve el no ejecutable mientras que la ejecución cmdejecutaría el ejecutable (el incorrecto también se tritura). FreeBSD sh(también basado en ash) tiene el mismo error. zsh, yash, ksh, mksh, bash como sh están bien.
Stéphane Chazelas
47

whichYa se han explicado las razones por las que uno no quiere usar , pero aquí hay algunos ejemplos en algunos sistemas donde whichrealmente falla.

En los shells tipo Bourne, estamos comparando la salida de whichcon la salida de type(al typeser un shell incorporado, está destinado a ser la verdad fundamental, ya que es el shell que nos dice cómo invocaría un comando).

Muchos casos son casos de esquina , pero tenga en cuenta que which/ typese usan a menudo en casos de esquina (para encontrar la respuesta a un comportamiento inesperado como: ¿por qué ese comando se comporta así, a quién estoy llamando? ).

La mayoría de los sistemas, la mayoría de los shells tipo Bourne: funciones

El caso más obvio es para las funciones:

$ type ls
ls is a function
ls ()
{
[ -t 1 ] && set -- -F "$@";
command ls "$@"
}
$ which ls
/bin/ls

La razón es que whichsolo informa sobre ejecutables y, a veces, sobre alias (aunque no siempre los de su shell), no sobre funciones.

El GNU cuya página de manual tiene un ejemplo roto (como se olvidaron de citar $@) sobre cómo usarlo para informar funciones también, pero al igual que para los alias, porque no implementa un analizador sintáctico de shell, se engaña fácilmente:

$ 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 mayoría de los sistemas, la mayoría de las conchas tipo Bourne: builtins

Otro caso es obvio órdenes internas o palabras clave, ya que whichsiendo un comando externo no tiene manera de saber qué órdenes internas de su cáscara tiene (y algunas conchas como zsh, basho kshpuede cargar dinámicamente órdenes internas):

$ 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

(eso no se aplica a zshdónde whichestá incorporado)

Solaris 10, AIX 7.1, HP / UX 11i, Tru64 5.1 y muchos otros:

$ 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

Esto se debe a que en la mayoría de los Unices comerciales, which(como en la implementación original en 3BSD) hay un cshscript que lee ~/.cshrc. Los alias que informará son los definidos allí, independientemente de los alias que haya definido actualmente y del shell que esté utilizando.

En HP / UX o Tru64:

% echo 'setenv PATH /bin:/usr/bin' >> ~/.cshrc
% setenv PATH ~/bin:/bin:/usr/bin
% ln -s /bin/ls ~/bin/
% which ls
/bin/ls

(Las versiones de Solaris y AIX han solucionado ese problema guardando $pathantes de leerlo ~/.cshrcy restableciéndolo antes de buscar los comandos)

$ 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

O:

$ 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

(por supuesto, al ser un cshscript, no puede esperar que funcione con argumentos que contienen espacios ...)

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='

En ese sistema, hay un alias definido en todo el sistema que envuelve el whichcomando GNU .

La salida falsa es porque whichlee la salida de bash's aliaspero no sabe cómo analizar correctamente y utiliza la heurística (un alias por línea, busca la encontró por primera vez después de un comando |, ;, &...)

Lo peor en CentOS es que zshtiene un whichcomando incorporado perfectamente bien, pero CentOS logró romperlo al reemplazarlo con un alias que no funciona para GNU which.

Debian 7.0, ksh93:

(aunque se aplica a la mayoría de los sistemas con muchos shells)

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

En Debian, /bin/whiches un /bin/shscript. En mi caso, shser dashpero es lo mismo cuando lo es bash.

Un desarmado PATHno es deshabilitar la PATHbúsqueda, sino que significa usar la RUTA predeterminada del sistema, que desafortunadamente en Debian, nadie está de acuerdo ( dashy bashhave /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin, zshhas /bin:/usr/bin:/usr/ucb:/usr/local/bin, ksh93has /bin:/usr/bin, mkshhas /usr/bin:/bin( $(getconf PATH)), execvp()(like in env) has :/bin:/usr/bin(yes, ¡primero busca en el directorio actual! )).

Es por eso que whichse equivoca más arriba ya que está usando dashel valor predeterminado PATHque es diferente del ksh93de

No es mejor con GNU whichque informa:

which: no which in ((null))

(curiosamente, en efecto, hay una /usr/local/bin/whichen mi sistema, que es en realidad una akangasecuencia de comandos que viene con akanga(un rcderivado de la cáscara cuando el incumplimiento PATHes /usr/ucb:/usr/bin:/bin:.))

bash, cualquier sistema:

Al que Chris se refiere en su respuesta :

$ 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

También después de llamar hashmanualmente:

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

Ahora un caso donde whichya veces typefalla:

$ 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

Ahora, con algunas conchas:

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

Con otros:

$ foo
a/foo

Ni whichtampoco typepueden saber de antemano que b/foono se puede ejecutar. Algunas conchas como bash, ksho yash, cuando se invoca foode hecho va a tratar de ejecutar b/fooe informar de un error, mientras que otros (como zsh, ash, csh, Bourne, tcsh) se ejecutarán a/foosobre el fracaso de la execve()llamada al sistema de b/foo.

Stéphane Chazelas
fuente
mkshen realidad usa algo diferente para el valor predeterminado $PATH: primero, _PATH_DEFPATHse usa la constante de tiempo de compilación del sistema operativo (más comúnmente en los BSD), luego, confstr(_CS_PATH, …)se usa (POSIX), y si ambos no existen o fallan, /bin:/usr/bin:/sbin:/usr/sbinse usa.
mirabilos
1
En su primer ejemplo, incluso si lses una función que está utilizando lsdesde PATH. Y whichestá bien decirle cuál se usa /usr/bin/ls o /usr/local/bin/ls. No veo "¿Por qué no utiliza la cual" ....
rudimeier
@rudimeier, eso which lsme dará /bin/lsindependientemente de si la lsfunción llama /bin/lso /opt/gnu/bin/lso diro nada en absoluto. IOW, which(lo que implementaciones, IMMV) está dando algo irrelevante
Stéphane Chazelas
1
@ StéphaneChazelas. No no no. Ya que mi lses una función. Yo que mi lsfunción está llamando lsdesde PATH. Ahora whichme dice dónde está el archivo. Solo ve un caso de uso único: "¿Qué haría mi shell con este comando?" Para este caso de uso whiches incorrecto, correcto. Pero hay otros casos de uso donde (GNU) whiches exactamente lo correcto.
rudimeier
@rudimeter, depende de la whichimplementación. Algunos le dirán que es un alias (si tiene un alias configurado, o si hay uno ~/.cshrcen su hogar que tiene dicho alias), algunos le darán una ruta pero la incorrecta en algunas condiciones. sh -c 'command -v ls', aunque no es perfecto, es más probable que le dé la respuesta correcta a ese requisito diferente (y también es estándar).
Stéphane Chazelas
21

Una cosa que (desde mi rápida lectura) parece que Stephane no mencionó es que whichno tiene idea sobre la tabla hash de ruta de acceso de su shell. Esto tiene el efecto de que puede devolver un resultado que no es representativo de lo que realmente se ejecuta, lo que lo hace ineficaz en la depuración.

Chris Down
fuente
6

En el espíritu de UNIX: haga que cada programa haga una cosa bien.

Si el objetivo es responder: ¿Qué ejecutable existe con este nombre?

El programa ejecutable que se proporciona con los sistemas Debian es una buena respuesta. El que proporcionaba csh incluía alias, que es una fuente de problemas. El que proporcionan algunos proyectiles como una construcción interna tiene un objetivo diferente. Utilice ese ejecutable o use el script proporcionado al final de esta respuesta.

Si se utiliza este script, lo que responde es limpio, simple y útil.

Este objetivo coincidirá con la primera oración de su pregunta:

Al buscar la ruta a un ejecutable ... ...

Si tiene un sistema que no tiene un ejecutable llamado que (la mayoría de los sistemas de Linux tienen uno), puede crear uno ~/bin/whichantes /bin/en la RUTA para que el sistema de anulación de ejecutables personales sea el que se encuentra al final de esta publicación:

Ese ejecutable enumerará (por defecto) todos los ejecutables encontrados en la RUTA. Si solo se requiere el primero, la opción -festá disponible.


En este punto caemos en un objetivo diferente:

qué ejecutará el shell (después del análisis)

Eso viene de tu segunda oración:

comprobar lo que sucedería si ingresa un nombre de comando en un shell de Unix

Este segundo tema intenta encontrar una buena respuesta a una pregunta que es bastante difícil de responder. Los proyectiles tienen vistas divergentes, casos de esquina y (como mínimo) diferentes interpretaciones. Agregando a eso:

Hay una gran cantidad de diferentes utilidades (que, tipo, comando, de dónde, dónde, dónde, qué, hash, etc.).

Y claro, todos los intentos coinciden con ese objetivo.


¿Evitar cuál?

A menudo escuchamos lo que debe evitarse.

Me pregunto: ¿por qué debería decirse eso si whichfunciona bien (al menos en Debian)?

En el espíritu de UNIX: haga que cada programa haga una cosa bien.

El programa externowhich está haciendo una cosa: encontrar el primer ejecutable en la RUTA que tenga el mismo nombre que el nombre del comando . Y lo está haciendo razonablemente bien.

No conozco ningún otro programa o utilidad que responda a esta pregunta de una manera más fundamental. Como tal, es útil y podría usarse cuando sea necesario.

La alternativa más cercana parece ser:, command -pv commandNamepero eso también informará sobre alias y construcciones. No es la misma respuesta.

Por supuesto, whiches limitado, no responde todas las preguntas, ninguna herramienta podría hacer eso (bueno, todavía no ...). Pero es útil cuando se usa para responder la pregunta para la que fue diseñada (la que se encuentra arriba). Mucho como edfue limitado y luego sedapareció (o vi/ vim). O like awkfue limitado e hizo que Perl apareciera y se extendiera. Sin embargo, ed, sedy / o awktienen casos de uso específicos en los que vimo perlson no las mejores herramientas.

¿Por qué?

Probablemente porque whichresponde solo una parte de la pregunta que un usuario de shell podría hacer:

¿Qué se ejecuta cuando escribo un nombre de comando?


Externo que

Que debería estar disponible (en muchos sistemas) como un ejecutable externo.
La única forma segura de llamar a esa herramienta externa es usar env para salir del shell y luego llamar which(que funciona en todos los shells):

 $ env which which
 /usr/bin/which

O use la ruta completa a which(que puede variar en diferentes sistemas):

 /usr/bin/which which 

¿Por qué es eso hacknecesario? Debido a que algunos shells (especialmente zsh) se esconden which:

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

Ser una herramienta externa (como env) explica perfectamente por qué no informará información interna del shell. Como alias, funciones, incorporados, incorporados especiales, variables de shell (no exportadas), etc.

 $ env which ls
 /usr/bin/ls
 $ env which ll       # empty output

La salida vacía de ll(un alias común para ll='ls -l') indica que llno está relacionado con un programa ejecutable, o al menos, que no hay un archivo ejecutable nombrado llen la RUTA. El uso de lldebería llamar a otra cosa, en este caso, un alias:

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

type y command

Los comandos typey command -vson solicitados por POSIX. Se debe esperar que trabajen en la mayoría de los depósitos, y lo hacen, excepto en csh, tcsh, fish y rc.

Ambos comandos podrían usarse para proporcionar otro punto de vista de qué comando se ejecutará.

whence` where` whereis` whatis`hash

Entonces, hay whence, where, whereis, whatis, hash, y algunos otros. Todas las respuestas diferentes a preguntas similares. Todos trabajan de diferentes maneras en diferentes conchas. Probablemente, whencees el más común después type. Los otros son soluciones especiales que responden a la misma pregunta de diferentes maneras.

¿Qué deberíamos usar en su lugar?

Probablemente whichprimero en saber si existe un archivo ejecutable con el nombre de la CommandName , a continuación, typey commanda continuación, si el CommandName no se ha encontrado todavía: whence, where, whereis, whatis, hashen ese orden.


Script de shell para proporcionar un whichejecutable.

#! /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
fuente
0

A menudo escuchamos lo que debe evitarse. ¿Por qué? ¿Qué deberíamos usar en su lugar?

Nunca he escuchado eso. Por favor proporcione ejemplos específicos. ¡Me preocuparía su distribución de Linux y los paquetes de software instalados, ya que es de donde whichviene!

SLES 11.4 x86-64

en tcsh versión 6.18.01:

> which which

which: shell built-in command.

en la versión 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.

whichforma parte de util-linux, un paquete estándar distribuido por Linux Kernel Organization para su uso como parte del sistema operativo Linux. También proporciona estos otros archivos

/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

mi util-linuxes la versión 2.19. Las notas de la versión se pueden encontrar fácilmente en la versión v2.13 con fecha (28 de agosto de 2007). No estoy seguro de cuál era el objetivo o el objetivo de esto, ciertamente no fue respondido en ese largo asunto votado 331 veces.

ron
fuente
2
Observe cómo la pregunta no menciona a qué Unix se refiere. Linux es solo uno de los pocos.
Kusalananda
2
Como which -vmuestra, ese es GNU que (el extravagante mencionado en la otra respuesta y no es específico para Linux), no util-linux, que AFAIK nunca incluyó una whichutilidad. util-linux 2.19 es de 2011, GNU que 2.19 es de 2008.
Stéphane Chazelas