linguaggio - Come verificare se un programma esiste da uno script Bash?




linguaggio linux (20)

Come convaliderei che un programma esiste, in un modo che restituirà un errore e uscirà o continuerà con lo script?

Sembra che dovrebbe essere facile, ma mi ha bloccato.


Risposta

Compatibile con POSIX:

command -v <the_command>

Per ambienti specifici di bash :

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

Spiegazione

Evitare which . Non solo è un processo esterno che stai lanciando per fare pochissimo (significa che builtin come hash , type o command sono molto più economici), puoi anche fare affidamento sui builtin per fare effettivamente quello che vuoi, mentre gli effetti dei comandi esterni possono facilmente variare da sistema a sistema.

Perché preoccuparsi?

  • Molti sistemi operativi hanno uno che non imposta nemmeno uno stato di uscita , ovvero if which foo non funziona nemmeno lì e segnalerà sempre che foo esiste, anche se non lo fa (notare che alcune shell POSIX sembrano fare anche questo per hash ).
  • Molti sistemi operativi fanno ciò which fanno cose personalizzate e malvagie come cambiare l'output o persino collegarsi al gestore di pacchetti.

Quindi, non usare which . Usa invece uno di questi:

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

(Lato secondario: alcuni suggeriscono 2>&- è lo stesso 2>/dev/null ma più breve - questo è falso . 2>&- chiude FD 2 che causa un errore nel programma quando tenta di scrivere su stderr , che è molto diverso da scrivere con successo su di esso e scartare l'output (e pericoloso!))

Se il tuo hash bang è /bin/sh , dovresti preoccuparti di ciò che dice POSIX. i codici di uscita di type e di hash non sono terribilmente ben definiti da POSIX, e l' hash è visto per uscire correttamente quando il comando non esiste (non ho ancora visto questo con il type ). command stato di uscita del command è ben definito da POSIX, quindi uno è probabilmente il più sicuro da usare.

Se il tuo script usa bash , le regole POSIX non contano più e sia il type che l' hash diventano perfettamente sicuri da usare. type ora ha un -P per cercare solo il PATH e l' hash ha l'effetto collaterale che l'ubicazione del comando sarà hash (per una ricerca più rapida la prossima volta che la usi), che di solito è una buona cosa dal momento che probabilmente controlli la sua esistenza in per poterlo effettivamente utilizzare.

Come semplice esempio, ecco una funzione che esegue gdate se esiste, altrimenti date :

gnudate() {
    if hash gdate 2>/dev/null; then
        gdate "[email protected]"
    else
        date "[email protected]"
    fi
}

Ci sono un sacco di opzioni qui, ma non sono stato sorpreso da battute veloci, questo è quello che ho usato all'inizio dei miei script: [[ "$(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; }

questo è basato sulla risposta selezionata qui e su un'altra fonte (e io a giocare un po ').

spero che questo sarà utile per gli altri.


Direi che non esiste un modo affidabile e al 100% affidabile a causa dell'alias ciondolante. Per esempio:

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

Naturalmente solo l'ultimo è problematico (senza offesa a Ringo!) Ma tutti sono alias validi dal punto di vista del command -v .

Per rifiutare quelli che ringo come ringo , dobbiamo analizzare l'output del comando alias incorporato nella shell e ricorrere a loro ( command -v non è superiore alias qui.) Non c'è una soluzione portatile per questo, e persino un Bash Una soluzione specifica è piuttosto noiosa.

Nota che una soluzione come questa rifiuta incondizionatamente alias ls='ls -F'

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

Espandendo le risposte di @ lhunath e @ GregV, ecco il codice per le persone che vogliono mettere facilmente questo controllo in una dichiarazione if :

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

Ecco come usarlo:

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

I secondo l'uso di "comando -v". Ad esempio in questo modo:

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

Il comando which potrebbe essere utile. uomo che

Restituisce 0 se viene trovato l'eseguibile, 1 se non è stato trovato o non è eseguibile:

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

La cosa bella di ciò è che capisce se l'eseguibile è disponibile nell'ambiente in cui viene eseguito - salva alcuni problemi ...

-Adamo


La variante hash ha una trappola: sulla riga di comando è possibile, ad esempio, digitare

one_folder/process

per avere processo eseguito. Per questo la cartella padre di one_folder deve essere in $ PATH . Ma quando provate a eseguire questo comando hash, avrà sempre successo:

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

Lo uso perché è molto semplice:

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

Usa lo shell integrato e lo stato di echo del programma allo stdout e nulla allo stderr dall'altro se non viene trovato un comando, lo stato di echos solo per stderr.


Non ho mai avuto le soluzioni di cui sopra per lavorare sulla scatola a cui ho accesso. Per uno, il tipo è stato installato (facendo quello che fa di più). Quindi è necessaria la direttiva integrata. Questo comando funziona per me:

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

Per coloro che sono interessati, nessuna delle metodologie di cui sopra funziona se si desidera rilevare una libreria installata. Immagino che tu sia rimasto con il controllo fisico del percorso (potenzialmente per i file header e così via), o qualcosa del genere (se sei su una distribuzione basata su Debian):

