¿Qué es “ declare ” en Bash?

Después de leer la respuesta de ilkkachu a esta pregunta , me enteré de la existencia del declare (con argumento -n) shell integrado.

help declare trae:

Establece valores y atributos de variables.

Declara variables y dales atributos. Si no se dan NAME, muestra atributos y valores de todas las variables.

-n … hacer NAME una referencia a la variable nombrada por su valor

I pida una explicación general con un ejemplo sobre declare porque no entiendo el man. Sé qué es una variable y expandirla, pero todavía extraño el man en declare (¿atributo de variable?).

Tal vez le gustaría explicar esto basado en el código de ilkkachu en la respuesta:

#!/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 } 

Comentarios

Respuesta

En la mayoría de los casos es suficiente con una declaración implícita en bash

asdf="some text" 

Pero, a veces, desea que el valor de una variable sea solo un número entero (por lo que, en caso de que cambie más tarde, incluso automáticamente, solo se puede cambiar a un número entero, el valor predeterminado es cero en algunos casos) y puede usar:

declare -i num 

o

declare -i num=15 

A veces desea matrices y luego necesita declare

declare -a asdf # indexed type 

o

Puede encontrar buenos tutoriales sobre matrices en bash cuando navega por Internet con la cadena de búsqueda» bash array tutorial «(sin comillas), por ejemplo

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


Creo que estos son los casos más comunes en los que declara variables.


Tenga en cuenta también que

  • en una función, declare hace que la variable sea local (en la función)
  • sin ningún nombre, enumera todas las variables (en el shell activo)

    declare 

Finalmente, obtiene un breve resumen de las características del comando integrado de shell declare en bash con el comando

help declare 

Comentarios

  • ¡Hola desde el OP! Creo que tu respuesta es genial y la he votado a favor y gracias por esta, en mi opinión, respuesta muy didáctica. Acabo de sugerir una pequeña edición que, en mi humilde opinión, hace que sea aún más fácil y accesible de leer para los recién llegados; por favor revise esa sugerencia.
  • Acabo de ver que user:muru votó para rechazar la edición; tenga en cuenta que muru y yo hemos » enfrentados » varias veces en las diferentes secciones de este sitio web y supongo que su rechazo no es ‘ t objetivo y es realmente dañino en contraste con los cambios directos presentados en la página de sugerencias de edición aquí: unix.stackexchange.com/review/suggested -edits / 325749
  • @JohnDoea, vi que también otro usuario ha rechazado sus ediciones. Creo que algunas de sus ediciones son buenas, pero algunas de ellas cambiarán el mensaje de lo que pretendía, así que no me gustarían. Puedo editar la respuesta para incluir lo que creo que son buenas ediciones.
  • gracias; como probablemente sepa, rechazar las ediciones es mucho más común en SE que aceptarlas (incluso parcialmente); gracias por hacerme saber que otro usuario rechazó la edición (honestamente, no ‘ vi antes) y por considerar insertar al menos algunas de mis ediciones en su respuesta; Lo respeto mucho,

Responder

La salida de help declare es bastante conciso. Se puede encontrar una explicación más clara en man bash o info bash, siendo esta última la fuente de lo que sigue.

Primero, algunas definiciones. Acerca de variables y atributos :

Un parámetro es una entidad que almacena valores. … Una variable es un parámetro indicado por un name. Una variable tiene un valor y cero o más atributos . Los atributos se asignan mediante el comando interno declare

Y sobre el declare incorporado :

declare

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

Declara variables y dales atributos. Si no se dan nombres, muestre los valores de las variables.

-n
Dé cada name el atributo nameref , convirtiéndolo en una referencia de nombre a otra variable. Esa otra variable está definida por el valor de nombre . Todas las referencias, asignaciones y modificaciones de atributos a nombre , excepto aquellas que usan o cambian el atributo -n en sí, se realizan en la variable a la que hace referencia nombre del valor. …

Tenga en cuenta que las variables de referencia de nombre solo están disponibles en Bash 4.3 o posterior 1 .

Además, para una introducción útil a declare y los atributos de las variables en Bash, le señalaría este answer to » ¿Qué hacen declare name y declare -g? » (que se centra principalmente en el alcance de las variables).


Básicamente 2 , declare name=[value] es equivalente a la asignación name=[value] con la que probablemente estés familiarizado. En ambos casos, a name se le asigna el valor nulo valor si value falta.

