web-dev-qa-db-fra.com

Qu'est-ce que «déclarer» dans Bash?

Après avoir lu la réponse d'ilkkachu à cette question j'ai appris l'existence du declare (avec l'argument -n) Shell intégré.

help declare apporte:

Définissez les valeurs et les attributs des variables.

Déclarez les variables et donnez-leur des attributs. Si aucun NOM n'est indiqué, affichez les attributs et les valeurs de toutes les variables.

-n ... fait de NAME une référence à la variable nommée par sa valeur

Je demande une explication générale avec un exemple concernant declare car je ne comprends pas le man. Je sais ce qu'est une variable et je la développe mais je manque toujours le man sur declare (attribut variable?).

Peut-être voudriez-vous expliquer cela en vous basant sur le code d'ilkkachu dans la réponse:

#!/bin/bash
function read_and_verify  {
    read -p "Please enter value for '$1': " tmp1
    read -p "Please repeat the value to verify: " tmp2
    if [ "$tmp1" != "$tmp2" ]; then
        echo "Values unmatched. Please try again."; return 2
    else
        declare -n ref="$1"
        ref=$tmp1
    fi
}
12
user149572

Dans la plupart des cas, cela suffit avec une déclaration implicite dans bash

asdf="some text"

Mais, parfois, vous voulez que la valeur d'une variable soit uniquement un entier (donc au cas où elle changerait plus tard, même automatiquement, elle ne pourrait être changée qu'en entier, par défaut à zéro dans certains cas), et peut utiliser:

declare -i num

ou

declare -i num=15

Parfois, vous voulez des tableaux, puis vous avez besoin de declare

declare -a asdf   # indexed type

ou

declare -A asdf   # associative type

Vous pouvez trouver de bons didacticiels sur les tableaux dans bash lorsque vous naviguez sur Internet avec la chaîne de recherche "bash array tutorial" (sans guillemets), par exemple

linuxconfig.org/how-to-use-arrays-in-bash-script


Je pense que ce sont les cas les plus courants lorsque vous déclarez des variables.


Veuillez également noter que

  • dans une fonction, declare rend la variable locale (dans la fonction)
  • sans aucun nom, il répertorie toutes les variables (dans le shell actif)

    declare
    

Enfin, vous obtenez un bref résumé des fonctionnalités de la commande intégrée de Shell declare dans bash avec la commande

help declare
4
sudodus

La sortie de help declare Est assez laconique. Une explication plus claire peut être trouvée dans man bash Ou info bash - ce dernier étant la source de ce qui suit.

Tout d'abord, quelques définitions. À propos variables et attributs :

Un paramètre est une entité qui stocke des valeurs. ... A variable est un paramètre désigné par name. Une variable a une valeur et zéro ou plusieurs attributs. Les attributs sont attribués à l'aide de la commande intégrée declare ...

Et sur le declareintégré :

declare

declare [-aAfFgilnrtux] [-p] [name[=value] …]

Déclarez les variables et donnez-leur des attributs. Si aucun nom n'est donné, affichez plutôt les valeurs des variables.

...

-n
Donnez à chacun nom l'attribut nameref, ce qui en fait une référence de nom à une autre variable. Cette autre variable est définie par la valeur de nom. Toutes les références, affectations et modifications d'attribut à name, à l'exception de celles qui utilisent ou modifient l'attribut -n Lui-même, sont effectuées sur la variable référencée par name valeur. ...

Notez que les variables nom de référence ne sont disponibles que dans Bash 4.3 ou version ultérieure1.

De plus, pour une introduction utile à declare et aux attributs variables dans Bash, je vous indiquerais cette réponse à "Que font declare name Et declare -g ? "(qui se concentre principalement sur la portée des variables, cependant).


Fondamentalement2, declare name=[value] Équivaut à la tâche name=[value] Que vous connaissez probablement. Dans les deux cas, name reçoit la valeur nulle si value est manquant.

Notez que le declare name Légèrement différent, à la place, ne fait pas set la variable name3:

$ declare name

## With the -p option, declare is used to display
## attributes and values of variables
$ declare -p name
declare -- name            ## "name" exists

## Parameter expansion can be used to reveal if a variable is set:
## "isunset" is substituted to "name" only if unset 
$ echo "${name-isunset}"
isunset

Ainsi, la variable name peut être:

  • déclaré et non défini, après declare name;
  • déclaré et défini avec null comme valeur, après name= ou declare name=;
  • déclaré, défini et avec une valeur non nulle après name=value ou declare name=value .

