Come verificherei l'esistenza di un programma, in modo da restituire un errore e uscire o continuare con lo script?
Sembra che dovrebbe essere facile, ma mi ha stordito.
Come verificherei l'esistenza di un programma, in modo da restituire un errore e uscire o continuare con lo script?
Sembra che dovrebbe essere facile, ma mi ha stordito.
Risposte:
POSIX compatibile:
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
Evitare which
. Non solo è un processo esterno si stai lanciando per fare molto poco (builtins significato piace hash
, type
o command
sono il modo più economico), si può anche fare affidamento sui comandi incorporati effettivamente fare quello che vuoi, mentre gli effetti di comandi esterni possono facilmente variare da da sistema a sistema.
Perché preoccuparsene?
which
che non imposta nemmeno uno stato di uscita , il che significa if which foo
che non funzionerà nemmeno lì e segnalerà sempre che foo
esiste, anche se non lo fa (si noti che alcune shell POSIX sembrano farlo hash
anche per questo ).which
cose personalizzate e malvagie come cambiare l'output o persino agganciarsi al gestore dei pacchetti.Quindi, non usare which
. Invece usa 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; }
(Nota a margine minore: alcuni suggeriranno che 2>&-
è lo stesso 2>/dev/null
ma più breve - questo non è vero . 2>&-
Chiude FD 2 che causa un errore nel programma quando tenta di scrivere su stderr, che è molto diverso dal scrivere con successo su di esso e scartare l'output (e pericoloso!))
Se il tuo hash bang è /bin/sh
allora dovresti preoccuparti di ciò che dice POSIX. type
e hash
i codici di uscita non sono terribilmente ben definiti da POSIX, ed hash
è visto per uscire con successo quando il comando non esiste (non l'ho ancora visto type
). command
Lo stato di uscita è ben definito da POSIX, quindi è probabilmente il più sicuro da usare.
Se lo script utilizza bash
, tuttavia, le regole POSIX non contano più ed entrambe type
e hash
diventano perfettamente sicure da usare. type
ora ha una -P
ricerca solo PATH
e hash
ha l'effetto collaterale che la posizione del comando sarà sottoposta a hash (per una ricerca più veloce la prossima volta che la usi), che di solito è una buona cosa poiché probabilmente verifichi la sua esistenza per usarla effettivamente .
Come semplice esempio, ecco una funzione che viene eseguita gdate
se esiste, altrimenti date
:
gnudate() {
if hash gdate 2>/dev/null; then
gdate "$@"
else
date "$@"
fi
}
2>&-
("chiudi il descrittore di file di output 2", che è stderr) ha lo stesso risultato di 2> /dev/null
; 2) >&2
è una scorciatoia per 1>&2
, che potresti riconoscere come "reindirizzare stdout a stderr". Consulta la pagina di reindirizzamento i / o della Guida avanzata agli script Bash per ulteriori informazioni.
while read element ; do .. done <<< $(echo ${ArrayVar[*]})
, for word in $(fgrep -l $ORIGINAL *.txt)
, ls -l "$directory" | sed 1d
, {{for a in seq $BEGIN $END
}}, ... Molti hanno tentato di contattare gli autori e proporre miglioramenti ma non è un wiki e le richieste sono sbarcati nel vuoto.
2>&-
è lo stesso di . Il primo chiude il descrittore di file, mentre il secondo lo reindirizza semplicemente a . Potresti non visualizzare un errore perché il programma tenta di informarti su stderr che stderr è chiuso. 2>/dev/null
/dev/null
Di seguito è riportato un modo portatile per verificare se esiste un comando $PATH
ed è eseguibile:
[ -x "$(command -v foo)" ]
Esempio:
if ! [ -x "$(command -v git)" ]; then
echo 'Error: git is not installed.' >&2
exit 1
fi
Il controllo eseguibile è necessario perché bash restituisce un file non eseguibile se non viene trovato alcun file eseguibile con quel nome $PATH
.
Si noti inoltre che se in precedenza esiste un file non eseguibile con lo stesso nome dell'eseguibile $PATH
, trattino restituisce il primo, anche se quest'ultimo verrà eseguito. Questo è un bug e viola lo standard POSIX. [ Segnalazione bug ] [ Standard ]
Inoltre, ciò fallirà se il comando che stai cercando è stato definito come alias.
command -v
un percorso anche per un file non eseguibile? Cioè, la -x è davvero necessaria?
-x
verifica che il file sia eseguibile, qual è la domanda.
command
metterà alla prova se stesso eseguibile - non è vero?
$PATH
durante l'esecuzione di un comando. Tuttavia, il comportamento di command -v
è molto incoerente. Nel trattino, restituisce il primo file corrispondente $PATH
, indipendentemente dal fatto che sia eseguibile o meno. In bash, restituisce la prima corrispondenza eseguibile $PATH
, ma se non ce ne sono, può restituire un file non eseguibile. E in zsh, non restituirà mai un file non eseguibile.
dash
è l'unico su quei tre che non è conforme a POSIX; [ -x "$(command -v COMMANDNAME)"]
funzionerà negli altri due. Sembra che questo errore sia già stato segnalato ma non abbia ancora ricevuto risposta: bugs.debian.org/cgi-bin/bugreport.cgi?bug=874264
Concordo con lhunath per scoraggiare l'uso which
e la sua soluzione è perfettamente valida per gli utenti di Bash . Tuttavia, per essere più portatile, command -v
deve essere utilizzato 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. Vedi qui per le sue specifiche: comando - esegue un comando semplice
Nota: type
è conforme POSIX, ma type -P
non lo è.
exit 1;
uccide un xterm, se invocato da lì.
&>/dev/null
. Tuttavia, sono d'accordo con te, ciò che conta davvero è la portabilità, ho modificato la mia risposta di conseguenza, ora usando il reindirizzamento sh standard >/dev/null 2>&1
.
Ho una funzione definita nel mio .bashrc che rende tutto più semplice.
command_exists () {
type "$1" &> /dev/null ;
}
Ecco un esempio di come viene utilizzato (dal mio .bash_profile
.)
if command_exists mvim ; then
export VISUAL="mvim --nofork"
fi
&>
fa?
&>
reindirizzamenti sono entrambi stdout e stderr insieme.
&>
potrebbe non essere disponibile nella tua versione di Bash. Il codice di Marcello dovrebbe funzionare bene; fa la stessa cosa.
then
ad esempio con la parola . Vedi questa risposta se richiedi l'esistenza del file eseguibile $PATH
.
Dipende se vuoi sapere se esiste in una delle directory nella $PATH
variabile o se conosci la sua posizione assoluta. Se vuoi sapere se è nella $PATH
variabile, usa
if which programname >/dev/null; then
echo exists
else
echo does not exist
fi
altrimenti usa
if [ -x /path/to/programname ]; then
echo exists
else
echo does not exist
fi
Il reindirizzamento /dev/null/
nel primo esempio elimina l'output del which
programma.
Espandendo le risposte di @lhunath e @ GregV, ecco il codice per le persone che vogliono inserire facilmente quel controllo all'interno di una if
dichiarazione:
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
command
successo anche per gli alias, che potrebbe essere in qualche modo controintuitivo. Il controllo dell'esistenza in una shell interattiva darà risultati diversi da quando lo si sposta in uno script.
shopt -u expand_aliases
ignora / nasconde gli alias (come quello alias ls='ls -F'
menzionato in un'altra risposta) e shopt -s expand_aliases
li risolve tramite command -v
. Quindi forse dovrebbe essere impostato prima del controllo e non impostato dopo, anche se potrebbe influire sul valore di ritorno della funzione se non si acquisisce e restituisce l'output della chiamata di comando in modo esplicito.
Prova a usare:
test -x filename
o
[ -x filename ]
Dalla manpage di Bash in Espressioni condizionali :
-x file True if file exists and is executable.
Per usare hash
, come suggerisce @lhunath , in uno script Bash:
hash foo &> /dev/null
if [ $? -eq 1 ]; then
echo >&2 "foo not found."
fi
Questo script viene eseguito hash
e quindi controlla se il codice di uscita del comando più recente, il valore archiviato $?
, è uguale a 1
. In caso hash
contrario foo
, il codice di uscita sarà 1
. Se foo
presente, il codice di uscita sarà 0
.
&> /dev/null
reindirizza l'errore standard e l'output standard in hash
modo che non appaia sullo schermo e echo >&2
scriva il messaggio nell'errore standard.
if hash foo &> /dev/null; then ...
?
Non ho mai avuto le risposte precedenti per lavorare sulla casella a cui ho accesso. Per uno, type
è stato installato (facendo quello che more
fa). Quindi è necessaria la direttiva integrata. Questo comando funziona per me:
if [ `builtin type -p vim` ]; then echo "TRUE"; else echo "FALSE"; fi
if
sintassi, basta usare if builtin type -p vim; then ...
. E i backtick sono sintassi molto antiche e deprecate, $()
sono supportati anche da sh
tutti i sistemi moderni.
Verificare la presenza di più dipendenze e informare lo stato degli utenti finali
for cmd in latex pandoc; do
printf '%-10s' "$cmd"
if hash "$cmd" 2>/dev/null; then
echo OK
else
echo missing
fi
done
Uscita campione:
latex OK
pandoc missing
Regolare la 10
lunghezza massima del comando. Non è automatico, perché non vedo un modo POSIX non dettagliato per farlo:
come posso allineare le colonne di una tabella separata dallo spazio in Bash?
Controllare se alcuni apt
pacchetti sono installati con dpkg -s
e installarli altrimenti .
Vedi: Controlla se è installato un pacchetto apt-get e installalo se non è su Linux
È stato precedentemente menzionato in: Come posso verificare se esiste un programma da uno script Bash?
column -t
(parte di util-linux).
Se controlli l'esistenza del programma, probabilmente lo eseguirai in seguito comunque. Perché non provare a eseguirlo in primo luogo?
if foo --version >/dev/null 2>&1; then
echo Found
else
echo Not found
fi
È un controllo più affidabile che il programma viene eseguito piuttosto che guardare semplicemente le directory PATH e le autorizzazioni dei file.
Inoltre puoi ottenere alcuni risultati utili dal tuo programma, come la sua versione.
Naturalmente gli svantaggi sono che alcuni programmi possono essere pesanti da avviare e alcuni non hanno --version
un'opzione per uscire immediatamente (e con successo).
hash foo 2>/dev/null
: funziona con Z shell (Zsh), Bash, Dash e Ash .
type -p foo
: sembra funzionare con Z shell, Bash e ash ( BusyBox ), ma non Dash (interpreta -p
come argomento).
command -v foo
: funziona con Z shell, Bash, Dash, ma non ash (BusyBox) ( -ash: command: not found
).
Si noti inoltre che builtin
non è disponibile con ash e Dash.
Usa i built-in di Bash se puoi:
which programname
...
type -P programname
which
non è incorporato in Bash.
-P
non è POSIX. Perché è type -P
preferito?
Il comando -v
funziona correttamente se l'opzione POSIX_BUILTINS è impostata per<command>
test per, ma può non riuscire in caso contrario. (Ha funzionato per me per anni, ma di recente mi sono imbattuto in uno in cui non ha funzionato.)
Trovo che quanto segue sia più a prova di errore:
test -x $(which <command>)
Dal momento che verifica tre cose: percorso, esistenza e permesso di esecuzione.
test -x $(which ls)
restituisce 0, come fa test -x $(which sudo)
, anche se ls
è installato e eseguibile e sudo
non è nemmeno installato nel contenitore docker in cui sto eseguendo.
test -x "$(which <command>)"
ls
è aliasato ? Non penso che funzionerebbe se il comando avesse un parametro.
Per gli interessati, nessuna delle metodologie nelle risposte precedenti funziona se si desidera rilevare una libreria installata. Immagino che ti rimanga con il controllo fisico del percorso (potenzialmente per i file di intestazione e simili), 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 puoi 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.
cmd; if [ $? -eq 0 ]; then
dovrebbe essere refactored aif cmd; then
dpkg
oapt
Ci sono un sacco di opzioni qui, ma sono rimasto sorpreso senza una battuta veloce. 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; }
Questo si basa sulla risposta selezionata qui e su un'altra fonte.
Direi che non esiste alcun modo portatile e affidabile al 100% a causa di penzoloni alias
. Per esempio:
alias john='ls --color'
alias paul='george -F'
alias george='ls -h'
alias ringo=/
Certo, solo l'ultimo è problematico (senza offesa per Ringo!). Ma tutti sono validi alias
dal punto di vista di command -v
.
Per respingere quelli penzolanti come ringo
, dobbiamo analizzare l'output del alias
comando integrato della shell e ricorrere in essi ( command -v
non è un superiore a alias
qui.) Non esiste alcuna soluzione portatile per esso, e nemmeno un Bash- la soluzione specifica è piuttosto noiosa.
Si noti che una soluzione come questa rifiuterà incondizionatamente alias ls='ls -F'
:
test() { command -v $1 | grep -qv alias }
shopt -u expand_aliases
ignora / nasconde questi alias e shopt -s expand_aliases
li mostra tramite command -v
.
Questo dirà in base alla posizione se il programma esiste o no:
if [ -x /usr/bin/yum ]; then
echo "This is Centos"
fi
Il which
comando potrebbe essere utile.uomo che
Restituisce 0 se viene trovato l'eseguibile e restituisce 1 se non viene 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 executable
2 if an invalid option is specified
La cosa bella which
è che capisce se l'eseguibile è disponibile nell'ambiente in cui which
viene eseguito - salva alcuni problemi ...
La mia configurazione per un Debian server :
Ho avuto il problema quando più pacchetti contenevano lo stesso nome.
Per esempio apache2
. Quindi questa era la mia soluzione:
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
}
Se voi / ragazze non riuscite a far funzionare le cose nelle risposte qui e vi state strappando i capelli dalla schiena, provate a eseguire lo stesso comando usando bash -c
. Guarda questo delirio sonnambulo. Questo è ciò che accade realmente quando si esegue $ (comando secondario):
Primo. Può darti un output completamente diverso.
$ command -v ls
alias ls='ls --color=auto'
$ bash -c "command -v ls"
/bin/ls
Secondo. Non può darti alcun risultato.
$ command -v nvm
nvm
$ bash -c "command -v nvm"
$ bash -c "nvm --help"
bash: nvm: command not found
.bashrc
ho [ -z "$PS1" ] && return
anteposto # If not running interactively, don't do anything
quindi immagino che sia una ragione per cui anche l'approvvigionamento esplicito di bashrc in modalità non interattiva non aiuta. Il problema può essere risolto chiamando uno script con un operatore punto ss64.com/bash/source.html , . ./script.sh
ma non è una cosa che vorresti ricordare di digitare ogni volta.
La variante hash ha una trappola: nella riga di comando è possibile ad esempio digitare
one_folder/process
per eseguire il processo. Per questo la cartella principale di one_folder deve essere in $ PATH . Ma quando provi a eseguire l'hashing di questo comando, riuscirà sempre:
hash one_folder/process; echo $? # will always output '0'
$PATH
" - Questo è completamente inaccurato. Provalo. Perché ciò funzioni, one_folder deve trovarsi nella directory corrente .
In secondo luogo 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
Ho dovuto verificare se Git era installato come parte della distribuzione del nostro server CI . Il mio script finale di Bash era il seguente (server Ubuntu):
if ! builtin type -p git &>/dev/null; then
sudo apt-get -y install git-core
fi
sudo
: senza il condizionale, si fermerebbe sempre e chiederebbe la password (a meno che non abbia fatto un sudo di recente). A proposito, può essere utile farlo in sudo -p "Type your password to install missing git-core: "
modo che il prompt non venga fuori dal nulla.
Per imitare Bash type -P cmd
, possiamo usare il POSIX conforme 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; }
type
sembra essere un builtin
nella maggior parte delle shell, quindi questo non può funzionare perché env
usa execvp
per essere eseguito, command
quindi command
non può essere un builtin
(e builtin
verrà sempre eseguito nello stesso ambiente). Questo non per me in bash
, ksh93
, zsh
, busybox [a]sh
e dash
ognuno dei quali forniscono type
come un builtin di shell.
Se non è type
disponibile alcun comando esterno (come dato scontato qui ), possiamo utilizzare POSIX conforme 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 v10.6.8 (Snow Leopard) utilizzando Bash 4.2.24 (2) command -v ls
non corrisponde a uno spostamento /bin/ls-temp
.
Nel caso in cui si desideri verificare se esiste un programma ed è davvero un programma, non un comando incorporato di Bash , quindi command
, type
e hash
non sono appropriati per il test in quanto restituiscono tutti 0 stato di uscita per i comandi integrati.
Ad esempio, esiste il programma orario che offre più funzioni rispetto al comando incorporato time . Per verificare se il programma esiste, suggerirei di utilizzare which
come nell'esempio seguente:
# 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
Volevo che rispondesse alla stessa domanda ma che fosse eseguita in un Makefile.
install:
@if [[ ! -x "$(shell command -v ghead)" ]]; then \
echo 'ghead does not exist. Please install it.'; \
exit -1; \
fi
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.
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
Utilizza lo stato dell'eco dei programmi incorporati della shell e dell'output standard e nulla per l'errore standard. D'altra parte, se non viene trovato un comando, viene visualizzato lo stato solo l'errore standard.
which
ritorna vero per questi.type
senza argomenti restituirà inoltre true per le parole riservate e i builtin della shell. Se "programma" significa "eseguibile$PATH
", vedere questa risposta .