Tenga en cuenta que el declare name ligeramente diferente, en cambio, no establece la variable name 3 :

 $ 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  

Así, la variable name puede ser:

  • declarada y desarmado , después de declare name;
  • declarado y configurado con null como valor, después de name= o declare name=;
  • declarado , establecido y con un valor no nulo después de name=value o .

De manera más general, declare [options] name=value

  1. crea la variable name – que es un parámetro con un nombre, que a su vez es solo una parte de la memoria que puede usar para almacenar información 4 ;
  2. asigna el valor value;
  3. establece opcionalmente los atributos name «, que definen tanto el tipo de valor puede almacenar (no en términos de un tipo , estrictamente hablando, ya que el lenguaje de Bash no está escrito) y las formas en que se puede manipular.

Los atributos son probablemente más fácil de explicar con un ejemplo: el uso de declare -i name establecerá el atributo » integer » de name, dejando que se trate como un número entero; citando el manual , » se realizará una evaluación aritmética cuando a la variable se le asigne un valor «:

 ## 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  

A la luz de Lo anterior, lo que está sucediendo en el código de ilkkachu es que:

  1. Se declara una variable llamada ref, con la » nameref » conjunto de atributos, y el contenido de $1 (el primer argumento posicional) se asigna a it:

     declare -n ref="$1"  

    El objetivo de una variable de referencia de nombre como ref es contener el nombre de otra variable, que generalmente no se conocería de antemano, posiblemente porque queremos que se defina dinámicamente (por ejemplo, porque queremos reutilizar un fragmento de código y tenerlo aplicado a varias variables), y a proporcionan una forma conveniente de referirse a él (y manipularlo). (Sin embargo, no es el único: la indirección es una alternativa; consulte Expansión de parámetros de shell ).

  2. Cuándo el valor de la variable tmp1 se asigna a ref:

     ref=$tmp1  

    una variable adicional, cuyo nombre es el valor de ref, se declara implícitamente. El valor de tmp1 también se asigna indirectamente a la variable declarada implícitamente mediante esta asignación explícita a ref .

En el contexto de su pregunta vinculada , llamando a read_and_verify as

 read_and_verify domain "Prompt text here..."  

declarará la variable domain y asígnele el valor de tmp1 (es decir, la entrada del usuario). Está diseñado exactamente para reutilizar el código que interactúa con el usuario y aprovechar una variable nameref para declarar domain y algunas otras variables.

Para ver más de cerca la parte implícita, podemos reproducir el proceso paso a paso:

 ## 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  

1 Referencia: CHANGES archivo, sección » 3. Nuevas funciones en Bash «, punto » w «.
Esto puede ser relevante: por ejemplo, CentOS Linux 7.6 (actualmente el última versión) se envía con Bash 4.2 .

2 Como es habitual con shell incorporado, una explicación exhaustiva y concisa es esquiva ya que realizan varias acciones, posiblemente heterogéneas. Me centraré en declarar, asignar y establecer atributos únicamente, y consideraré enumerar, definir el alcance y eliminar atributos como fuera del alcance de esta respuesta.

3 Este comportamiento de declare -p se introdujo en Bash 4.4. Referencia: archivo CHANGES , sección » 3. Nuevas funciones en Bash «, punto » f «.
Como G-Man señaló en los comentarios, en Bash 4.3 declare name; declare -p name arroja un error. Pero aún puede verificar que name existe con declare -p | grep "declare -- name".

4 FullBashGuide, Parámetros en mywiki.wooledge.org

Comentarios

  • (1) No puedo reproducir los resultados que muestra en su primer bloque de código: declare name seguido de declare -p name produce “bash: declare: name: not found”. (Aunque declare -p | grep na produce declare -- name.) (2) Creo que es un poco engañoso presentar echo "${name-isunset}" en el contexto de declare name, ya que trata una variable no declarada (es decir, no definida) de la misma forma que una declarada pero variable sin establecer . (3) Es posible que desee mencionar que las referencias de nombre están disponibles solo en bash versión 4.3 y superior.
  • @ G-Man ¡Gracias por sus comentarios! Yo ‘ los abordaré tan pronto como pueda y ‘ actualizaré mi respuesta cuando corresponda. En cuanto a (1), mi GNU bash, version 5.0.7(1)-release (x86_64-pc-linux-gnu) en Arch Linux todavía produce los resultados que mostré. Tal vez ese comportamiento se haya introducido recientemente, ‘ investigaré eso.
  • Sí; ‘ solo uso la versión 4.3.
  • Respuesta de @ G-Man actualizada con notas sobre (1) y (3). Acerca de (2): mi objetivo era ilustrar que declare x no ‘ t establece x, mientras que declare x= lo hace. No pude ‘ t encontrar ninguna referencia que respalde la afirmación de que declare -- x (como resultado de declare -p x) significa » no configurado «, mientras que declare -- x="" significa » set «; por lo tanto, introduje la expansión ${parameter-word}, incluso si no puede discriminar entre » unset » y » no ‘ no existe «, como usted señala. Sin embargo, ‘ no estoy seguro de cómo puedo aclarar esto en mi respuesta (sin distraer al lector del punto).