dpkg --status libdb-dev | grep -q not-installed

if [ $? -eq 0 ]; then
    apt-get install libdb-dev
fi

Come si può vedere da quanto sopra, una risposta "0" dalla query indica che il pacchetto non è installato. Questa è una funzione di "grep" - uno "0" significa che è stata trovata una corrispondenza, un "1" significa che non è stata trovata alcuna corrispondenza.


Per usare l' hash , come suggerisce @lhunath , in uno script bash:

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

Questo script esegue l' hash e quindi controlla se il codice di uscita del comando più recente, il valore memorizzato in $? , è uguale a 1 . Se l' hash non trova foo , il codice di uscita sarà 1 . Se è presente foo , il codice di uscita sarà 0 .

&> /dev/null reindirizza l'errore standard e l'output standard hash modo che non appaia sullo schermo e echo >&2 scriva il messaggio sull'errore standard.


Perché non usare i builtin di Bash se puoi?

which programname

...

type -P programname

Risposta e spiegazione fantastiche di @lhunath. Ho salvato la mia giornata. L'ho esteso un po '. Non potevo controllarmi condividendola, sperando che potesse essere utile per qualcuno. Se qualcuno ha bisogno di controllare (una serie di) più programmi, ecco lo snippet veloce.

Che cosa sta facendo? (1) Leggi array di programmi. (2) Mostra messaggio per programma fallito. (3) Richiedere all'utente di continuare (forzare il ciclo) le opzioni y / n per la convalida del resto dei programmi.

#!/bin/bash

proginstalldir=/full/dir/path/of/installation
progsbindir=$proginstalldir/bin
echo -e "\nMy install directory - $proginstalldir"
echo -e "My binaries directory - $progsbindir"

VerifyInstall () {
clear
myprogs=( program1 program2 program3 program4 program5 programn ); 
echo -e "\nValidation of my programs started...."
for ((i=0; i<${#myprogs[@]}; i++)) ; do 
command -v $progsbindir/${myprogs[i]} >/dev/null && echo -e "Validating....\t${myprogs[i]}\tSUCCESSFUL"  || { echo -e "Validating.... \t${myprogs[i]}\tFAILED" >&2;
while true; do 
printf "%s:  "  "ERROR.... Validation FAILED for ${myprogs[i]} !!!! Continue?"; read yn; 
case $yn in [Yy] )  echo -e "Please wait..." ; break;;
[Nn]) echo -e "\n\n#################################\n##   Validation Failed .. !!   ##\n#################################\n\n" ; exit 1; break;;
*) echo -e "\nPlease answer y or n then press Enter\n"; esac; done; >&2; }; done
sleep 2
}

VerifyInstall

Se non è disponibile alcun comando di type esterno (come dato per scontato here ), possiamo usare env -i sh -c 'type cmd 1>/dev/null 2>&1' compatibile 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

Almeno su Mac OS X 10.6.8 usando il command -v ls Bash 4.2.24 (2) command -v ls non corrisponde a spostato /bin/ls-temp .


Se voi ragazzi non potete ottenere le cose sopra / sotto per lavorare e tirare fuori i capelli dalla vostra schiena, provate a eseguire lo stesso comando usando bash -c . Basta guardare questo delirio somnambular, questo è ciò che realmente accade quando si esegue $ (sottocomando):

Primo. Può darti risultati completamente diversi.

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

Secondo. Non può dare alcun risultato.

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

Sono d'accordo con lhunath per scoraggiare l'uso del which e la sua soluzione è perfettamente valida per gli utenti di BASH . Tuttavia, per essere più portabile, deve essere usato il command -v invece:

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

Il comando command è conforme a POSIX, vedere qui per le sue specifiche: http://pubs.opengroup.org/onlinepubs/9699919799/utilities/command.html

Nota: type è conforme a POSIX, ma non lo è type -P .


copione

#!/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"'

Risultato

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

il comando -v funziona bene se l'opzione POSIX_BUILTINS è impostata per il <command> da verificare ma può fallire se non lo è. (Ha funzionato per anni ma recentemente ne ho trovato uno in cui non funzionava).

Trovo che quanto segue sia più a prova di errore:

test -x $(which <command>)

Dal momento che prova per 3 cose: percorso, esecuzione e permesso.


hash foo 2>/dev/null : funziona con zsh, bash, dash e ash.

type -p foo : sembra funzionare con zsh, bash e ash (busybox), ma non dash (interpreta -p come argomento).

command -v foo : funziona con zsh, bash, dash, ma non ash (busybox) ( -ash: command: not found ).

Si noti inoltre che il builtin non è disponibile con ash e dash .


Verifica la presenza di dipendenze multiple e informa lo stato sugli utenti finali

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

Uscita di esempio:

latex     OK
pandoc    missing

Regola il 10 alla lunghezza massima del comando. Non automatico perché non vedo un modo POSIX non verboso per farlo: come allineare le colonne di una tabella separata da spazio in Bash?







bash