¿Cómo verificar si existe un progtwig desde un script Bash?

¿Cómo podría validar que existe un progtwig, de forma que devuelva un error y salga, o continúe con el script?

Parece que debería ser fácil, pero me ha estado golpeando.

Responder

Compatible con POSIX:

command -v  

Para entornos específicos de bash :

 hash  # For regular commands. Or... type  # To check built-ins and keywords 

Explicación

Evitar which . No solo se trata de un proceso externo que está iniciando para hacer muy poco (lo que significa que los comandos integrados son menos costosos), también puede confiar en que los editores realmente hagan lo que desee, mientras que los efectos de los comandos externos pueden varían fácilmente de un sistema a otro.

¿Por qué importa?

  • Muchos sistemas operativos tienen un que ni siquiera establece un estado de salida , lo que significa if which foo ni siquiera funciona allí y siempre informará que foo existe, incluso si no lo hace (tenga en cuenta que algunas shells POSIX parecen hacer esto para hash también).
  • Muchos sistemas operativos hacen cosas personalizadas y malvadas como cambiar la salida o incluso enganchar en el administrador de paquetes.

Entonces, no uses which . En su lugar, use uno de estos:

 $ command -v foo >/dev/null 2>&1 || { echo >&2 "I require foo but it's not installed. Aborting."; exit 1; } $ type foo >/dev/null 2>&1 || { echo >&2 "I require foo but it's not installed. Aborting."; exit 1; } $ hash foo 2>/dev/null || { echo >&2 "I require foo but it's not installed.  Aborting."; exit 1; } 

