Qual è la differenza tra shell incorporato e parola chiave shell?


Risposte:


45

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 /bino /usr/binche 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 timeun 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ò timela pipeline completa, compresi i reindirizzamenti! Se timefosse 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.


2
D'accordo con @JohnyTex, questa è una delle risposte più complete e appropriate che ho visto sui siti Stack. Grazie. Una domanda forse non collegati: solo per curiosità che sto cercando di trovare la documentazione per il 'disabilitare temporaneamente alias' funzionalità dal precedente un comando con =\'utilizzando man, apropose helped io non ho avuto fortuna. Qualche idea su dove andrei a cercare queste informazioni? Soprattutto in modo che in futuro possa vedere cos'altro c'è dentro, perché penso che mi manca una fonte di riferimento.
nc.

@nc: non lo troverai documentato esplicitamente. Il motivo per cui funziona è spiegato in questa risposta. Il più vicino che troverai è nel manuale di riferimento nella sezione Funzionamento della shell . Vedrai che l'espansione dell'alias viene eseguita molto presto (cosa che ho cercato di enfatizzare in questa risposta), nel passaggio 2. La rimozione della citazione, l'espansione dei parametri, il globbing, ecc. Vengono eseguite in seguito. Pertanto, per disabilitare un alias, è possibile utilizzare un tipo di quotazione per impedire alla shell di comprendere un token come alias, ad esempio \ll, "ll"o 'll'.
gniourf_gniourf,

1
In realtà ho ricevuto l'oggetto dell'ordine, gli alias e le parole chiave che venivano licenziati si verificano per primi. Dato che citiamo il [[rendimento \[[, non viene analizzato come alias. Giusto finora? Il punto in cui mi sono perso non mi rendevo conto che la barra rovesciata era una cosa che citava in Bash, e ho provato a cercarlo con alias e parole chiave e mi sono perso completamente ... Tutto bene ora. Nella sezione Virgolette:> Una barra rovesciata non quotata () è il carattere di escape.
nc.

1
Quindi possiamo pensare a (2) in \[[quell'elenco Op come tokenizzazione, ed è un singolo token quello di tipo LiteralQuoteToken, al contrario del [[quale sarà un OpenTestKeywordToken, e che richiede un ]]CloseTestKeywordToken di chiusura per compilazione oroper / sintassi corretta. Più tardi, in (4) LiteralQuoteToken verrà valutato [[come il nome del comando / comando da eseguire, e in (6) Bash apparirà perché non ci sono [[comandi o comandi integrati. Bello. Potrei dimenticare i dettagli precisi nel tempo, ma in questo momento il modo in cui Bash gestisce le cose è molto più chiaro per me; grazie.
nc.

9

man bashli chiama SHELL BUILTIN COMMANDS. Quindi, un "shell builtin" è proprio come un normale comando, come grep, ecc., Ma invece di essere contenuto in un file separato, è incorporato in bash stesso . Ciò li rende più efficienti dei comandi esterni.

Una parola chiave è anche "codificata in Bash, ma a differenza di un built-in, una parola chiave non è di per sé un comando, ma una subunità di un costrutto di comando". Interpreto questo per indicare che le parole chiave non hanno una sola funzione, ma richiedono comandi per fare qualsiasi cosa. (Dal link, altri esempi sono for, while, do, e !, e ci sono più in mia risposta alla tua domanda di altri.)


1
Fatto interessante: [[ is a shell keywordma [ is a shell builtin. Non ho idea del perché.
Sparhawk,

1
È probabile che per ragioni storiche e standard POSIX siano conformi alla vecchia shell Bourne il più vicino possibile, dal momento che [esisteva come comando separato nel corso della giornata. [[non è specificato dallo standard, quindi gli sviluppatori possono scegliere di includerlo come parola chiave o come incorporato.
Sergiy Kolodyazhnyy,

1

Il manuale della riga di comando fornito con Ubuntu non fornisce una definizione di parole chiave, tuttavia il manuale online (vedi sidenote) e le specifiche standard del linguaggio di comando POSIX Shell , si riferiscono a queste come "Parole riservate" ed entrambi forniscono un elenco di queste. Dallo standard POSIX:

Questo riconoscimento deve avvenire solo quando nessuno dei caratteri è citato e quando la parola è usata come:

  • La prima parola di un comando

  • La prima parola che segue una delle parole riservate diversa da case, for o in

  • La terza parola in un comando case (solo in è valida in questo caso)

  • La terza parola in un comando for (solo in e do sono validi in questo caso)

La chiave qui è che le parole chiave / parole riservate hanno un significato speciale perché facilitano la sintassi della shell, servono a segnalare determinati blocchi di codice come loop, comandi composti, istruzioni di ramificazione (if / case), ecc. Consentono di formare istruzioni di comando, ma da soli - non fare nulla, e infatti se si immette parole chiave quali for, until, case- il guscio si aspetta una dichiarazione completa, altrimenti - errore di sintassi:

$ for
bash: syntax error near unexpected token `newline'
$  

A livello di codice sorgente, le parole riservate per bash sono definite in parese.y , mentre i built-in hanno un'intera directory a loro dedicata.

Nota a margine

L'indice GNU mostra [come parola riservata, tuttavia è in realtà un comando incorporato. [[al contrario è una parola riservata.

Vedi anche: Differenze tra parola chiave, parola riservata e built-in?

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.