2015-01-06 20 views
21

Solitamente, le funzioni bash vengono definiti utilizzando parentesi graffe per racchiudere il corpo:funzioni bash: racchiude il corpo in parentesi vs. parentesi

foo() 
{ 
    ... 
} 

Quando si lavora su uno script oggi facendo ampio uso di funzioni, I' ci sono problemi con variabili che hanno lo stesso nome nella chiamata come nella funzione chiamante, vale a dire che quelle variabili sono le stesse. Ho quindi scoperto che questo può essere evitato definendo le variabili locali all'interno della funzione come locale: local var=xyz.

Poi, ad un certo punto, ho scoperto un filo (Defining bash function body using parenthesis instead of braces), in cui si spiega che è altrettanto valida per definire una funzione usando parentesi in questo modo:

foo() 
(
    ... 
) 

L'effetto di questo è che il corpo della funzione è eseguito in una subshell, che ha il vantaggio che la funzione ha il suo ambito variabile, che mi permette di definirli senza local. Dal momento che avere una portata funzione di locale sembra avere molto più senso e di essere molto più sicuro che tutte le variabili di essere globale, ho subito chiedo:

  • Perché le parentesi graffe utilizzate per impostazione predefinita per racchiudere il corpo della funzione, invece di parentesi ?

Tuttavia, ho subito anche scoperto un grave inconveniente per l'esecuzione della funzione in una subshell, specificamente che uscendo lo script dall'interno di una funzione non funziona più, invece mi costringe a lavorare con lo stato di ritorno lungo la intero albero delle chiamate (in caso di funzioni annidate). Questo mi porta a questa domanda di follow-up:

  • Ci sono altri aspetti negativi principali (*) per utilizzare le parentesi graffe invece di (che potrebbe spiegare perché bretelle sembrano essere preferito)?

(*) Sono consapevole (dalle discussioni eccezioni legate Ho inciampato su nel corso del tempo), che alcuni sostengono che in modo esplicito utilizzando lo stato di errore è molto meglio che essere in grado di uscire da qualsiasi luogo, ma io preferisco quest'ultimo.

Apparentemente entrambi gli stili hanno i loro vantaggi e svantaggi. Quindi spero che qualcuno di voi utenti bash più esperti mi può dare qualche indicazione generale:

  • Quando userò le parentesi graffe per racchiudere il corpo della funzione, e quando è consigliabile passare alla parentesi?

EDIT: Take-away dalle risposte

Grazie per le vostre risposte, la mia testa ora è un po 'più chiaro per quanto riguarda questo. Quindi quello che prendo lontano dalle risposte è:

  • Stick per le parentesi graffe convenzionali, se non altro per non confondere i potenziali altri utenti/sviluppatori dello script (e anche usare le parentesi graffe se l'intero corpo è avvolto tra parentesi).

  • L'unico vero svantaggio delle parentesi graffe è che qualsiasi variabile nello scope genitore può essere modificata, anche se in alcune situazioni questo potrebbe essere un vantaggio. Questo può essere facilmente aggirato dichiarando le variabili come local.

  • L'utilizzo delle parentesi, d'altra parte, potrebbe avere alcuni effetti indesiderati gravi, come l'incasinamento delle uscite, che porta a problemi con l'uccisione di uno script e l'isolamento dell'ambito della variabile.

+0

La modifica è un ottimo riassunto! – fedorqui

risposta

12

Perché le parentesi graffe utilizzate per impostazione predefinita per racchiudere il corpo della funzione, invece di parentesi?

Il corpo di una funzione può essere un qualsiasi comando composto. Si tratta in genere di { list; }, ma altre tre forme di comandi composti sono tecnicamente consentite: (list), ((expression)) e [[ expression ]].

C e le lingue nella famiglia C come C++, Java, C# e JavaScript utilizzano tutte le parentesi graffe per delimitare i corpi delle funzioni.Le parentesi graffe sono la sintassi più naturale per i programmatori che hanno familiarità con quelle lingue.

Ci sono altri svantaggi principali (*) per l'utilizzo di parentesi anziché di parentesi graffe (che potrebbe spiegare perché le parentesi sembrano essere preferite)?

Sì. Esistono numerose cose che non è possibile eseguire da una sotto shell, tra cui:

  • Modificare le variabili globali. Le modifiche alle variabili non si propagheranno alla shell madre.
  • Uscire dallo script. Un'istruzione exit uscirà solo dalla sotto-shell.

L'avvio di un sub-shell può anche essere un grave problema di prestazioni. Stai lanciando un nuovo processo ogni volta che chiami la funzione.

Si potrebbe anche ottenere un comportamento strano se il tuo copione viene ucciso. I segnali ricevuti dalla shell genitore e figlio cambieranno. È un effetto sottile, ma se hai gestori trap o lo kill script le parti non funzionano nel modo desiderato.

Quando utilizzare le parentesi graffe per racchiudere il corpo della funzione e quando è consigliabile passare alle parentesi?

Consiglierei di utilizzare sempre le parentesi graffe. Se vuoi una sub-shell esplicita, aggiungi un insieme di parentesi all'interno delle parentesi graffe. Usare solo le parentesi è una sintassi molto insolita e confonderebbe molte persone che leggono il tuo script.

foo() { 
    (
     subshell commands; 
    ) 
} 
+0

Anche se ho prima preso in considerazione @ kojiro's, accetterò la tua risposta dato che hai dato esplicitamente un rispondi a tutte e tre le domande, inoltre hai sollevato due punti molto buoni con il comportamento della diga se lo script è stato ucciso (non ci avrei pensato) e che usare parentesi senza parentesi sarebbe stato troppo sintetico. – flotzilla

+0

Ma vedo ancora la possibilità di modificare le variabili globali immediatamente, ma dover dichiarare le variabili locali esplicitamente come più di un pericolo che un plus. Preferisco di gran lunga che le variabili siano locali per impostazione predefinita, ma in grado di dichiararle come globali in modo da poterle comunque accedere. Ma prenderò l'abitudine di dichiararli sempre con 'local' per aggirare questo. – flotzilla

+1

@VaticanViolator Sono d'accordo, sarebbe più bello se fossero locali di default. È solo una di quelle cose che impari ad accettare e ad affrontare. Ad esempio, nomino le variabili locali in lettere minuscole e globali in maiuscolo. Rende chiaro quale è il problema e riduce il rischio di calpestare accidentalmente i globali se dimentico una dichiarazione 'local'. –

4

È davvero importante. Poiché le funzioni di bash non restituiscono valori e le variabili utilizzate dall'ambito globale (ovvero, possono accedere alle variabili da "esterno" al relativo ambito), il modo usuale per gestire l'output di una funzione è archiviare il valore in una variabile e quindi chiamarla.

Quando si definisce una funzione con (), hai ragione: creerà la sotto-shell. Quella sottoguscio conterrà gli stessi valori dell'originale, ma non sarà in grado di modificarli. In modo da perdere quella risorsa di modifica delle variabili di ambito globale.

vedere un esempio:

$ cat a.sh 
#!/bin/bash 

func_braces() { #function with curly braces 
echo "in $FUNCNAME. the value of v=$v" 
v=4 
} 

func_parentheses() (
echo "in $FUNCNAME. the value of v=$v" 
v=8 
) 


v=1 
echo "v=$v. Let's start" 
func_braces 
echo "Value after func_braces is: v=$v" 
func_parentheses 
echo "Value after func_parentheses is: v=$v" 

Diamo eseguirlo:

$ ./a.sh 
v=1. Let's start 
in func_braces. the value of v=1 
Value after func_braces is: v=4 
in func_parentheses. the value of v=4 
Value after func_parentheses is: v=4 # the value did not change in the main shell 
+1

tecnicamente le funzioni possono restituire i valori di un codice di uscita tramite 'return' che sarà restituito al genitore accessibile tramite concatenazione o' $? ' – Catskul

4

tendo a usare una sottoshell quando voglio cambiare directory, ma sempre dalla stessa directory originale, e non può essere si è preso la briga di usare pushd/popd o di gestire personalmente le directory.

for d in */; do 
    (cd "$d" && dosomething) 
done 

Ciò funziona altrettanto bene da un corpo di una funzione, ma anche se si definisce la funzione con parentesi graffe, è ancora possibile utilizzarlo da una subshell.

doit() { 
    cd "$1" && dosomething 
} 
for d in */; do 
    (doit "$d") 
done 

Naturalmente, si può ancora mantenere scope delle variabili all'interno di una funzione di parentesi graffe-definita utilizzando dichiarano o locale:

myfun() { 
    local x=123 
} 

Quindi direi, definire in modo esplicito la funzione come una subshell solo se non essendo una subshell è dannosa per l'ovvio comportamento corretto di tale funzione.

Curiosità: come nota a margine, considera che bash in realtà sempre considera la funzione come un comando composto di parentesi graffa. Ha solo a volte parentesi in esso:

$ f() (echo hi) 
$ type f 
f is a function 
f() 
{ 
    (echo hi) 
}