(Nota secundaria: algunos sugieren que 2>&- es el mismo 2>/dev/null pero más corto – esto es falso . 2>&- cierra FD 2 lo que causa un error en el progtwig cuando intenta escribir en stderr , que es muy diferente de escribir con éxito y descartar la salida (¡y peligroso!)

Si su hash bang es /bin/sh entonces debería preocuparse por lo que dice POSIX. los códigos de salida de type y hash no están muy bien definidos por POSIX, y se ve que el hash sale exitosamente cuando el comando no existe (aún no lo ha visto con el type ). command estado de salida del command está bien definido por POSIX, por lo que uno es probablemente el más seguro de usar.

Si su script usa bash , las reglas POSIX realmente ya no importan y tanto el type como el hash vuelven perfectamente seguros de usar. type ahora tiene una -P para buscar solo la PATH y el hash tiene el efecto secundario de que la ubicación del comando será hash (para una búsqueda más rápida la próxima vez que lo use), que generalmente es algo bueno ya que probablemente verifique su existencia en para usarlo realmente

Como un ejemplo simple, aquí hay una función que ejecuta gdate si existe, de lo contrario date :

 gnudate() { if hash gdate 2>/dev/null; then gdate "$@" else date "$@" fi } 

La siguiente es una forma portátil de comprobar si existe un comando en $PATH y es ejecutable:

 [ -x "$(command -v foo)" ] 

Ejemplo:

 if ! [ -x "$(command -v git)" ]; then echo 'Error: git is not installed.' >&2 exit 1 fi 

La verificación ejecutable es necesaria porque bash devuelve un archivo no ejecutable si no se encuentra ningún archivo ejecutable con ese nombre en $PATH .

También tenga en cuenta que si existe un archivo no ejecutable con el mismo nombre que el ejecutable en $PATH , dash devuelve el primero, aunque este último se ejecutará. Este es un error y está en violación del estándar POSIX. [ Informe de error ] [ Estándar ]

Además, esto fallará si el comando que está buscando se ha definido como un alias.

Estoy de acuerdo con lhunath para desalentar el uso de los which , y su solución es perfectamente válida para los usuarios de BASH . Sin embargo, para ser más portátil, se usará el command -v su lugar:

 $ command -v foo >/dev/null 2>&1 || { echo "I require foo but it's not installed. Aborting." >&2; exit 1; } 

El command Command cumple con POSIX, consulte aquí su especificación: http://pubs.opengroup.org/onlinepubs/9699919799/utilities/command.html

Nota: el type es compatible con POSIX, pero el type -P no lo es.

Tengo una función definida en mi .bashrc que hace esto más fácil.

 command_exists () { type "$1" &> /dev/null ; } 

Aquí hay un ejemplo de cómo se usa (de mi .bash_profile ).

 if command_exists mvim ; then export VISUAL="mvim --nofork" fi 

Depende de si desea saber si existe en uno de los directorios en la variable $PATH o si conoce la ubicación absoluta de la misma. Si quiere saber si está en la variable $PATH , use

 if which programname >/dev/null; then echo exists else echo does not exist fi 

de otro modo usar

 if [ -x /path/to/programname ]; then echo exists else echo does not exist fi 

La redirección a /dev/null/ en el primer ejemplo suprime la salida de cada progtwig.

Expandiendo las respuestas de @lhunath y @ GregV, aquí está el código para las personas que quieren poner fácilmente ese cheque dentro de una statement if :

 exists() { command -v "$1" >/dev/null 2>&1 } 

He aquí cómo usarlo:

 if exists bash; then echo 'Bash exists!' else echo 'Your system does not have Bash' fi 

Intenta usar:

 test -x filename 

o

 [ -x filename ] 

Desde la página de manual de bash en Expresiones condicionales :

  -x file True if file exists and is executable. 

Para usar hash , como lo sugiere @lhunath , en un script bash:

 hash foo &> /dev/null if [ $? -eq 1 ]; then echo >&2 "foo not found." fi 

Esta secuencia de comandos ejecuta hash y luego comprueba si el código de salida del comando más reciente, el valor almacenado en $? , es igual a 1 . Si hash no encuentra foo , el código de salida será 1 . Si foo está presente, el código de salida será 0 .

&> /dev/null redirige el error estándar y la salida estándar del hash para que no aparezca en la pantalla y echo >&2 escribe el mensaje en error estándar.

Nunca obtuve las soluciones anteriores para trabajar en la caja a la que tengo acceso. Para uno, el tipo ha sido instalado (haciendo lo que más hace). Entonces la directiva integrada es necesaria. Este comando funciona para mí:

 if [ `builtin type -p vim` ]; then echo "TRUE"; else echo "FALSE"; fi 

Si comprueba la existencia del progtwig, probablemente lo ejecute más tarde de todos modos. ¿Por qué no intentar ejecutarlo en primer lugar?

 if foo --version >/dev/null 2>&1; then echo Found else echo Not found fi 

Es una verificación más confiable que se ejecuta el progtwig que simplemente mirar los directorios PATH y los permisos de archivos.

Además, puede obtener algunos resultados útiles de su progtwig, como su versión.

Por supuesto, los inconvenientes son que algunos progtwigs pueden ser pesados ​​para comenzar y algunos no tienen una opción de --version para salir de inmediato (y con éxito).

Verifique las múltiples dependencias e informe el estado a los usuarios finales

 for cmd in "latex" "pandoc"; do printf "%-10s" "$cmd" if hash "$cmd" 2>/dev/null; then printf "OK\n"; else printf "missing\n"; fi done 

Muestra de salida:

 latex OK pandoc missing 

Ajuste la longitud de comando de 10 a la máxima. No es automático porque no veo una forma POSIX no detallada de hacerlo: ¿Cómo alinear las columnas de una tabla separada por espacios en Bash?

Para aquellos interesados, ninguna de las metodologías anteriores funciona si desea detectar una biblioteca instalada. Imagino que le quedan ya sea con la comprobación física de la ruta (potencialmente para los archivos de encabezado y tal), o algo así (si está en una distribución basada en Debian):

 dpkg --status libdb-dev | grep -q not-installed if [ $? -eq 0 ]; then apt-get install libdb-dev fi 

Como puede ver en lo anterior, una respuesta “0” de la consulta significa que el paquete no está instalado. Esta es una función de “grep” – un “0” significa que se encontró una coincidencia, un “1” significa que no se encontraron coincidencias.

¿Por qué no usar Bash builtins si puedes?

 which programname 

 type -P programname 

hash foo 2>/dev/null : funciona con zsh, bash, dash y ash.

type -p foo : parece funcionar con zsh, bash y ash (busybox), pero no dash (interpreta -p como argumento).

command -v foo : funciona con zsh, bash, dash, pero no ceniza (busybox) ( -ash: command: not found ).

También tenga en cuenta que el builtin in no está disponible con ash y dash .

El comando which podría ser útil. hombre que

Devuelve 0 si se encuentra el ejecutable, 1 si no se encuentra o no es ejecutable:

 NAME which - locate a command SYNOPSIS which [-a] filename ... DESCRIPTION which returns the pathnames of the files which would be executed in the current environment, had its arguments been given as commands in a strictly POSIX-conformant shell. It does this by searching the PATH for executable files matching the names of the arguments. OPTIONS -a print all matching pathnames of each argument EXIT STATUS 0 if all specified commands are found and executable 1 if one or more specified commands is nonexistent or not exe- cutable 2 if an invalid option is specified 

Lo bueno de esto es que se da cuenta si el ejecutable está disponible en el entorno en el que se ejecuta, guarda algunos problemas …

-Adán

Diría que no existe una forma portátil y 100% confiable debido a los alias colgantes. Por ejemplo:

 alias john='ls --color' alias paul='george -F' alias george='ls -h' alias ringo=/ 

Por supuesto, solo el último es problemático (¡sin ofender a Ringo!) Pero todos ellos son alias válidos desde el punto de vista del command -v .

Para rechazar los colgantes como ringo , tenemos que analizar el resultado del comando de alias incorporado al shell y recursar en ellos (el command -v no es superior al alias aquí.) No hay una solución portátil para él, e incluso un Bash una solución específica es bastante tediosa

Tenga en cuenta que una solución como esta rechazará incondicionalmente alias ls='ls -F'

 test() { command -v $1 | grep -qv alias } 

Para imitar el type -P cmd Bash, podemos usar un tipo de entorno compatible con POSIX env -i type cmd 1>/dev/null 2>&1 .

 man env # "The option '-i' causes env to completely ignore the environment it inherits." # In other words, there are no aliases or functions to be looked up by the type command. ls() { echo 'Hello, world!'; } ls type ls env -i type ls cmd=ls cmd=lsx env -i type $cmd 1>/dev/null 2>&1 || { echo "$cmd not found"; exit 1; } 

La variante de hash tiene una dificultad: en la línea de comando puede, por ejemplo, escribir

 one_folder/process 

tener el proceso ejecutado. Para esto, la carpeta primaria de one_folder debe estar en $ PATH . Pero cuando intentes hash este comando, siempre tendrá éxito:

 hash one_folder/process; echo $? # will always output '0' 

Yo segundo el uso de “comando -v”. Por ejemplo:

 md=$(command -v mkdirhier) ; alias md=${md:=mkdir} # bash emacs="$(command -v emacs) -nw" || emacs=nano alias e=$emacs [[ -z $(command -v jed) ]] && alias jed=$emacs 

Si no hay ningún comando de type externo disponible (como se da por sentado aquí ), podemos usar el entorno compatible con POSIX, env -i sh -c 'type cmd 1>/dev/null 2>&1' :

 # portable version of Bash's type -P cmd (without output on stdout) typep() { command -p env -i PATH="$PATH" sh -c ' export LC_ALL=C LANG=C cmd="$1" cmd="`type "$cmd" 2>/dev/null || { echo "error: command $cmd not found; exiting ..." 1>&2; exit 1; }`" [ $? != 0 ] && exit 1 case "$cmd" in *\ /*) exit 0;; *) printf "%s\n" "error: $cmd" 1>&2; exit 1;; esac ' _ "$1" || exit 1 } # get your standard $PATH value #PATH="$(command -p getconf PATH)" typep ls typep builtin typep ls-temp 

Al menos en Mac OS X 10.6.8 usando el comando Bash 4.2.24 (2) command -v ls no coincide con un movimiento /bin/ls-temp .

mi configuración para un servidor Debian. Tuve un problema cuando varios paquetes contienen el mismo nombre. por ejemplo apache2. así que esta fue mi solución.

 function _apt_install() { apt-get install -y $1 > /dev/null } function _apt_install_norecommends() { apt-get install -y --no-install-recommends $1 > /dev/null } function _apt_available() { if [ `apt-cache search $1 | grep -o "$1" | uniq | wc -l` = "1" ]; then echo "Package is available : $1" PACKAGE_INSTALL="1" else echo "Package $1 is NOT available for install" echo "We can not continue without this package..." echo "Exitting now.." exit 0 fi } function _package_install { _apt_available $1 if [ "${PACKAGE_INSTALL}" = "1" ]; then if [ "$(dpkg-query -l $1 | tail -n1 | cut -c1-2)" = "ii" ]; then echo "package is already_installed: $1" else echo "installing package : $1, please wait.." _apt_install $1 sleep 0.5 fi fi } function _package_install_no_recommends { _apt_available $1 if [ "${PACKAGE_INSTALL}" = "1" ]; then if [ "$(dpkg-query -l $1 | tail -n1 | cut -c1-2)" = "ii" ]; then echo "package is already_installed: $1" else echo "installing package : $1, please wait.." _apt_install_norecommends $1 sleep 0.5 fi fi } 

Si ustedes no pueden hacer que las cosas de arriba / abajo funcionen y se quiten el pelo de la espalda, intenten ejecutar el mismo comando usando bash -c . Solo mire este delirio somnambular, esto es lo que realmente sucede cuando ejecuta $ (sub-comando):

Primero. Puede darte resultados completamente diferentes.

 $ command -v ls alias ls='ls --color=auto' $ bash -c "command -v ls" /bin/ls 

Segundo. No le puede dar ninguna salida en absoluto.

 $ command -v nvm nvm $ bash -c "command -v nvm" $ bash -c "nvm --help" bash: nvm: command not found 

En caso de que desee verificar si existe un progtwig y realmente es un progtwig, no un comando integrado de bash , entonces el command , type y hash no son apropiados para la prueba ya que todos devuelven el estado de salida 0 para los comandos incorporados.

Por ejemplo, está el progtwig de tiempo que ofrece más funciones que el comando de tiempo incorporado. Para verificar si el progtwig existe, sugiero usarlo como en el siguiente ejemplo:

 # first check if the time program exists timeProg=`which time` if [ "$timeProg" = "" ] then echo "The time program does not exist on this system." exit 1 fi # invoke the time program $timeProg --quiet -o result.txt -f "%S %U + p" du -sk ~ echo "Total CPU time: `dc -f result.txt` seconds" rm result.txt 
 checkexists() { while [ -n "$1" ]; do [ -n "$(which "$1")" ] || echo "$1": command not found shift done } 

Lo uso porque es muy fácil:

 if [ `LANG=C type example 2>/dev/null|wc -l` = 1 ];then echo exists;else echo "not exists";fi 

o

 if [ `LANG=C type example 2>/dev/null|wc -l` = 1 ];then echo exists else echo "not exists" fi 

Utiliza el shell incorporado y el estado de eco del progtwig para stdout y nada para stderr con la otra mano si no se encuentra un comando, echos estado solo a stderr.

Guión

 #!/bin/bash # Commands found in the hash table are checked for existence before being # executed and non-existence forces a normal PATH search. shopt -s checkhash function exists() { local mycomm=$1; shift || return 1 hash $mycomm 2>/dev/null || \ printf "\xe2\x9c\x98 [ABRT]: $mycomm: command does not exist\n"; return 1; } readonly -f exists exists notacmd exists bash hash bash -c 'printf "Fin.\n"' 

Resultado

 ✘ [ABRT]: notacmd: command does not exist hits command 0 /usr/bin/bash Fin. 

Aquí hay un montón de opciones, pero me sorprendió que no haya frases rápidas, esto es lo que usé al principio de mis scripts: [[ "$(command -v mvn)" ]] || { echo "mvn is not installed" 1>&2 ; exit 1; } [[ "$(command -v java)" ]] || { echo "java is not installed" 1>&2 ; exit 1; } [[ "$(command -v mvn)" ]] || { echo "mvn is not installed" 1>&2 ; exit 1; } [[ "$(command -v java)" ]] || { echo "java is not installed" 1>&2 ; exit 1; }

esto se basa en la respuesta seleccionada aquí y en otra fuente (y yo jugando un poco).

Espero que esto sea útil para otros.

el comando -v funciona bien si la opción POSIX_BUILTINS está configurada para que el pruebe, pero puede fallar si no lo hace. (Me ha funcionado durante años, pero recientemente me encontré con uno en el que no funcionó).

Creo que lo siguiente es más a prueba de fallas:

 test -x $(which ) 

Como prueba 3 cosas: ruta, ejecución y permiso.

Estoy usando una versión muy útil y corta:

 dpkg -s curl 2>/dev/null >/dev/null || apt-get -y install curl 

Tan fácil si solo se debe verificar un progtwig.

Simplemente intentaría llamar al progtwig con, por ejemplo, --version o --help y verificaría si el comando tuvo éxito o falló

Usado con set -e script saldrá si no se encuentra el progtwig, y ​​obtendrá un mensaje de error significativo:

 #!/bin/bash set -e git --version >> /dev/null