Script de shell de Linux: ejecute un programa solo si existe, ignórelo si no existe

15

Estoy programando un script de shell de Linux que imprimirá pancartas de estado durante su ejecución solo si la herramienta adecuada, por ejemplo figlet, está instalada (esto es: accesible en la ruta del sistema ).

Ejemplo:

#!/usr/bin/env bash
echo "foo"
figlet "Starting"
echo "moo"
figlet "Working"
echo "foo moo"
figlet "Finished"

Me gustaría que mi script funcione sin errores incluso cuando nofiglet esté instalado .

¿Cuál podría ser un método práctico ?

Sopalajo de Arrierez
fuente
@sudodus: simplemente ignorar el comando 'figlet' (y sus parámetros) estaría bien. Ejecución continua, por supuesto.
Sopalajo de Arrierez
2
El título de esta pregunta me metió en todo tipo de problemas metafísicos
g_uint
2
¿Quieres ignorar todos los errores? Solo úsalo figlet ... || true.
Giacomo Alzetta
Si no le interesan los códigos de salida, debe utilizar un acceso directo figlet || true, pero en su caso, probablemente, una función de shell que Echos de texto simple Si no se puede imprimir un Banner es más probable que lo que desee.
Eckes

Respuestas:

30

Mi interpretación usaría una función de contenedor llamada igual que la herramienta; en esa función, ejecute la herramienta real si existe:

figlet() {
  command -v figlet >/dev/null && command figlet "$@"
}

Entonces puede haber figlet arg1 arg2...cambiado en su secuencia de comandos.

A @Olorin se le ocurrió un método más simple: definir una función de contenedor solo si es necesario (si la herramienta no existe):

if ! command -v figlet > /dev/null; then figlet() { :; }; fi

Si desea que figletse impriman los argumentos incluso si el figlet no está instalado, ajuste la sugerencia de Olorin de la siguiente manera:

if ! command -v figlet > /dev/null; then figlet() { printf '%s\n' "$*"; }; fi
Jeff Schaller
fuente
1
De donde commandviene No lo tengo en mis instalaciones (Red Hat 6.8 Enterprise y Cygwin64).
eewanco
1
@eewanco, ver unix.stackexchange.com/a/85250/117549 ; Para resumir, está construido para bash, que probablemente sea tu caparazón.
Jeff Schaller
44
commandes un shell POSIX Bourne incorporado. Normalmente ejecuta el comando proporcionado, pero la -vbandera hace que se comporte más como typeotro shell integrado.
wyrm
@eewanco type -a commandte lo mostrará. whichserá únicamente mostrar los ejecutables en sus $PATH, no empotrados, palabras clave, funciones o alias.
l0b0
@ l0b0: en RedHat familiar con bash y el perfil predeterminado (s) which no encuentra un alias aplicable, porque alias whichpara ejecutarse /usr/bin/which(!) y el tubo es una lista de alias de la shell que mirar en (Por supuesto \whichsuprime el alias y usa solo el programa, que no muestra alias.)
dave_thompson_085
14

Puedes probar para ver si figletexiste

if type figlet >/dev/null 2>&1
then
    echo Figlet is installed
fi
roaima
fuente
6