Respuesta

Voy a intentar explicar esto, pero perdóname si no sigo el ejemplo que proporcionaste. Prefiero tratar de guiarlo a lo largo de mi propio enfoque diferente.

Dice que ya comprende conceptos como «variables» y «expandirlas», etc., así que solo echaré un vistazo a algunos conocimientos previos que de otro modo requerirían un enfoque más profundo.

Empezaré diciendo que, como mucho, básico , el comando declare es solo una forma de decirle a Bash que necesita un valor variable (es decir, un valor que podría cambiar durante la ejecución del script), y que lo hará refiérase a ese valor usando un nombre específico, precisamente el nombre que indique junto al comando declare.

Es decir:

 declare foo="bar"  

le dice a Bash que quiere que la variable llamada foo tenga el valor bar.

Pero … espere un minuto … podemos haz eso sin usar declare en absoluto, ¿no? Como en:

 foo="bar"  

Muy cierto.

Bueno , sucede que la simple asignación anterior es en realidad una forma implícita para .. de hecho .. declarar una variable.

( También sucede que lo anterior es una de varias formas de cambia el valor de la variable denominada foo; de hecho, es precisamente la más directa, forma concisa, evidente y directa … pero no es la única … Volveré a esto más tarde … ).

Pero entonces, si es así bien es posible declarar un «nombre que etiquetará valores variables» (solo «variable» de aquí en adelante, en aras de la brevedad) sin usar declare en absoluto, ¿por qué querrías hacerlo? utilizar este pomposo comando de «declarar»?

La respuesta está en el hecho de que lo anterior implici t forma de declarar una variable (foo="bar"), esto … implícitamente … hace que Bash considere que la variable es del tipo que se usa más comúnmente en el escenario de uso típico de un shell .

Este tipo es el tipo de cadena, es decir, una secuencia de caracteres sin un significado particular. Por lo tanto, una cadena es lo que obtiene cuando usa la declaración implícita.

Pero usted, como programador, a veces necesita considerar una variable como, por ejemplo, un número … en el que necesita hacer aritmética operaciones … y el uso de una declaración implícita como foo=5+6 no hará que Bash asigne el valor 11 a foo como usted podría esperar. Preferirá asignar a foo la secuencia de los tres caracteres 5 + 6.

Entonces … necesitas una forma de decirle a Bash que quieres que foo se considere un número, no un string … y para eso es útil una declare explícita.

Simplemente diga:

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

y Bash felizmente hará los cálculos por usted y le asignará el valor numérico 11 a la variable foo.

Es decir: al decir declare -i foo le da a la variable foo el atributo de ser un número entero.

Declarar números (precisamente enteros, porque Bash todavía no entiende decimales, puntos flotantes y todo eso) puede ser la primera razón para usar declare, pero no es la única razón. Como ya ha entendido, hay algunos otros atributos que puede asignar a las variables. Por ejemplo, puede hacer que Bash siempre ponga el valor de una variable en mayúsculas sin importar qué: si dice declare -u foo, luego cuando diga foo=bar Bash realmente asigna la cadena BAR a la variable foo.

Para dar cualquiera de estos atributos a una variable, debe usar el comando declare, no hay otra opción.


Ahora, otro de los atributos que puede dar a través de declare es el infame «name-ref», el atributo -n. ( Y ahora voy a retomar el concepto que dejé en suspenso antes ).

El atributo name-ref, básicamente, permite a los programadores de Bash otra forma de cambiar el valor de una variable. Más precisamente, proporciona una forma indirecta de hacerlo.

Aquí está cómo funciona:

