Depois de ler a resposta de ilkkachu a esta pergunta , aprendi sobre a existência do shell declare
(com argumento -n
) embutido.
help declare
traz:
Defina valores e atributos de variáveis.
Declare variáveis e dê-lhes atributos. Se nenhum NOME for fornecido, exiba os atributos e valores de todas as variáveis.
-n ... torna NAME uma referência à variável nomeada por seu valor
Peço uma explicação geral com um exemplo a respeito declare
porque não entendo o man
. Eu sei o que é uma variável e expandi-la, mas ainda sinto falta do man
( declare
atributo variável?).
Talvez você queira explicar isso com base no código de ilkkachu na resposta:
#!/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
}
In most cases it is enough with an implicit declaration in
bash
But, sometimes you want a variable's value to only be integer (so in case it would later change, even automatically, it could only be changed to an integer, defaults to zero in some cases), and can use:
or
Sometimes you want arrays, and then you need
declare
or
You can find good tutorials about arrays in
bash
when you browse the internet with the search string 'bash array tutorial' (without quotes), for examplelinuxconfig.org/how-to-use-arrays-in-bash-script
I think these are the most common cases when you declare variables.
Please notice also, that
declare
makes the variable local (in the function)without any name, it lists all variables (in the active shell)
Finally, you get a brief summary of the features of the shell built-in command
declare
inbash
with the commandA saída de
help declare
é bastante concisa. Uma explicação mais clara pode ser encontrada emman bash
ouinfo bash
— sendo este último a fonte do que se segue.Primeiro, algumas definições. Sobre variáveis e atributos :
E sobre o
declare
embutido :Observe que as variáveis de referência de nome estão disponíveis apenas no Bash 4.3 ou posterior 1 .
Além disso, para uma introdução útil
declare
e atributos de variáveis no Bash, eu indicaria esta resposta para "O que fazerdeclare name
edeclare -g
fazer?" (que se concentra principalmente no escopo das variáveis).Basicamente 2 ,
declare name=[value]
é equivalente à atribuição com aname=[value]
qual você provavelmente está familiarizado. Em ambos os casos,name
é atribuído o valor nulo sevalue
estiver ausente.Observe que o ligeiramente diferente
declare name
, em vez disso, não define a variávelname
3 :Assim, a variável
name
pode ser:declare name
;name=
oudeclare name=
;name=value
oudeclare name=value
.De forma geral,
declare [options] name=value
name
— que é um parâmetro com um nome, que por sua vez é apenas uma parte da memória que você pode usar para armazenar informações 4 ;value
a ele;name
os atributos de , que definem tanto o tipo de valor que ele pode armazenar (não em termos de um tipo , estritamente falando, já que a linguagem do Bash não é tipada) quanto as maneiras pelas quais ele pode ser manipulado.Atributos são provavelmente mais fáceis de explicar com um exemplo: using
declare -i name
irá definir o atributo "integer" dename
, deixando-o ser tratado como um inteiro; citando o manual , "a avaliação aritmética será realizada quando a variável receber um valor":À luz do exposto, o que está acontecendo no código de ilkkachu é que:
Uma variável nomeada
ref
é declarada, com o atributo "nameref" definido, e o conteúdo de$1
(o primeiro argumento posicional) é atribuído a ela:O objetivo de uma variável de referência de nome, como
ref
é manter o nome de outra variável, que geralmente não seria conhecida antecipadamente, possivelmente porque queremos que ela seja definida dinamicamente (por exemplo, porque queremos reutilizar um pedaço de código e tê-lo aplicado a várias variáveis) e para fornecer uma maneira conveniente de se referir a ela (e manipulá-la). (Mas não é o único: a indireção é uma alternativa; veja Expansão de Parâmetros do Shell ).Quando o valor da variável
tmp1
é atribuído aref
:uma variável adicional, cujo nome é o valor de
ref
, é declarada implicitamente. O valor detmp1
também é atribuído indiretamente à variável declarada implicitamente por meio dessa atribuição explícita aref
.No contexto da sua pergunta vinculada , chamando
read_and_verify
comodeclarará a variável
domain
e atribuirá a ela o valor detmp1
(ou seja, a entrada do usuário). Ele é exatamente projetado para reutilizar o código que interage com o usuário e alavancar uma variável nameref para declarardomain
e algumas outras variáveis.Para dar uma olhada mais de perto na parte implícita, podemos reproduzir o processo passo a passo:
1 Referência: arquivo CHANGES , seção "3. Novos recursos no Bash", ponto "w".
Isso pode ser relevante: por exemplo, o CentOS Linux 7.6 (atualmente a versão mais recente) é fornecido com o Bash 4.2 .
2 Como de costume com shell builtins, uma explicação exaustiva e concisa é indescritível, pois eles executam várias ações possivelmente heterogêneas. Vou me concentrar apenas em declarar, atribuir e definir atributos e considerarei listar, definir o escopo e remover atributos como fora do escopo desta resposta.
3 Este comportamento
declare -p
foi introduzido no Bash 4.4. Referência: arquivo CHANGES , seção "3. Novos recursos no Bash", ponto "f".Como G-Man apontou nos comentários, no Bash 4.3
declare name; declare -p name
gera um erro. Mas você ainda pode verificar sename
existe comdeclare -p | grep 'declare -- name'
.4 FullBashGuide, Parâmetros em mywiki.wooledge.org
Vou tentar explicar isso, mas me perdoe se não seguir o exemplo que você forneceu. Prefiro tentar guiá-lo ao longo de minha própria abordagem diferente.
Você diz que já entende conceitos como “variáveis” e “expandindo-as”, etc., então vou apenas passar por cima de alguns conhecimentos básicos que, de outra forma, exigiriam um foco mais profundo.
Então, vou começar dizendo que, em seu nível mais básico , o
declare
comando é apenas uma maneira de você dizer ao Bash que você precisa de um valor de variável (ou seja, um valor que pode mudar durante a execução do script), e que você irá se referir a esse valor usando um nome específico, precisamente o nome que você indica ao lado dodeclare
próprio comando.Aquilo é:
diz ao Bash que você quer que a variável nomeada
foo
tenha o valorbar
.Mas .. espere um minuto .. podemos fazer isso sem usar
declare
nada, não podemos. Como em:Muito verdadeiro.
Bem, acontece que a atribuição simples acima é na verdade uma maneira implícita para... na verdade... declarar uma variável.
( Acontece também que o acima é uma das poucas maneiras de alterar o valor da variável nomeada
foo
; na verdade, é precisamente a maneira mais direta, concisa, evidente, direta.. mas não é a única.. . . Voltarei a isso mais tarde .. ).Mas então, se é tão bem possível declarar um “nome que marcará valores de variáveis” (apenas “variável” daqui em diante, por uma questão de brevidade) sem usar
declare
nada, por que você iria querer usar este pomposo “declarar “comando?A resposta está no fato de que a maneira implícita acima de declarar uma variável (
foo="bar"
), ela... implicitamente... faz com que o Bash considere essa variável como sendo do tipo que é mais comumente usado no cenário de uso típico de um shell.Esse tipo é o tipo string, ou seja, uma sequência de caracteres sem significado específico. Portanto, uma string é o que você obtém quando usa a declaração implícita.
Mas você, como programador, às vezes precisa considerar uma variável como, por exemplo, um número .. no qual você precisa fazer operações aritméticas .. e usar uma declaração implícita como
foo=5+6
não fará com que o Bash atribua o valor 11foo
como você faria Espero. Em vez disso, atribuirá àfoo
sequência dos três caracteres5
+
6
.Então .. você precisa de uma maneira de dizer ao Bash que você deseja
foo
ser considerado um número, não uma string .. e é para isso que um explícitodeclare
é útil.Apenas diga:
e o Bash felizmente fará as contas para você e atribuirá o valor numérico 11 à variável
foo
.Ou seja: ao dizer
declare -i foo
você dá à variávelfoo
o atributo de ser um número inteiro.Declarar números (precisamente inteiros, porque o Bash ainda não entende decimais, pontos flutuantes e tudo isso) pode ser a primeira razão para usar
declare
, mas não é a única razão. Como você já entendeu, existem alguns outros atributos que você pode dar às variáveis. Por exemplo, você pode ter Bash para sempre tornar o valor de uma variável em maiúscula, não importa o quê: se você disserdeclare -u foo
, a partir de então, quando você disserfoo=bar
Bash, na verdade, atribui a stringBAR
à variávelfoo
.Para dar qualquer um desses atributos a uma variável, você deve usar o
declare
comando, não há outra escolha.Agora, um outro dos atributos que você pode fornecer
declare
é o infame “name-ref”, o-n
atributo. ( E agora vou retomar o conceito que coloquei em espera anteriormente ).O atributo name-ref, basicamente, permite aos programadores Bash outra maneira de alterar o valor de uma variável. Mais precisamente, fornece uma maneira indireta de fazer isso.
Veja como funciona:
Você é
declare
uma variável que tem o-n
atributo, e é muito recomendado (embora não seja estritamente obrigatório, mas torna as coisas mais simples) que você também dê um valor para essa mesma variável no mesmodeclare
comando. Assim:Isso diz ao Bash que, a partir de então, cada vez que você usar ou alterar o valor da variável chamada
baz
, ele deverá realmente usar ou alterar o valor da variável chamadafoo
.O que significa que, a partir de então, você pode dizer algo como
baz=10+3
fazerfoo
obter o valor de 13. Assumindo, é claro, quefoo
foi declarado anteriormente como inteiro (declare -i
) como fizemos apenas um minuto atrás, caso contrário, obterá a sequência dos quatro personagens1
0
+
3
.Além disso: se você alterar
foo
o valor de 's diretamente, como emfoo=15
, você verá 15 também dizendoecho “${baz}”
. Isso ocorre porque a variávelbaz
declarada como name-ref defoo
sempre refletefoo
o valor de .O
declare -n
comando acima é chamado de “nome-referência” porque faz a variávelbaz
se referir ao nome de outra variável. De fato, declaramosbaz
ter o valor "foo" que, por causa da-n
opção, é tratado pelo Bash como o nome de outra variável.Agora, por que diabos você iria querer fazer isso?
Bem.. vale dizer que este é um recurso para necessidades bastante avançadas.
Na verdade, tão avançado que, quando um programador enfrenta um problema que realmente exigiria uma referência de nome, também é provável que esse problema deva ser resolvido usando uma linguagem de programação adequada em vez de Bash.
Uma dessas necessidades avançadas é, por exemplo, quando você, como programador, não consegue saber durante o desenvolvimento qual variável terá que usar em um ponto específico de um script, mas será totalmente conhecido dinamicamente em tempo de execução. E dado que não há como nenhum programador intervir em tempo de execução, a única opção é fazer uma provisão prévia para tal situação no script, e um “name-ref” pode ser a única maneira viável. Como um caso de uso amplamente conhecido dessa necessidade avançada, pense nos plug-ins, por exemplo. O programador de um programa “pluginable” precisa fazer provisão genérica para plug-ins futuros (e possivelmente de terceiros) de antemão. Portanto, o programador precisará usar recursos como um name-ref no Bash.
Uma outra necessidade avançada é quando você tem que lidar com uma grande quantidade de dados na RAM e também precisa passar esses dados para funções do seu script que também precisam modificar esses dados ao longo do caminho. Nesse caso, você certamente poderia copiar esses dados de uma função para outra (como Bash faz quando você faz
dest_var="${src_var}"
ou quando invoca funções como emmyfunc "${src_var}"
), mas sendo esses dados uma grande quantidade, isso geraria um enorme desperdício de RAM e muito operação ineficiente. Portanto, a solução se tais situações surgirem é usar não uma cópia dos dados, mas uma referênciaa esses dados. Em Bash, um nome-ref. Este caso de uso é realmente a norma em qualquer linguagem de programação moderna, mas é bastante excepcional quando se trata do Bash, porque o Bash é projetado principalmente para scripts simples e curtos que lidam principalmente com arquivos e comandos externos e, portanto, os scripts Bash raramente precisam passar grandes quantidade de dados entre as funções. E quando as funções de um script precisam compartilhar alguns dados (acessá-los e modificá-los), isso geralmente é obtido usando apenas uma variável global, o que é bastante normal em scripts Bash, tanto quanto é muito obsoleto em linguagens de programação adequadas.Então, pode haver um caso de uso notável para name-refs no Bash e (talvez ironicamente) está associado a quando você usa ainda outros tipos de variáveis:
declare -a
)declare -A
).These are a type of variables that may be more easily (as well as more efficiently) passed along functions by using name-refs instead of by normal copying, even when they don’t carry huge amounts of data.
If all these examples sound weird, and still incomprehensible, it’s only because name-refs are indeed an advanced topic, and a rare need for the typical usage scenario of Bash.
I could tell you about occasions on which I for one have found use for name-refs in Bash, but so far they have been mostly for quite “esoteric” and complicated needs, and I’m afraid that if I described them I would only complicate things for you at this point of your learning. Just to mention the least complex (and possibly not esoteric): returning values from functions. Bash does not really support this functionality, so I obtained the same by using name-refs. This, incidentally, is exactly what your example code does.
Besides this, a small personal advice, which would actually be better suited for a comment but I haven't been able to condense it enough to fit into StackExchange's comment's limits.
I think that the most you should do at the moment is to just experiment with name-refs by using the simple examples I showed and maybe with the example code you provided, disregarding for the moment the “why on earth” part and focusing only on the “how it works” part. By experimenting a bit, the “how” part may sink better into your mind, so that the “why” part will come clear to you in due time when (or if) you’ll have a real practical problem for which a name-ref would truly come in handy.
Em geral,
declare
nobash
shell define (ou remove ou exibe) atributos em variáveis. Um atributo é um tipo de anotação que diz "esta é uma referência de nome", ou "esta é uma matriz associativa", ou "esta variável deve sempre ser avaliada como um inteiro" ou "esta variável é somente leitura e não pode ser redefinido", ou "esta variável é exportada (uma variável de ambiente)" etc.O
typeset
built-in é sinônimo dedeclare
inbash
, comotypeset
é usado em outros shells (ksh
, onde se originou ezsh
, por exemplo) para definir atributos de variáveis.Olhando mais de perto o exemplo de referência de nome na pergunta:
A função shell que você mostra, com um pouco de código adicionado que a usa:
Executando isso:
Isso mostra que a
foo
variável não está sendo definida como nada quando o usuário insere duas strings diferentes .Isso mostra que a variável
foo
é definida para a string que o usuário digitou quando digitou a mesma string duas vezes.A maneira que
$foo
obtém o valorhello
na parte principal do script é pelas seguintes linhas na função shell:onde
$tmp1
é a stringhello
inserida pelo usuário e$1
é a stringfoo
passada na linha de comando da função da parte principal do script.Observe que a
ref
variável é declaradadeclare -n
como uma variável de referência de nome e que o valorfoo
é fornecido como o valor nessa declaração. Isso significa que a partir desse ponto, até que a variável saia do escopo, qualquer uso da variávelref
será o mesmo que usarfoo
. A variávelref
é uma variável de referência de nome referenciadafoo
neste ponto.Isso tem como consequência que atribuir um valor a
ref
, como é feito na linha após a declaração, atribuirá o valor afoo
.O valor
hello
está disponível na$foo
parte principal do script.