Una forma común de hacer esto es con test -xaka [ -x. Aquí hay un ejemplo tomado de /etc/init.d/ntpun sistema Linux:

if [ -x /usr/bin/lockfile-create ]; then
    lockfile-create $LOCKFILE
    lockfile-touch $LOCKFILE &
    LOCKTOUCHPID="$!"
fi

Esta variante se basa en conocer la ruta completa del ejecutable. En /bin/lesspipeEncontré un ejemplo que funciona alrededor de eso combinando -xy el whichcomando:

if [ -x "`which bunzip`" ]; then bunzip -c "$1"
else echo "No bunzip available"; fi ;;

De esta forma, esto funcionará sin saber de antemano en qué parte PATHdel bunzipejecutable se encuentra.

kasperd
fuente
44
No utilicewhich . E incluso si whichfunciona, usarlo test -xen su salida es una tontería: si obtiene una ruta which, existe.
Gilles 'SO- deja de ser malvado'
1
@Gilles: Técnicamente hablando, test -xhace más que simplemente verificar si existe un archivo (para eso test -eestá). También verifica si el archivo tiene establecidos los permisos de ejecución.
comfreak
@comfreak También lo hace which.
Gilles 'SO- deja de ser malvado'
5

Al comienzo de su script, verifique si figletexiste, y si no existe, defina una función de shell que no haga nada:

type figlet >/dev/null 2>&1 || figlet() { :; }

typecomprueba si figletexiste como un shell incorporado, función, alias o palabra clave, >/dev/null 2>&1descarta stdin y stdout para que no obtenga ningún resultado, y si no existe,figlet() { :; } define figletcomo una función que no hace nada.

De esta manera, no tiene que editar cada línea de su script que use figlet , o verificar si existe cada vez que figletse llama.

Puede agregar un mensaje de diagnóstico, si lo desea:

type figlet >/dev/null 2>&1 || { echo 'figlet not installed.' ; figlet() { :; } ; }

Como beneficio adicional, dado que no mencionó qué shell está utilizando, creo que esto es compatible con POSIX, por lo que debería funcionar en la mayoría de los shell.

Chris
fuente
Me gusta la simplicidad de esta respuesta. También podría reemplazar type figlet >/dev/null 2>&1con hash figlet 2>/dev/nullsi está usando bash. (El OP dijo "si está en mi RUTA".)
Joe
5

Otra alternativa: un patrón que he visto en los scripts de configuración automática del proyecto:

if [ -x /usr/bin/figlet ]
then
    FIGLET=/usr/bin/figlet
else
    FIGLET=:
fi

$FIGLET "Hello, world!"

En su caso específico, incluso podría hacer,

if [ -x /usr/bin/figlet ]
then
   SAY=/usr/bin/figlet
elif [ -x /usr/local/bin/figlet ]
then
   SAY=/usr/local/bin/figlet
elif [ -x /usr/bin/banner ]
then
   SAY=/usr/bin/banner
else
   SAY=/usr/bin/echo
fi

$SAY "Hello, world!"

Si no conoce la ruta específica, puede intentar múltiples elif(ver arriba) para probar ubicaciones conocidas, o simplemente usar el PATHpara resolver siempre el comando:

if command -v figlet >/dev/null
then
    SAY=figlet
elif command -v banner >/dev/null
then
    SAY=banner
else
    SAY=echo
fi

En general, cuando escribo guiones, prefiero llamar solo a comandos en ubicaciones específicas especificadas por mí. No me gusta la incertidumbre / riesgo de lo que el usuario final podría haber puesto en ellos PATH, quizás en su propio~/bin .

Si, por ejemplo, estaba escribiendo una secuencia de comandos complicada para otros que podrían eliminar archivos en función de la salida de un comando en particular al que estoy llamando, no quisiera recoger accidentalmente algo en su ~/binque podría o no ser el comando Esperaba.

rrauenza
fuente
3
¿Qué pasa si figletestaba en /usr/local/bino /home/bob/stuff/programs/executable/figlet?
Gilles 'SO- deja de ser malvado'
3
type -p figlet > /dev/null && figlet "foo"

El typecomando bash encuentra un comando, una función, un alias, una palabra clave o una función incorporada (ver help type) e imprime la ubicación o definición. También devuelve un código de retorno que representa el resultado de la búsqueda; verdadero (0) si se encuentra. Entonces, lo que estamos haciendo aquí es tratar de encontrar figleten la ruta ( -psignifica solo buscar archivos, no funciones integradas o funciones, y también suprime los mensajes de error), descartando la salida (eso es lo que > /dev/nullhace), y si devuelve verdadero ( &&) , se ejecutaráfiglet .

Esto es más simple si figletestá en una ubicación fija:

[ -x /usr/bin/figlet ] && /usr/bin/figlet "foo"

Aquí estamos usando el testcomando (aka [) para ver si /usr/bin/figletes ejecutable ( -x) y si es así ( &&) ejecutarlo. Creo que esta solución es más portátil que usartype que es un bashismo, creo.

Podría hacer una función que haga esto por usted:

function x() {
    if type -p "$1" >/dev/null; then
        cmd="$1"
        shift
        "$cmd" "$@"
    fi
}

(Las citas son necesarias debido a los espacios potenciales)

Entonces simplemente harías:

x figlet "foo"
eewanco
fuente
0

o /, diría algo como

#!/usr/bin/env bash
# if figlet is installed :
if [ "$(which figlet 2>/dev/null)" ]; then
       # do what you wanted to do
       echo "foo"
       figlet "Starting"
       echo "moo"
       figlet "Working"
       echo "foo moo"
       figlet "Finished"
# if not
else
       # exit program with an error
       echo "please install figlet"
       exit 1
fi
Jus de Patate_
fuente
0

Puede hacer una ejecución de prueba, suprimir cualquier salida y probar el código de éxito / falla. Elija argumentos para figlet para hacer esta prueba de bajo costo. -? o --help o --version son posibilidades obvias.

if figlet --help >/dev/null 2>&1 ; then
    # figlet is available
    echo "foo"
    figlet "starting"
    #etc
else
    rc=$?
    echo "figlet is not installed or not working correctly (return code ${rc})"
fi

Se agregó en respuesta al comentario a continuación: si realmente desea probar que existe el figlet, no que sea utilizable, entonces lo haría

figlet --help >/dev/null 2>&1 
rc=$?
if [ $rc -eq 127 ] ; then # 127 is "command not found" on linux bash 4.4.23(1)
    echo "command figlet not found"
else
    figlet "whatever" # use figlet
fi
nigel222
fuente
El problema aquí es que Figlet puede fallar por razones además de no estar instalado, por lo que simplemente probar el código de salida no es suficiente.
Chris
Si realmente quiere probar solo su instalación, entonces quiere probar si $?es igual a 127 (en mi sistema Linux). 127 es "comando no encontrado". Pero creo que para los comandos racionales, si command --helpfalla, entonces la instalación está lo suficientemente bloqueada como para que no esté allí.
nigel222
1
126 es "comando encontrado pero no ejecutable", por lo que también querrá probarlo. Lamentablemente, no puede depender de --helpestar disponible. Las utilidades de Posix no lo tienen, por ejemplo, y las pautas de posix realmente recomiendan no hacerlo . at --helpfalla con at: invalid option -- '-', por ejemplo, y un estado de salida de 130en mi sistema.
Chris
Además, por supuesto, si figlet puede salir con el estado 127, eso también podría ser un problema.
Chris
Punto justo sobre 126. La mayoría de las empresas sanas tienen algún tipo de opciones de comando inofensivas y ligeras, como -? --help --version... o podría averiguar qué figlet -0 --illegalexiste y tratarlo como su indicador de éxito (siempre que no sea 127, lo que clasificaría como sabotaje).
nigel222