fonctions bash: enfermer le corps entre accolades et parenthèses

Habituellement, les fonctions bash sont définies à l'aide d'accolades pour entourer le corps:

foo()
{
    ...
}

Lorsque je travaille sur un script shell aujourd'hui en utilisant largement les fonctions, j'ai rencontré des problèmes avec les variables qui ont le même nom dans l'appelé que dans la fonction appelante, à savoir que ces variables sont les mêmes. J'ai alors découvert que cela peut être évité en définissant les variables locales à l'intérieur de la fonction locale: local var=xyz.

Ensuite, à un moment donné, j'ai découvert un fil (définir le corps de la fonction bash en utilisant des parenthèses au lieu d'accolades ) dans lequel il est expliqué qu'il est tout aussi valable de définir une fonction en utilisant des parenthèses comme ceci:

foo()
(
    ...
)

L'effet de ceci est que le corps de la fonction est exécuté dans un sous-shell, ce qui a l'avantage que la fonction a sa propre portée variable, ce qui me permet de les définir sans local. Puisque avoir une portée locale de fonction semble avoir beaucoup plus de sens et être beaucoup plus sûr que toutes les variables étant global, je me demande immédiatement:

  • pourquoi les accolades sont-elles utilisées par défaut pour entourer le corps de la fonction au lieu de parenthèses?

Cependant, j'ai rapidement découvert un inconvénient majeur à l'exécution de la fonction dans un sous-shell, en particulier que quitter le script de l'intérieur d'une fonction ne fonctionne plus, me forçant à travailler avec le statut de retour le long de l'arbre d'appel entier (en cas de fonctions imbriquées). Ce qui m'amène à ce suivi question:

  • y a-t-il d'autres inconvénients majeurs (*) à utiliser des parenthèses au lieu d'accolades (ce qui pourrait expliquer pourquoi les accolades semblent préférées)?

(*) je suis conscient (d'après les discussions liées aux exceptions sur lesquelles j'ai trébuché au fil du temps) que certains diront que l'utilisation explicite du statut d'erreur est beaucoup mieux que de pouvoir sortir de n'importe où, mais je préfère ce dernier.

Apparemment, les deux styles ont leurs avantages et leurs inconvénients. Si J'espère que certains d'entre vous les utilisateurs de bash plus expérimentés peuvent me donner quelques conseils généraux:

  • Quand dois-je utiliser des accolades pour entourer le corps de la fonction, et quand est-il conseillé de passer aux parenthèses?

EDIT: à emporter à partir des réponses

Merci pour vos réponses, ma tête est maintenant un peu plus claire à ce sujet. Donc, ce que j'enlève des réponses est:

  • Tenez-vous aux accolades conventionnelles, si seulement dans l'ordre ne pas confondre les autres utilisateurs/développeurs potentiels du script (et même utiliser les accolades si le corps entier est enveloppé entre parenthèses).

  • Le seul inconvénient réel des accolades est que toute variable de la portée parente peut être modifiée, bien que dans certaines situations cela puisse être un avantage. Cela peut facilement être contourné en déclarant les variables comme local.

  • L'utilisation de parenthèses, d'autre part, peut avoir des effets indésirables graves, tels que gâcher les sorties, entraînant des problèmes de destruction d'un script et d'isolement de la portée de la variable.

23
demandé sur Community 2015-01-06 18:34:59

3 réponses

Pourquoi les accolades sont-elles utilisées par défaut pour entourer le corps de la fonction au lieu de parenthèses?

Le corps d'une fonction peut être n'importe quelle commande composée. C'est typiquement { list; }, mais les trois autres formes de commandes composées sont techniquement autorisé: (list), ((expression)), et [[ expression ]].

C et les langages de la famille C comme C++, Java, C# et JavaScript utilisent tous des accolades pour délimiter les corps de fonction. Accolades sont la syntaxe la plus naturelle pour les programmeurs familiers avec ceux langue.

Y a-t-il d'autres inconvénients majeurs (*) à utiliser des parenthèses au lieu d'accolades (ce qui pourrait expliquer pourquoi les accolades semblent préférées)?

Oui. Il y a beaucoup de choses que vous ne pouvez pas faire à partir d'un sous-shell, y compris:

  • changer les variables globales. Les modifications des Variables ne se propageront pas au shell parent.
  • quitte le script. Une instruction exit ne quittera que le sous-shell.

Le démarrage d'un sous-shell peut aussi être des performances. Vous lancez un nouveau processus chaque fois que vous appelez la fonction.

Vous pourriez également avoir un comportement étrange si votre script est tué. Les signaux que les coquilles parent et enfant reçoivent vont changer. C'est un effet subtil mais si vous avez des gestionnaires trap ou si vous kill votre script, ces parties ne fonctionnent pas comme vous le souhaitez.

Quand dois-je utiliser des accolades pour entourer le corps de la fonction, et quand est-il conseillé de passer aux parenthèses?

Je vous conseille toujours utiliser des accolades. Si vous voulez un sous-shell explicite, ajoutez un ensemble de parenthèses à l'intérieur des accolades. L'utilisation de parenthèses est une syntaxe très inhabituelle et confondrait beaucoup de gens lisant votre script.

foo() {
   (
       subshell commands;
   )
}
13
répondu John Kugelman 2015-01-07 14:29:55

Ça compte vraiment. Puisque les fonctions bash ne renvoient pas de valeurs et que les variables utilisées proviennent de la portée globale (c'est-à-dire qu'elles peuvent accéder aux variables depuis "en dehors" de sa portée), la manière habituelle de gérer la sortie d'une fonction est de stocker la valeur dans une variable, puis de l'appeler.

Lorsque vous définissez une fonction avec (), vous avez raison: elle va créer un sous-shell. Ce sous-shell contiendra les mêmes valeurs que l'original, mais ne pourra pas les modifier. De sorte que vous perdre cette ressource de changer les variables de portée globale.

Voir un exemple:

$ 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"

Exécutons-le:

$ ./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
5
répondu fedorqui 2015-01-06 15:47:20

J'ai tendance à utiliser un sous-shell quand je veux changer de répertoire, mais toujours à partir du même répertoire d'origine, et je ne peux pas être dérangé d'utiliser pushd/popd ou de gérer les répertoires moi-même.

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

Cela fonctionnerait aussi bien à partir d'un corps de fonction, mais même si vous définissez la fonction avec des accolades, il est toujours possible de l'utiliser à partir d'un sous-shell.

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

Bien sûr, vous pouvez toujours maintenir une portée variable dans une fonction définie par une accolade en utilisant declare ou local:

myfun() {
    local x=123
}

Donc, je dirais, définissez explicitement votre fonction comme un sous-shell seulement si Pas être un sous-shell est préjudiciable au comportement correct évident de cette fonction.

Trivia: comme note de côté, considérez que bash en fait toujours traite la fonction comme une commande composée d'accolades. Il a parfois des parenthèses:

$ f() ( echo hi )
$ type f
f is a function
f () 
{ 
    ( echo hi )
}
4
répondu kojiro 2015-01-06 15:41:55