Plus généralement, declare [options] name=value

  1. crée la variable name - qui est un paramètre avec un nom, qui à son tour n'est qu'une partie de la mémoire que vous pouvez utiliser pour stocker des informations4;
  2. lui attribue la valeur value;
  3. définit facultativement les attributs de name, qui définissent à la fois le type de valeur qu'il peut stocker (pas en termes de type, à proprement parler, car le langage de Bash n'est pas tapé) et les manières il peut être manipulé.

Les attributs sont probablement plus faciles à expliquer avec un exemple: l'utilisation de declare -i name Définira l'attribut "entier" de name, le laissant être traité comme un entier; en citant manuel , "l'évaluation arithmétique sera effectuée lorsque la variable se voit attribuer une valeur":

## Let's compare an ordinary variable with an integer
$ declare var
$ declare -i int
$ var="1+1"
$ int="1+1"
$ echo "$var"
1+1                 ## The literal "1+1"
$ echo "$int"
2                   ## The result of the evaluation of 1+1

À la lumière de ce qui précède, ce qui se passe dans le code d'ilkkachu est que:

  1. Une variable nommée ref est déclarée, avec l'attribut "nameref" défini, et le contenu de $1 (Le premier argument positionnel) lui est affecté:

    declare -n ref="$1"
    

    Le but d'une variable de référence de nom telle que ref est de conserver le nom d'une autre variable, qui ne serait généralement pas connue à l'avance, peut-être parce que nous voulons qu'elle soit définie dynamiquement (par exemple parce que nous voulons réutiliser un morceau de code et l'appliquer à plusieurs variables), et pour fournir un moyen pratique de s'y référer (et de le manipuler). (Pas le seul, cependant: l'indirection est une alternative; voir Expansion des paramètres du shell ).

  2. Lorsque la valeur de la variable tmp1 Est affectée à ref:

    ref=$tmp1
    

    une variable supplémentaire, dont le nom est la valeur de ref, est implicitement déclarée. La valeur de tmp1 Est également indirectement affectée à la variable implicitement déclarée au moyen de cette affectation explicite à ref.

Dans le contexte de votre question liée , appeler read_and_verify En tant que

read_and_verify domain "Prompt text here..."

déclarera la variable domain et lui attribuera la valeur de tmp1 (c'est-à-dire l'entrée de l'utilisateur). Il est exactement conçu pour réutiliser le code qui interagit avec l'utilisateur et tirer parti d'une variable nameref pour déclarer domain et quelques autres variables.

Pour regarder de plus près la partie implicite, nous pouvons reproduire le processus étape par étape:

## Assign a value to the first positional argument
$ set -- "domain"

## Declare the same "tmp1" variable as in your code
$ tmp1="value for domain"

## Declare a "ref" variable with the nameref attribute set and
## assign the value "domain" to it
$ declare -n ref="$1"

## Note that there is no "domain" variable yet
$ declare -p domain
bash: declare: domain: not found

## Assign a value to "ref" and, indirectly, to the "domain" variable
## that is implicitly declared  
$ ref=$tmp1

## Verify that a variable named "domain" now exists, and that
## its value is that of "tmp1"
$ declare -p domain
declare -- domain="value for domain"

## Verify that "ref" is actually a reference to "domain"
$ domain="new value"
$ echo "$domain"
new value
$ declare -p ref
declare -n ref="domain"
$ echo "$ref"
new value

1Référence: CHANGE fichier, section "3. Nouvelles fonctionnalités dans Bash", point "w".
Cela peut être pertinent: par exemple, CentOS Linux 7.6 (actuellement la dernière version) est livré avec Bash 4.2 .

2Comme d'habitude avec les builds Shell, une explication concise et exhaustive est difficile à atteindre car ils effectuent diverses actions, éventuellement hétérogènes. Je me concentrerai uniquement sur la déclaration, l'attribution et la définition d'attributs, et je considérerai la liste, la portée et la suppression d'attributs comme hors de la portée de cette réponse.

3Ce comportement de declare -p A été introduit dans Bash 4.4. Référence: CHANGE fichier, section "3. Nouvelles fonctionnalités dans Bash", point "f".
Comme G-Man l'a souligné dans les commentaires, dans Bash 4.3 declare name; declare -p name Renvoie une erreur. Mais vous pouvez toujours vérifier que name existe avec declare -p | grep 'declare -- name'.

4FullBashGuide, Paramètres sur mywiki.wooledge.org

13
fra-san

Je vais essayer d'expliquer cela, mais pardonnez-moi si je ne suivrai pas l'exemple que vous avez fourni. Je vais plutôt essayer de vous guider dans ma propre approche différente.

Vous dites que vous comprenez déjà des concepts tels que les "variables" et les "élargissant", etc. Je vais donc simplement parcourir quelques connaissances de base qui, autrement, nécessiteraient une attention plus approfondie.

Je vais donc commencer par dire que, au maximum basique niveau, la commande declare est juste un moyen pour vous de dire à Bash que vous avez besoin d'une valeur variable (c'est-à-dire une valeur peut changer lors de l'exécution du script), et que vous ferez référence à cette valeur en utilisant un nom spécifique, précisément le nom que vous indiquez à côté de la commande declare elle-même.

C'est:

declare foo="bar"

indique à Bash que vous voulez que la variable nommée foo ait la valeur bar.

Mais .. attendez une minute .. nous pouvons le faire sans utiliser declare du tout, n'est-ce pas. Un péché:

foo="bar"

Très vrai.

Eh bien, il se trouve que l'affectation simple ci-dessus est en fait une manière implicite pour .. en fait .. déclarer une variable.

( Il arrive aussi que ce qui précède soit n de plusieurs façons pour changer la valeur de la variable nommée foo; en effet, c'est précisément la manière la plus directe, concise, évidente et directe .. mais ce n'est pas la seule .. .. j'y reviendrai plus tard .. ) .

Mais alors, s'il est si bien possible de déclarer un "nom qui marquera les valeurs des variables" (juste "variable" ci-après, par souci de concision) sans utiliser du tout declare, pourquoi voudriez-vous jamais utiliser cette pompeuse commande "declare"?

La réponse réside dans le fait que la manière implicite ci-dessus de déclarer une variable (foo="bar"), il .. implicitement .. oblige Bash à considérer cette variable comme étant du type le plus couramment utilisé dans le scénario d'utilisation typique d'un Shell.

Ce type est le type chaîne, c'est-à-dire une séquence de caractères sans signification particulière. Par conséquent, une chaîne est ce que vous obtenez lorsque vous utilisez la déclaration implicite.

Mais vous, en tant que programmeur, devez parfois plutôt considérer une variable comme, par exemple, un nombre .. sur lequel vous devez effectuer des opérations arithmétiques .. et utiliser une déclaration implicite comme foo=5+6 ne fera pas attribuer à Bash la valeur 11 à foo comme vous pouvez vous y attendre. Il assignera plutôt à foo la séquence des trois caractères 5+6.

Donc .. vous avez besoin d'un moyen de dire à Bash que vous voulez que foo soit considéré comme un nombre, pas une chaîne .. et c'est pour cela qu'un declare explicite est utile.

Dis le:

declare -i foo=5+6  # <<- note the '-i' option: it means 'integer'

et Bash se fera un plaisir de faire le calcul pour vous, et assignera la valeur numérique 11 à la variable foo.

C'est-à-dire: en disant declare -i foo vous donnez à la variable foo l'attribut d'être un nombre entier.

La déclaration de nombres (précisément des entiers, car Bash ne comprend toujours pas les décimales, les virgules flottantes et tout cela) peut être la première raison d'utiliser declare, mais ce n'est pas la seule. Comme vous l'avez déjà compris, il existe quelques autres attributs que vous pouvez attribuer aux variables. Par exemple, vous pouvez demander à Bash de toujours mettre la valeur d'une variable en majuscule, peu importe: si vous dites declare -u foo, puis à partir de là quand vous dites foo=bar Bash affecte en fait la chaîne BAR à la variable foo.

Pour donner l'un de ces attributs à une variable, vous devez utiliser la commande declare, il n'y a pas d'autre choix.


Maintenant, un autre des attributs que vous pouvez donner via declare est le tristement célèbre "nom-ref", le -n attribut. ( Et maintenant je vais reprendre le concept que j'ai mis en attente plus tôt ).

L'attribut name-ref, fondamentalement, permet aux programmeurs Bash de changer la valeur d'une variable d'une autre manière. Il donne plus précisément un moyen indirect de le faire.

Voici comment cela fonctionne:

Vous declare une variable ayant le -n attribut, et il est très recommandé (mais pas strictement requis, mais cela simplifie les choses) que vous donnez également une valeur à cette très variable sur la même commande declare. Comme ça:

declare -n baz="foo"

Cela indique à Bash qu'à partir de ce moment, chaque fois que vous utiliserez ou modifierez la valeur de la variable nommée baz, il devra effectivement utiliser ou modifier la valeur de la variable nommée foo.

Ce qui signifie qu'à partir de là, vous pouvez dire quelque chose comme baz=10+3 pour que foo obtienne la valeur de 13. En supposant bien sûr que foo ait été précédemment déclaré comme entier (declare -i) comme nous l'avons fait il y a une minute, sinon il obtiendra la séquence des quatre caractères 10+3.

Aussi: si vous modifiez directement la valeur de foo, comme dans foo=15, vous en verrez également 15 en disant echo “${baz}”. En effet, la variable baz déclarée comme référence de nom de foo reflète toujours la valeur de foo.

Ce qui précède declare -n la commande est dite "référence-nom" car elle fait que la variable baz renvoie à la nom d'une autre variable. En fait, nous avons déclaré que baz a la valeur "foo" qui, en raison de -n option, est gérée par Bash comme le nom d'une autre variable.

Maintenant, pourquoi sur Terre voudriez-vous jamais faire ça?

Eh bien .. il vaut la peine de dire que c'est une fonctionnalité pour des besoins assez avancés.

En fait, si avancé que lorsqu'un programmeur est confronté à un problème qui nécessiterait vraiment une référence de nom, il est également probable que ce problème devrait plutôt être résolu en utilisant un langage de programmation approprié au lieu de Bash.

Un de ces besoins avancés est, par exemple, lorsque vous, en tant que programmeur, ne pouvez pas savoir pendant le développement quelle variable vous devrez utiliser dans un point spécifique d'un script, mais il sera entièrement connu dynamiquement au moment de l'exécution. Et étant donné qu'il n'y a aucun moyen pour un programmeur d'intervenir au moment de l'exécution, la seule option est de prévoir au préalable une telle situation dans le script, et un "nom-ref" peut être le seul moyen viable. En tant que cas d'utilisation largement connu de ce besoin avancé, pensez aux plug-ins, par exemple. Le programmeur d'un programme "plugin-able" doit au préalable prévoir de manière générique des plug-ins futurs (et éventuellement tiers). Par conséquent, le programmeur devra utiliser des installations comme une référence de nom dans Bash.

Un autre besoin avancé est lorsque vous devez traiter une énorme quantité de données dans RAM et , vous devez également passer ces données autour des fonctions de votre script qui doivent également modifier ces données en cours de route. Dans ce cas, vous pourriez certainement copiez ces données d'une fonction à une autre (comme le fait Bash lorsque vous faites dest_var="${src_var}" ou lorsque vous appelez des fonctions comme dans myfunc "${src_var}"), mais étant donné que ces données sont énormes, cela représenterait un énorme gaspillage de RAM et pour un fonctionnement très inefficace. Donc, la solution si de telles situations surviennent est d’utiliser non pas une copie des données, mais une référence à ces données. Dans Bash, un nom-ref. Ce cas d'utilisation est vraiment la norme dans tout langage de programmation moderne, mais il est tout à fait exceptionnel en ce qui concerne Bash, car Bash est principalement conçu pour de courts scripts simples qui traitent principalement des fichiers et des commandes externes et donc des scripts Bash rarement doivent passer une énorme quantité de données entre les fonctions. Et quand les fonctions d'un script ont besoin de partager certaines données (y accéder et les modifier), cela est généralement réalisé en utilisant simplement une variable globale, ce qui est tout à fait la norme dans les scripts Bash autant que il est très déconseillé dans les langages de programmation appropriés.

Ensuite, il peut y avoir un cas d'utilisation notable pour les références de nom dans Bash, et (peut-être ironiquement), il est associé à lorsque vous utilisez encore d'autres types de variables:

  1. variables déclarées comme "tableaux indexés" (declare -a)
  2. variables déclarées comme "tableaux associatifs" (declare -A).

Il s'agit d'un type de variables qui peuvent être plus facilement (ainsi que plus efficacement) transmises le long des fonctions en utilisant des références de nom au lieu d'une copie normale, même lorsqu'ils ne transportent pas d'énormes quantités de données.

Si tous ces exemples semblent étranges et toujours incompréhensibles, c'est uniquement parce que les références de nom sont en effet un sujet avancé et un besoin rare du scénario d'utilisation typique de Bash.

Je pourrais vous parler des occasions où j'ai pour ma part trouvé une utilisation pour les références de nom dans Bash, mais jusqu'à présent, elles ont été principalement pour des besoins assez "ésotériques" et compliqués, et je crains que si je les décrivais, je ne compliquer les choses pour vous à ce stade de votre apprentissage. Pour ne citer que le moins complexe (et peut-être pas ésotérique): renvoyer des valeurs à partir de fonctions. Bash ne prend pas vraiment en charge cette fonctionnalité, j'ai donc obtenu la même chose en utilisant des références de nom. C'est d'ailleurs exactement ce que fait votre exemple de code.


En plus de cela, un petit conseil personnel, qui serait en fait mieux adapté à un commentaire, mais je n'ai pas été en mesure de le condenser suffisamment pour entrer dans les limites du commentaire de StackExchange.

Je pense que le plus que vous devriez faire en ce moment est de simplement expérimenter avec les références de nom en utilisant les exemples simples que j'ai montrés et peut-être avec l'exemple de code vous avez fourni, en ignorant pour le moment la partie "pourquoi diable" et en vous concentrant uniquement sur la partie "comment ça marche". En expérimentant un peu, la partie "comment" peut mieux sombrer dans votre esprit, de sorte que la partie "pourquoi" vous deviendra claire en temps voulu quand (ou si) vous aurez un vrai problème pratique pour lequel un nom- ref serait vraiment utile.

7
LL3

En général, declare dans le bash Shell définit (ou supprime ou affiche) attributs sur les variables. Un attribut est une sorte d'annotation qui dit "ceci est une référence de nom", ou "ceci est un tableau associatif", ou "cette variable doit toujours être évaluée comme un entier", ou "cette variable est en lecture seule et ne peut pas être réinitialisé ", ou" cette variable est exportée (une variable d'environnement) "etc.

Le typeset intégré est un synonyme de declare dans bash, car typeset est utilisé dans d'autres shells (ksh, où il originated, et zsh, par exemple) pour définir les attributs des variables.


En examinant de plus près l'exemple de référence de nom dans la question:

La fonction Shell que vous montrez, avec un peu de code supplémentaire qui l'utilise:

#!/bin/bash

function read_and_verify  {
    read -p "Please enter value for '$1': " tmp1
    read -p "Please repeat the value to verify: " tmp2
    if [ "$tmp1" != "$tmp2" ]; then
        echo "Values unmatched. Please try again."; return 2
    else
        declare -n ref="$1"
        ref=$tmp1
    fi
}

read_and_verify foo
printf 'The variable "foo" has the value "%s"\n' "$foo"

Exécuter ceci:

bash script.sh
 Veuillez saisir une valeur pour 'foo': bonjour
 Veuillez répéter la valeur pour vérifier: bonjour?
 Des valeurs inégalées. Veuillez réessayer. 
 La variable "foo" a la valeur "" 

Cela montre que la variable foo n'est définie sur rien lorsque l'utilisateur entre deux chaînes différentes.

bash script.sh
 Veuillez saisir une valeur pour 'foo': bonjour
 Veuillez répéter la valeur pour vérifier: bonjour
 La variable "foo" a la valeur "bonjour" 

Cela montre que la variable foo est définie sur la chaîne que l'utilisateur a entrée lorsqu'il a entré la chaîne même deux fois.

La façon dont $foo obtient la valeur hello dans la partie principale du script par les lignes suivantes dans la fonction Shell:

declare -n ref="$1"
ref=$tmp1

$tmp1 est la chaîne hello entrée par l'utilisateur, et $1 est la chaîne foo passée sur la ligne de commande de la fonction depuis la partie principale du script.

Notez que la variable ref est déclarée avec declare -n comme variable de référence de nom et que la valeur foo est donnée comme valeur dans cette déclaration. Cela signifie qu'à partir de ce moment, jusqu'à ce que la variable soit hors de portée, toute utilisation de la variable ref sera la même que l'utilisation de foo. La variable ref est un nom référence variable référençant foo à ce stade.

Cela a pour conséquence que l'attribution d'une valeur à ref, comme cela se fait sur la ligne suivant la déclaration, affectera la valeur à foo.

La valeur hello est alors disponible dans $foo dans la partie principale du script.

6
Kusalananda