Usted declare una variable que tiene el atributo -n, y es muy recomendado (aunque no es estrictamente obligatorio, pero simplifica las cosas) que también le dé un valor a esta muy variable en la misma declare comando. Así:

 declare -n baz="foo"  

Esto le dice a Bash que, a partir de entonces encendido, cada vez que utilice o cambie el valor de la variable denominada baz, en realidad utilizará o cambiará el valor de la variable denominada foo.

Lo que significa que, a partir de ese momento, Puedes decir algo como baz=10+3 para hacer que foo obtenga el valor de 13.Suponiendo, por supuesto, que foo se declaró previamente como entero (declare -i) como lo hicimos hace un minuto, de lo contrario obtendrá la secuencia de los cuatro caracteres 1 0 + 3.

Además: si cambia el valor de foo directamente, como en foo=15, verá 15 también diciendo echo “${baz}”. Esto se debe a que la variable baz declarada como name-ref de foo siempre refleja foo valor.

El comando declare -n anterior se dice «nombre-referencia» porque hace que la variable baz haga referencia al nombre de otra variable. De hecho, hemos declarado que baz tiene el valor «foo» que, debido a la opción -n, Bash lo maneja como el nombre de otra variable.

Ahora, ¿por qué en la Tierra querrías hacer eso alguna vez?

Bueno … vale la pena decir que esta es una característica para necesidades bastante avanzadas.

De hecho, tan avanzada que cuando un programador enfrenta un problema que realmente requeriría una referencia de nombre, también lo es Es probable que dicho problema deba resolverse utilizando un lenguaje de programación adecuado en lugar de Bash.

Una de esas necesidades avanzadas es, por ejemplo, cuando usted, como programador, no puede saber durante el desarrollo cuál variable tendrá que usar en un punto específico de un script, pero será completamente conocida dinámicamente en tiempo de ejecución. Y dado que no hay forma de que ningún programador intervenga en tiempo de ejecución, la única opción es hacer una provisión de antemano para tal situación en el script, y un «name-ref» puede ser el único viable. camino. Como caso de uso ampliamente conocido de esta necesidad avanzada, piense en complementos, por ejemplo. El programador de un programa «apto para complementos» debe realizar una provisión genérica para complementos futuros (y posiblemente de terceros) de antemano. Por lo tanto, el programador necesitará usar funciones como una referencia de nombre en Bash.

Otra necesidad avanzada es cuando tienes que manejar una gran cantidad de datos en RAM y también necesita pasar esos datos a las funciones de su script que también tienen que modificar esos datos en el camino. En tal caso, ciertamente podría copiar esos datos de una función a otra (como lo hace Bash cuando lo hace dest_var="${src_var}" o cuando invoca funciones como en myfunc "${src_var}"), pero al tratarse de una gran cantidad de datos, supondría una gran pérdida de RAM y una operación muy ineficiente. Entonces, la solución si surgen tales situaciones es usar no una copia de los datos, sino una referencia a esos datos. En Bash, un nombre-ref. Este caso de uso es realmente la norma en cualquier lenguaje de programación moderno, pero es bastante excepcional cuando se trata de Bash, porque Bash está diseñado principalmente para scripts cortos y simples que se ocupan principalmente de archivos y comandos externos y, por lo tanto, los scripts de Bash rara vez tienen que pasar grandes cantidad de datos entre funciones. Y cuando las funciones de un script necesitan compartir algunos datos (acceder a ellos y modificarlos), esto generalmente se logra usando una variable global, que es bastante la norma en los scripts Bash tanto como muy en desuso en los lenguajes de programación adecuados.

Entonces, puede haber un caso de uso notable para referencias de nombre en Bash, y (quizás irónicamente) está asociado cuando usa otros tipos de variables:

  1. variables que se declaran como «matrices indexadas» (declare -a)
  2. variables que se declaran como «matrices asociativas» (declare -A).

Se trata de un tipo de variables que pueden ser más fácilmente (así como más eficientemente) transmitidas a las funciones mediante el uso de referencias de nombre en lugar de la copia normal, incluso cuando no contienen grandes cantidades de datos.

Si todos estos ejemplos suenan extraños y aún incomprensibles, es solo porque las referencias de nombres son de hecho un tema avanzado y una rara necesidad del escenario de uso típico de B ash.

