C'è una forte differenza tra un builtin e una parola chiave, nel modo in cui Bash analizza il tuo codice. Prima di parlare della differenza, elenchiamo tutte le parole chiave e i builtin:
builtins:
$ compgen -b
. : [ alias bg bind break
builtin caller cd command compgen complete compopt
continue declare dirs disown echo enable eval
exec exit export false fc fg getopts
hash help history jobs kill let local
logout mapfile popd printf pushd pwd read
readarray readonly return set shift shopt source
suspend test times trap true type typeset
ulimit umask unalias unset wait
parole chiave:
$ compgen -k
if then else elif fi case
esac for select while until do
done in function time { }
! [[ ]] coproc
Si noti che, ad esempio, [
è incorporato e che [[
è una parola chiave. Userò questi due per illustrare la differenza di seguito, poiché sono operatori ben noti: tutti li conoscono e li usano regolarmente (o dovrebbero).
Una parola chiave viene scansionata e compresa da Bash molto presto nella sua analisi. Ciò consente ad esempio quanto segue:
string_with_spaces='some spaces here'
if [[ -n $string_with_spaces ]]; then
echo "The string is non-empty"
fi
Funziona bene e Bash sarà felicemente in uscita
The string is non-empty
Nota che non ho citato $string_with_spaces
. Considerando quanto segue:
string_with_spaces='some spaces here'
if [ -n $string_with_spaces ]; then
echo "The string is non-empty"
fi
mostra che Bash non è felice:
bash: [: too many arguments
Perché funziona con le parole chiave e non con i builtin? perché quando Bash analizza il codice, vede [[
quale è una parola chiave e capisce molto presto che è speciale. Quindi cercherà la chiusura ]]
e tratterà l'interno in modo speciale. Un builtin (o comando) viene trattato come un comando effettivo che verrà chiamato con argomenti. In questo ultimo esempio, bash comprende che dovrebbe eseguire il comando[
con argomenti (mostrati uno per riga):
-n
some
spaces
here
]
poiché si verifica l'espansione variabile, la rimozione delle virgolette, l'espansione del percorso e la divisione delle parole. Il comando[
risulta essere costruito nella shell, quindi lo esegue con questi argomenti, il che si traduce in un errore, quindi nel reclamo.
In pratica, vedi che questa distinzione consente un comportamento sofisticato, cosa impossibile con builtin (o comandi).
Ancora in pratica, come puoi eventualmente distinguere un builtin da una parola chiave? questo è un esperimento divertente da eseguire:
$ a='['
$ $a -d . ]
$ echo $?
0
Quando Bash analizza la linea $a -d . ]
, non vede nulla di speciale (ovvero, nessun alias, nessun reindirizzamento, nessuna parola chiave), quindi esegue solo l'espansione variabile. Dopo le espansioni variabili, vede:
[ -d . ]
quindi esegue il comando (incorporato) [
con argomenti -d
, .
e ]
che, naturalmente è vero (questa verifica solo se.
è una directory).
Ora guarda:
$ a='[['
$ $a -d . ]]
bash: [[: command not found
Oh. Questo perché quando Bash vede questa linea, non vede nulla di speciale, quindi espande tutte le variabili e alla fine vede:
[[ -d . ]]
Al momento, le espansioni di alias e la scansione delle parole chiave sono state eseguite a lungo e non verranno più eseguite, quindi Bash cerca di trovare il comando chiamato [[
, non lo trova e si lamenta.
Sulla stessa linea:
$ '[' -d . ]
$ echo $?
0
$ '[[' -d . ]]
bash: [[: command not found
e
$ \[ -d . ]
$ echo $?
0
$ \[[ -d . ]]
bash: [[: command not found
L'espansione dell'alias è anche qualcosa di piuttosto speciale. Hai fatto quanto segue almeno una volta:
$ alias ll='ls -l'
$ ll
.... <list of files in long format> ....
$ \ll
bash: ll: command not found
$ 'll'
bash: ll: command not found
Il ragionamento è lo stesso: l'espansione dell'alias si verifica molto prima dell'espansione variabile e della rimozione delle quotazioni.
Parola chiave vs alias
Ora, cosa pensi che accada se definiamo un alias come una parola chiave?
$ alias mytest='[['
$ mytest -d . ]]
$ echo $?
0
Oh, funziona! così gli alias possono essere usati per alias parole chiave! bello sapere.
Conclusione: i builtin si comportano davvero come comandi: corrispondono a un'azione eseguita con argomenti che subiscono l'espansione variabile diretta e la suddivisione e il globbing delle parole. È proprio come avere un comando esterno da qualche parte /bin
o /usr/bin
che viene chiamato con gli argomenti forniti dopo l'espansione variabile, ecc. Nota che quando dico è proprio come avere un comando esterno intendo solo per argomenti, suddivisione di parole, globbing, espansione variabile, ecc. Un built-in può modificare lo stato interno della shell!
Le parole chiave, d'altra parte, vengono scansionate e comprese molto presto e consentono un comportamento sofisticato della shell: la shell sarà in grado di proibire la divisione delle parole o l'espansione del percorso, ecc.
Ora guarda l'elenco di builtin e parole chiave e prova a capire perché alcuni devono essere parole chiave.
!
è una parola chiave. Sembra che sarebbe possibile imitare il suo comportamento con una funzione:
not() {
if "$@"; then
return false
else
return true
fi
}
ma ciò vieterebbe costrutti come:
$ ! ! true
$ echo $?
0
o
$ ! { true; }
echo $?
1
Lo stesso per time
: è più potente avere una parola chiave in modo che possa cronometrare comandi e pipeline composti complessi con reindirizzamenti:
$ time grep '^#' ~/.bashrc | { i=0; while read -r; do printf '%4d %s\n' "$((++i))" "$REPLY"; done; } > bashrc_numbered 2>/dev/null
Se fosse time
un semplice comando (anche incorporato), vedrebbe solo gli argomenti grep
, ^#
e /home/gniourf/.bashrc
, questo tempo, e quindi il suo output passerebbe attraverso le restanti parti della pipeline. Ma con una parola chiave, Bash può gestire tutto! può time
la pipeline completa, compresi i reindirizzamenti! Se time
fosse un semplice comando, non potremmo fare:
$ time { printf 'hello '; echo world; }
Provalo:
$ \time { printf 'hello '; echo world; }
bash: syntax error near unexpected token `}'
Prova a risolverlo (?):
$ \time { printf 'hello '; echo world;
time: cannot run {: No such file or directory
Senza speranza.
Parola chiave vs alias?
$ alias mytime=time
$ alias myls=ls
$ mytime myls
Cosa pensi che accada?
In realtà, un builtin è come un comando, tranne per il fatto che è incorporato nella shell, mentre una parola chiave è qualcosa che consente un comportamento sofisticato! possiamo dire che fa parte della grammatica della shell.