Comando command in Bash: saltare funzioni e verificare i nomi

command esegue un comando saltando la ricerca delle funzioni della shell: può scegliere un builtin, cioè un comando interno, oppure un programma trovato nel PATH. Con command -v potete invece verificare come la shell corrente risolve un nome, senza eseguirlo.

Questa guida usa Bash e distingue esecuzione, ricerca sintetica con -v, descrizione dettagliata con -V e percorso predefinito con -p. Il nucleo di queste opzioni è definito anche da POSIX, lo standard per interfacce dei sistemi di tipo Unix; formati e dettagli possono variare tra shell.

Che cos’è command nella shell?

Una funzione può avere lo stesso nome di un programma. Se al suo interno richiamate quel nome senza precauzioni, potete richiamare la funzione stessa e creare una ricorsione. command NOME salta la ricerca delle funzioni e cerca un builtin o un eseguibile nel percorso della shell.

command è esso stesso un builtin, non una normale funzione definita nello script. Il suo effetto riguarda il comando che segue: non cambia le regole degli altri comandi di una pipeline e non sostituisce la shell con un nuovo processo come farebbe exec.

Sintassi di base

command [-pVv] command [arguments...]

Senza -v o -V, viene eseguito il comando indicato. Nella sintassi, il secondo command e [arguments...] sono segnaposto da sostituire con nome e argomenti; non vanno copiati letteralmente.

command ls -la
command printf '%s\n' 'ciao'

Il primo comando elenca la directory corrente, compresi i file nascosti; il secondo stampa ciao. Se non esiste una funzione omonima, il risultato può sembrare identico all’esecuzione diretta.

Saltare una funzione senza richiamarla ricorsivamente

Aprite una nuova sessione Bash di prova per non sostituire una vostra funzione ls. L’esempio definisce una funzione che stampa un messaggio e passa gli argomenti al programma ls, mostra la directory corrente e infine rimuove la funzione. Richiede GNU ls, perché usa l’opzione --color=auto; altri ls possono usare opzioni diverse.

ls() {
  printf '%s\n' 'Elenco della directory:'
  command ls --color=auto "$@"
}
ls -ld -- .
unset -f ls

Dentro la funzione, ls "$@" richiamerebbe la stessa funzione. Con command ls la ricerca delle funzioni viene saltata: nel caso usuale di ls, viene eseguito il programma trovato nel PATH. Per nomi come printf, può invece essere scelto il builtin.

"$@" conserva un argomento per ciascun valore ricevuto, inclusi spazi e caratteri jolly letterali. Non sostituitelo con $* o $@ senza apici. Per esempio, un nome di file con spazi deve arrivare a ls come un solo argomento.

Controllare un nome con command -v

-v descrive in forma sintetica la risoluzione del nome nella shell corrente e restituisce successo se lo trova. Non avvia il comando. Usate lo stato di uscita per un controllo automatico, tenendo distinto il riconoscimento di un nome dal corretto funzionamento del programma.

command -v git
command -v cd
command -v printf

L’output può essere un percorso, il nome di un builtin o di una funzione, una parola riservata o la definizione di un alias. Non assumete che sia sempre un file. Salvate il blocco seguente in verifica-curl.sh ed eseguite bash verifica-curl.sh: controlla soltanto se la shell riconosce curl, anche come funzione o alias attivo.

if command -v curl >/dev/null 2>&1; then
  printf '%s\n' 'La shell riconosce il nome curl.'
else
  printf '%s\n' 'Nome curl non trovato.' >&2
  exit 1
fi

Leggere una descrizione con command -V

-V fornisce una descrizione più dettagliata, destinata alla lettura da parte di una persona. Gli esempi seguenti mostrano come vengono interpretati ls, cd e printf:

command -V ls
command -V cd
command -V printf

Non analizzate automaticamente le frasi di -V, che possono cambiare con shell e lingua. Per controllare un nome negli script, usate lo stato restituito da command -v. Una nuova shell non ha necessariamente gli stessi alias e le stesse funzioni della sessione interattiva.

command -p e il percorso di ricerca predefinito

-p usa per la ricerca un valore predefinito di PATH capace di trovare le utilità standard. Il primo comando mostra quel percorso tramite getconf; il secondo esegue ls usando la ricerca predefinita. Non rende permanente una modifica al vostro PATH.

command -p getconf PATH
command -p ls

Il percorso predefinito dipende dal sistema: -p non fissa una versione o un fornitore specifico e non disattiva tutti i builtin. Se uno script richiede un programma preciso, occorre verificare e controllare il percorso da usare.

Opzioni di command

Opzione Descrizione
-v Descrive sinteticamente la risoluzione di un nome, senza eseguirlo.
-V Descrive la risoluzione di un nome in forma più dettagliata, senza eseguirlo.
-p Usa per la ricerca il percorso predefinito delle utilità standard.

Capire l’espansione degli alias

Bash espande gli alias mentre legge il comando. Nella forma normale command ls, il nome ls non occupa la posizione iniziale del comando e non viene trattato come alias. Questo presuppone che command non sia stato a sua volta ridefinito: per esempio, un alias il cui valore termina con uno spazio può indurre Bash a controllare anche la parola successiva per l’espansione degli alias.

Per vedere le definizioni sovrapposte, consultate prima type -a e confrontate il risultato con command -V:

type -a ls
command -V ls

Il backslash davanti a ls impedisce l’espansione di quell’alias, ma non salta una funzione con lo stesso nome. Un percorso assoluto identifica invece il file da eseguire:

\ls
/usr/bin/ls

/usr/bin/ls è un percorso comune su Linux, da verificare sul sistema interessato prima di usarlo. In Bash potete cercare candidati esterni con type -P ls; ricordate che un percorso memorizzato nella tabella hash può influire sul risultato.

Confronto con builtin, exec, env e type

Comando Ruoli principali
command name Salta le funzioni e avvia un builtin o un programma nel PATH.
builtin name Esegue esplicitamente un builtin di Bash.
exec name Sostituisce la shell con un programma; se riesce, non torna alla shell originale.
env VAR=value name Avvia un programma esterno con le variabili di ambiente indicate.
type name Descrive come Bash interpreta un nome senza eseguirlo.

Per richiamare esplicitamente un builtin di Bash usate builtin NOME. Per saltare una funzione consentendo sia builtin sia eseguibili esterni usate command NOME. Quest’ultimo non permette di richiamare la funzione omonima: serve proprio a escluderla dalla ricerca.

Usare i controlli negli script

Controllare lo stato senza analizzare il testo

if ! command -v rsync >/dev/null 2>&1; then
  printf '%s\n' 'Nome rsync richiesto ma non trovato.' >&2
  exit 1
fi
printf '%s\n' 'La shell riconosce il nome rsync.'

Salvate questo controllo in uno script o inseritelo all’inizio di uno script che richiede il nome rsync. Se non viene trovato, il messaggio va allo standard error e lo script termina con stato 1; altrimenti prosegue e stampa il messaggio di riconoscimento. La redirezione >/dev/null 2>&1 nasconde entrambi i flussi del controllo. Per richiedere un vero file esterno, applicate anche il criterio descritto nel paragrafo successivo.

Richiedere un file eseguibile in Bash

command -v non è un selettore di soli eseguibili esterni. In Bash, cercate esplicitamente un file con type -P e poi controllate che sia un file regolare e che il processo possa eseguirlo. Salvate questo esempio completo in trova-eseguibile.sh ed eseguite bash trova-eseguibile.sh ls. Passate un solo nome non vuoto.

if [ "$#" -ne 1 ] || [ -z "$1" ]; then
  printf '%s\n' 'Uso: bash trova-eseguibile.sh NOME' >&2
  exit 2
fi

if resolved_path=$(type -P -- "$1") &&
   [ -f "$resolved_path" ] && [ -x "$resolved_path" ]; then
  printf 'File eseguibile: %s\n' "$resolved_path"
else
  printf 'File eseguibile non trovato: %s\n' "$1" >&2
  exit 1
fi

Il controllo stampa il percorso se soddisfa le condizioni e termina con successo; in caso contrario stampa un messaggio e restituisce 1. Gli argomenti mancanti o in eccesso producono stato 2. Questa soluzione usa type -P di Bash, non è un esempio portabile a tutte le shell POSIX e può restituire un percorso relativo se il PATH contiene componenti relativi.

Capire il limite TOCTOU

TOCTOU significa Time Of Check To Time Of Use: tra il controllo e l’uso, un file, i suoi permessi o il PATH possono cambiare. Le verifiche precedenti sono utili per diagnosi e messaggi agli utenti, ma non bloccano il file né garantiscono che una successiva esecuzione riesca. Quando serve un programma preciso, controllate anche directory, permessi e percorso; gestite comunque gli errori al momento dell’avvio.

Stato di uscita

In esecuzione, command restituisce lo stato del comando avviato. La mancata individuazione del comando produce normalmente 127; un’utilità trovata ma non avviabile produce 126. Gli errori d’uso e altri casi possono avere stati diversi. Con -v o -V, 0 indica che il nome è stato trovato e un valore maggiore di zero segnala assenza o errore.

if command false; then
  status=0
else
  status=$?
fi
printf 'status=%d\n' "$status"

if command does-not-exist; then
  status=0
else
  status=$?
fi
printf 'status=%d\n' "$status"

In una shell senza un comando chiamato does-not-exist, l’esempio stampa prima status=1, poi un messaggio diagnostico e status=127. I comandi sono dentro condizioni if, così potete leggerne lo stato anche con set -e attivo. Uno stato diverso da zero può essere il risultato previsto del comando chiamato, come nel caso di false.

Domande frequenti

command esegue soltanto programmi esterni?

No. Salta le funzioni, poi può eseguire un builtin oppure un programma nel PATH. Se serve un file esterno, cercatelo esplicitamente in Bash con type -P e verificatene le proprietà.

Il risultato di command -v è sempre un percorso assoluto?

No. Può indicare alias, funzioni, parole riservate o builtin oltre ai programmi esterni. Non usate il testo senza controlli come se fosse sempre un percorso: per riconoscere un nome è sufficiente lo stato di uscita.

Può essere usato nella forma di sudo command...?

command è normalmente un builtin della shell e sudo cerca un programma da avviare. Passate quindi a sudo il programma esterno richiesto. Se avete davvero bisogno di sintassi della shell con privilegi elevati, dovete scegliere esplicitamente la shell e gestire correttamente argomenti e apici; sudo command ... non è una forma portabile per richiamare un builtin.

Quando conviene command -v rispetto a which?

command -v è definito dalla shell e descrive la risoluzione nel suo ambiente corrente. which è spesso un programma esterno, con comportamenti diversi tra implementazioni, e può non conoscere gli alias e le funzioni di quella sessione.

Come posso ignorare le funzioni ed eseguire solo i comandi interni?

Usate builtin NOME in Bash. command NOME salta le funzioni, ma può scegliere sia builtin sia programmi esterni.

Le stesse opzioni possono essere utilizzate in altre shell?

-p, -v e -V hanno un nucleo definito da POSIX. Verificate comunque la documentazione della shell scelta, soprattutto per formati di output e opzioni aggiuntive. Gli esempi con type -P di questa guida richiedono Bash.

Scegliere esecuzione o ricerca

Per saltare una funzione durante l’esecuzione usate command NOME. Per verificare la risoluzione di un nome senza avviarlo scegliete command -v NOME; per una descrizione leggibile usate command -V NOME.

Confrontate la ricerca con type -a NOME quando un alias, un builtin o una funzione nasconde un programma. Eseguite la diagnosi nello stesso ambiente dello script o della sessione interessata, perché le definizioni e i percorsi possono differire.

Documentazione di riferimento