Podría contarte sobre ocasiones en las que he encontrado uso para referencias de nombre en Bash, pero hasta ahora han sido principalmente para necesidades bastante «esotéricas» y complicadas, y estoy Temo que si los describo solo les complicaría las cosas en este punto de su aprendizaje. Solo por mencionar el menos complejo (y posiblemente no esotérico): devolver valores de funciones. Bash realmente no es compatible con esta funcionalidad, por lo que obtuve lo mismo usando name-refs. Por cierto, esto es exactamente lo que hace su código de ejemplo.


Además de esto, un pequeño consejo personal, que en realidad sería más adecuado para un comentario, pero no he podido condensarlo lo suficiente. para encajar en los límites de los comentarios de StackExchange.

Creo que lo máximo que debería hacer en este momento es simplemente experimentar con referencias de nombre utilizando los ejemplos simples que mostré y tal vez con el código de ejemplo que proporcionó, sin tener en cuenta por el momento la parte del «por qué en la tierra» y centrándose solo en la parte «cómo funciona». Experimentando un poco, la parte del «cómo» puede penetrar mejor en su mente, de modo que la parte del «por qué» se le aclarará a su debido tiempo cuando (o si) tendrá un problema práctico real para el cual un nombre- ref sería realmente útil.

Comentarios

  • LL3 Me gusta mucho tu respuesta y le di el pulgar hacia arriba: finalmente entiendo lo que declare – – no declarar variables sino declarar sus atributos; sin embargo, lamentablemente perdí la pista cuando comenzaste a explicar qué es un name-ref. No tengo idea de lo que hace y por qué usarlo.
  • Es decir, ¿por qué se le daría este atributo
  • @JohnDoea Ya veo. Quizás podrías dejar este tema por el momento. Probablemente sea demasiado pronto para comprender este concepto en el momento actual de su aprendizaje. De todos modos, he ampliado mi respuesta con más ejemplos y también un pequeño apéndice personal sobre cómo creo que debería proceder. También puse líneas horizontales para delimitar la explicación general de declare de la explicación específica de -n del apéndice personal. También he modificado algunas palabras aquí y allá, pero nada sustancial, así que creo que puede volver a leer la parte -n más el pequeño apéndice.
  • Gracias, creo que ‘ está bien que aprenda sobre esto ahora mismo, solo necesito una explicación que pueda entender y debería volver a leer su respuesta completa hoy. ‘ estoy por aquí.
  • Mencionas foo="bar" y declare foo="bar" . Es posible que desee mencionar (aunque solo sea en una nota al pie) que, en una función de shell, declare foo="bar" crea una variable local y foo="bar" crea una variable global.

Respuesta

En general, declare en el bash shell establece (o elimina, o muestra) atributos en las variables. Un atributo es una especie de anotación que dice «esta es una referencia de nombre», o «esta es una matriz asociativa», o «esta variable siempre debe evaluarse como un número entero», o «esta variable es de solo lectura y no puede volver a establecer «, o» esta variable se exporta (una variable de entorno) «, etc.

El typeset incorporado es un sinónimo de declare en bash, ya que typeset se usa en otros shells (ksh, donde se originó, y zsh, por ejemplo) para establecer atributos de variables.


Mirando más de cerca el ejemplo de referencia de nombre en la pregunta:

La función de shell que muestra, con un poco de código agregado que la usa:

#!/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" 

Ejecutando esto:

 $ bash script.sh Please enter value for "foo": hello Please repeat the value to verify: hello? Values unmatched. Please try again. The variable "foo" has the value "" 

Eso muestra que la variable foo no se establece en nada cuando el usuario ingresa dos diferentes cadenas.

Eso muestra que la variable foo se establece en la cadena que el usuario ingresó cuando ingresó la misma cadena dos veces .

La forma en que $foo obtiene el valor hello en la parte principal del script es mediante las siguientes líneas en la función de shell:

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

donde $tmp1 es la cadena hello ingresado por el usuario, y $1 es la cadena foo pasada en la línea de comando de la función desde la parte principal del script.

Observe que la variable ref se declara con declare -n como una variable de referencia de nombre y que el valor foo se da como el valor en esa declaración. Esto significa que a partir de ese momento, hasta que la variable pase fuera del alcance, cualquier uso de la variable ref será lo mismo que usar foo. La variable ref es una variable de referencia de nombre que hace referencia a foo en este punto.

Esto tiene la consecuencia de que asignar un valor a ref, como se hace en la línea que sigue a la declaración, asignará el valor a foo.

El valor hello está disponible en $foo en la parte principal del script.

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *