O que “ declara ” no Bash?

Depois de ler a resposta de ilkkachu “a esta pergunta , aprendi sobre a existência de declare (com argumento -n) shell integrado.

help declare traz:

Defina os valores e atributos das variáveis.

Declare as variáveis e atribua-lhes atributos. atributos e valores de todas as variáveis.

-n … faça de NAME uma referência à variável nomeada por seu valor

I peça uma explicação geral com um exemplo sobre declare porque eu não entendo o man. Eu sei o que é uma variável e expandi-la, mas ainda sinto falta do man em declare (atributo de 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 } 

Comentários

Resposta

Na maioria dos casos é suficiente com uma declaração implícita em bash

asdf="some text" 

Mas, às vezes você quer que o valor de uma variável seja apenas inteiro (portanto, no caso de ser alterado posteriormente, mesmo automaticamente, ele só poderia ser alterado para um número inteiro, o padrão é zero em alguns casos) e pode usar:

declare -i num 

ou

declare -i num=15 

Às vezes você deseja matrizes e, em seguida, precisa de declare

declare -a asdf # indexed type 

ou

Você pode encontrar bons tutoriais sobre arrays em bash ao navegar na Internet com a string de pesquisa” tutorial de matriz bash “(sem aspas), para exemplo

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


Acho que esses são os casos mais comuns quando você declara variáveis.


Observe também que

  • em uma função, declare torna a variável local (na função)
  • sem nenhum nome, ela lista todas as variáveis (no shell ativo)

    declare 

Finalmente, você obtém um breve resumo dos recursos do comando interno do shell declare em bash com o comando

help declare 

Comentários

  • Olá do OP! Achei ótima sua resposta e já votei positivamente e agradeço por essa resposta, na minha opinião, muito didática. Acabei de sugerir uma pequena edição que, em minha humilde opinião, torna ainda mais fácil e acessível para os novatos lerem; siga essa sugestão.
  • Acabei de ver que user:muru votou para rejeitar a edição; saiba que eu e muru ” entramos em conflito ” várias vezes nas diferentes seções deste site e presumo que sua rejeição não seja ‘ t objetivo e é realmente prejudicial em contraste com as alterações diretas apresentadas na página de sugestão de edição aqui: unix.stackexchange.com/review/suggested -edits / 325749
  • @JohnDoea, vi que também outro usuário rejeitou suas edições. Acho que algumas de suas edições são boas, mas algumas delas mudarão a mensagem que eu pretendia, então eu não gostaria delas. Posso editar a resposta para incluir o que considero boas edições.
  • obrigado; como você provavelmente sabe, rejeitar edições é muito mais comum no SE do que aceitá-las (mesmo que parcialmente); obrigado por me informar que outro usuário rejeitou a edição (eu não ‘ vi antes honestamente) e por considerar inserir pelo menos algumas das minhas edições em sua resposta; Eu respeito muito isso,

Resposta

A saída de help declare é bastante conciso. Uma explicação mais clara pode ser encontrada em man bash ou info bash – sendo o último a fonte para o que se segue.

Primeiro, algumas definições. Sobre variáveis e atributos :

Um parâmetro é uma entidade que armazena valores. … Uma variável é um parâmetro denotado por um name. Uma variável tem um valor e zero ou mais atributos . Os atributos são atribuídos usando o comando interno declare

E sobre o declare integrado :

declare

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

Declare variáveis e atribua a elas. Se nenhum nome for fornecido, exiba os valores das variáveis.

-n
Dê a cada nomeie o atributo nameref , tornando-o uma referência de nome para outra variável. Essa outra variável é definida pelo valor de nome . Todas as referências, atribuições e modificações de atributos para nome , exceto para aqueles que usam ou alteram o próprio -n atributo, são realizadas na variável referenciada por valor do nome . …

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 a declare e atributos de variáveis no Bash, gostaria de mostrar isso responda a ” O que declare name e declare -g fazem? ” (que se concentra principalmente no escopo de variáveis).


Basicamente 2 , declare name=[value] é equivalente à atribuição name=[value] com a qual você provavelmente está familiarizado. Em ambos os casos, name recebe o valor nulo valor se value estiver ausente.

Observe que o declare name ligeiramente diferente, em vez disso, não define a variável 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  

Assim, a variável name pode ser:

  • declarada e não definido , após declare name;
  • declarado e definido com null como valor, após name= ou declare name=;
  • declarado , definido e com um valor não nulo após name=value ou .

De forma mais geral, declare [options] name=value

  1. cria a variável 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 ;
  2. atribuições o valor value para ele;
  3. opcionalmente define os atributos name “s, que definem tanto o tipo de valor que pode armazenar (não em termos de um tipo , estritamente falando, visto que a linguagem do Bash não é digitada) e as maneiras como pode ser manipulado.

Os atributos são provavelmente mais fácil de explicar com um exemplo: usar declare -i name definirá o atributo ” inteiro ” de name, permitindo que seja tratado como um inteiro; citando o manual , ” a avaliação aritmética será realizada quando a variável receber um 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  

À luz de acima, o que está acontecendo no código de ilkkachu “é que:

  1. Uma variável chamada ref é declarada, com o ” nameref ” conjunto de atributos, e o conteúdo de $1 (o primeiro argumento posicional) é atribuído a it:

     declare -n ref="$1"  

    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 com antecedência, possivelmente porque queremos que seja definida dinamicamente (por exemplo, porque queremos reutilizar um trecho de código e tê-lo aplicado a várias variáveis), e para fornecem uma maneira conveniente de se referir a (e manipular). (Mas não o único: indireção é uma alternativa; consulte Expansão de parâmetros do shell ).

  2. Quando o valor da variável tmp1 é atribuído a ref:

     ref=$tmp1  

    uma variável adicional, cujo nome é o valor de ref, é declarada implicitamente. O valor de tmp1 também é indiretamente atribuído à variável declarada implicitamente por meio desta atribuição explícita a ref .

No contexto de sua pergunta vinculada , ligue para read_and_verify como

 read_and_verify domain "Prompt text here..."  

irá declarar a variável domain e atribua a ele o valor de tmp1 (ou seja, a entrada do usuário). É exatamente projetado para reutilizar o código que interage com o usuário e alavancar uma variável nameref para declarar domain e algumas outras variáveis.

Para dar uma olhada mais de perto na parte implícita, podemos reproduzir o processo passo a passo:

 ## 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 Referência: CHANGES arquivo, seção ” 3. Novos recursos no Bash “, ponto ” w “.
Isso pode ser relevante: por exemplo, CentOS Linux 7.6 (atualmente o versão mais recente) é enviado com Bash 4.2 .

2 Como de costume com shell builtins, uma explicação exaustiva e concisa é elusiva, pois eles executam várias ações, possivelmente heterogêneas. Vou me concentrar em declarar, atribuir e definir atributos apenas e vou considerar listar, definir o escopo e remover atributos como fora do escopo desta resposta.

3 Este comportamento de declare -p foi introduzido no Bash 4.4. Referência: CHANGES arquivo, seção ” 3. Novos recursos no Bash “, ponto ” f “.
Como G-Man apontado nos comentários, no Bash 4.3 declare name; declare -p name retorna um erro. Mas você ainda pode verificar se name existe com declare -p | grep "declare -- name".

4 FullBashGuide, Parâmetros em mywiki.wooledge.org

Comentários

  • (1) Não consigo reproduzir os resultados que você mostra em seu primeiro bloco de código: declare name seguido por declare -p name produz “bash: declare: nome: não encontrado”. (Embora declare -p | grep na gere declare -- name.) (2) Acredito que seja um pouco enganador apresentar echo "${name-isunset}" no contexto de declare name, visto que trata uma variável não declarada (isto é, indefinida) da mesma forma que uma declarada mas não configurada a variável. (3) Você pode querer mencionar que namerefs estão disponíveis apenas no bash versão 4.3 e superior.
  • @ G-Man Obrigado por seus comentários! Eu ‘ irei abordá-los assim que puder e ‘ atualizarei minha resposta quando apropriado. Quanto a (1), meu GNU bash, version 5.0.7(1)-release (x86_64-pc-linux-gnu) no Arch Linux ainda produz os resultados que mostrei. Talvez esse comportamento tenha sido introduzido apenas recentemente, ‘ vou investigar isso.
  • Sim; Eu ‘ m apenas usando a versão 4.3.
  • @ Resposta do G-Man atualizada com notas sobre (1) e (3). Sobre (2): tive como objetivo ilustrar que declare x não ‘ t define x, enquanto declare x= faz. Não consegui ‘ encontrar qualquer referência para apoiar a afirmação de que declare -- x (como resultado de declare -p x) significa ” não definido “, enquanto declare -- x="" significa ” set “; assim, trouxe a ${parameter-word} expansão, mesmo que não possa discriminar entre ” unset ” e ” não ‘ não existe “, como você indicou. No entanto, ‘ não tenho certeza de como posso esclarecer isso em minha resposta (sem distrair o leitor desse ponto).

Resposta

Vou tentar explicar isso, mas me perdoe se não seguirei o exemplo que você forneceu. Vou tentar guiá-lo ao longo de minha própria abordagem diferente.

Você diz que já entende conceitos como “variáveis” e “expandi-los”, etc., então vou apenas dar uma olhada em alguns conhecimento prévio que, de outra forma, exigiria um foco mais profundo.

Portanto, começarei dizendo que, no máximo básico nível, o comando declare é apenas uma maneira de dizer ao Bash que você precisa de um valor variável (ou seja, um valor que pode mudar durante a execução do script), e que você irá referir-se a esse valor usando um nome específico, precisamente o nome que você indica ao lado do próprio comando declare.

Isto é:

 declare foo="bar"  

diz ao Bash que você queremos que a variável chamada foo tenha o valor bar.

Mas .. espere um minuto .. podemos fazer isso sem usar declare, não podemos? Como em:

 foo="bar"  

Muito verdadeiro.

Bem , acontece que a atribuição simples acima é na verdade uma forma implícita de .. de fato .. declarar uma variável.

( Também acontece que o acima é uma de algumas maneiras de mude o valor da variável chamada foo; na verdade, é precisamente o mais direto, forma concisa, evidente, direta .. mas não é a única .. .. Voltarei a isso mais tarde .. ).

Mas então, se for assim bem possível declarar um “nome que marcará valores de variáveis” (apenas “variável” a partir de agora, por uma questão de brevidade) sem usar declare em tudo, por que você iria querer usa este comando “declarar” pomposo?

A resposta está no fato de que o acima implícito t maneira de declarar uma variável (foo="bar"), isso .. implicitamente .. faz o Bash considerar que a variável é do tipo que é mais comumente usado no cenário de uso típico para um shell .

Esse tipo é o tipo string, ou seja, uma sequência de caracteres sem nenhum significado particular. 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 aritmética operações .. e usar uma declaração implícita como foo=5+6 não fará com que o Bash atribua o valor 11 a foo como você pode esperar. Em vez disso, ele atribuirá a foo a sequência dos três caracteres 5 + 6.

Então … você precisa de uma maneira de dizer ao Bash que deseja que foo seja considerado um número, não um string .. e é para isso que um declare explícito é útil.

Basta dizer:

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

e o Bash ficará feliz em fazer as contas para você e atribuir o valor numérico 11 à variável foo.

Isto é: ao dizer declare -i foo você dá à variável foo 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 mais) pode ser o primeiro motivo para usar declare, mas não é o único motivo. Como você já entendeu, existem alguns outros atributos que você pode atribuir às variáveis. Por exemplo, você pode fazer com que o Bash sempre coloque o valor de uma variável em maiúsculas, não importa o que aconteça: se você disser declare -u foo, a partir de então quando disser foo=bar O Bash realmente atribui a string BAR à variável foo.

Para fornecer qualquer um desses atributos a uma variável, você deve usar o comando declare, não há outra escolha.


Agora, um outro dos atributos que você pode fornecer por meio de declare é o infame “nome-ref”, o -n atributo. ( E agora vou retomar o conceito que coloquei em espera anteriormente ).

O atributo name-ref, basicamente, permite aos programadores Bash uma outra maneira de alterar o valor de uma variável. É mais precisamente uma forma indireta de fazer isso.

Veja como funciona:

Você declare uma variável com o atributo -n, e é muito recomendado (embora não estritamente obrigatório, mas torna as coisas mais simples) que você também dê um valor para esta variável no mesmo declare comando. Assim:

 declare -n baz="foo"  

Isso diz ao Bash que, a partir de então ativada, cada vez que você usar ou alterar o valor da variável chamada baz, ele deve realmente usar ou alterar o valor da variável chamada foo.

O que significa que, a partir de então, você você pode dizer algo como baz=10+3 para fazer foo obter o valor de 13.Assumindo, é claro, que foo foi declarado anteriormente como inteiro (declare -i) como fizemos há apenas um minuto, caso contrário, obterá a sequência dos quatro caracteres 1 0 + 3.

Além disso: se você alterar o valor de foo diretamente, como em foo=15, verá 15 também dizendo echo “${baz}”. Isso ocorre porque a variável baz declarada como nome-ref de foo sempre reflete foo s valor.

O comando declare -n acima é chamado de “referência de nome” porque torna a variável baz consulte o nome de outra variável. Na verdade, declaramos que baz tem o valor “foo” que, devido à opção -n, é tratado pelo Bash como o nome para outra variável.

Agora, por que na Terra você gostaria de fazer isso?

Bem … vale a pena 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 um nome-ref, ele também é provavelmente, esse problema deve ser resolvido usando uma linguagem de programação adequada em vez de Bash.

Uma dessas necessidades avançadas é, por exemplo, quando você, como o programador, não pode saber durante o desenvolvimento qual variável você terá que usar em um ponto específico de um script, mas será totalmente conhecida 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 a provisão de antemão para tal situação no script, e um “nome-ref” pode ser o único viável maneira. Como um caso de uso amplamente conhecido dessa necessidade avançada, pense em plug-ins, por exemplo. O programador de um programa “apto para plug-ins” precisa fazer uma provisão genérica para plug-ins futuros (e possivelmente de terceiros) de antemão. Portanto, o programador precisará usar recursos como um nome-ref no Bash.

Uma outra necessidade avançada é quando você tem que lidar com uma grande quantidade de dados na RAM e você também precisa passar esses dados pelas funções de 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 o Bash faz quando você faz dest_var="${src_var}" ou quando invoca funções como em myfunc "${src_var}"), mas sendo esses dados uma grande quantidade, seria um grande desperdício de RAM e uma operação muito ineficiente. Portanto, a solução, se tais situações surgirem, é usar não uma cópia dos dados, mas uma referência a 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 de Bash, porque Bash é principalmente projetado para scripts simples e curtos que lidam principalmente com arquivos e comandos externos e, portanto, scripts Bash raramente têm que passar muito 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 é feito usando apenas uma variável global, o que é bastante comum em scripts Bash, por mais que seja muito obsoleto em linguagens de programação apropriadas.

Então, pode haver um caso de uso notável para nomes-refs no Bash, e (talvez ironicamente) ele está associado quando você usa outros tipos de variáveis:

  1. variáveis que são declaradas como “matrizes indexadas” (declare -a)
  2. variáveis que são declaradas como “matrizes associativas” (declare -A).

Esses são um tipo de variável que pode ser mais facilmente (e também mais eficiente) transmitida às funções usando name-refs em vez de cópia normal, mesmo quando eles não carregam grandes quantidades de dados.

Se todos esses exemplos soam estranhos, e ainda incompreensíveis, é apenas porque name-refs são de fato um tópico avançado e uma necessidade rara para o cenário de uso típico de B ash.

Eu poderia contar a você sobre ocasiões em que eu, por exemplo, achei uso para nomes-árbitros em Bash, mas até agora eles têm sido principalmente para necessidades bastante “esotéricas” e complicadas, e eu estou medo de que, se eu os descrevesse, só complicasse as coisas para você neste ponto de seu aprendizado. Apenas para mencionar o menos complexo (e possivelmente não esotérico): retornar valores de funções. Bash realmente não oferece suporte a essa funcionalidade, então obtive o mesmo usando name-refs. Isso, aliás, é exatamente o que seu código de exemplo faz.


Além disso, um pequeno conselho pessoal, que na verdade seria mais adequado para um comentário, mas não fui capaz de condensá-lo o suficiente para se ajustar aos limites de comentários do StackExchange.

Eu acho que o máximo que você deve fazer no momento é apenas experimentar com nomes-refs usando os exemplos simples que mostrei e talvez com o código de exemplo que você forneceu, desconsiderando por enquanto, a parte “por que na terra” e focando apenas na parte “como funciona”. Experimentando um pouco, a parte “como” pode afundar melhor em sua mente, de modo que a parte “por que” fique clara para você no devido tempo, quando (ou se) você terá um problema prático real para o qual um nome- ref seria realmente útil.

Comentários

  • LL3 Gosto muito da sua resposta e apaguei: finalmente entendi o que declara faz – – não declarando variáveis, mas declarando seus atributos; mas infelizmente perdi a noção quando você começou a explicar o que é um nome-ref. Não tenho ideia do que faz e por que usá-lo.
  • Isso é – por que alguém daria este atributo
  • @JohnDoea entendo. Talvez você possa apenas deixar este tópico por enquanto. Provavelmente é muito cedo para entender esse conceito no ponto atual de seu aprendizado. De qualquer forma, expandi minha resposta com mais exemplos e também um pequeno adendo pessoal sobre como acho que você deve proceder. Também coloquei linhas horizontais para delimitar a explicação geral de declare da explicação específica de -n do adendo pessoal. Também reformulei um pouco aqui e ali, mas nada substancial, então acho que você pode apenas reler a -n parte mais o pequeno adendo.
  • Obrigado, acho que ‘ posso aprender sobre isso agora, só preciso de uma explicação que possa entender e devo reler sua resposta inteira hoje, eu ‘ m aqui.
  • Você mencionou foo="bar" e declare foo="bar" . Você pode querer mencionar (mesmo que apenas em uma nota de rodapé) que, em uma função shell, declare foo="bar" cria uma variável local e foo="bar" cria um variável global.

Resposta

Em geral, declare em o bash 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 reconfigurado “ou” esta variável é exportada (uma variável de ambiente) “etc.

O typeset integrado é um sinônimo de declare em bash, já que typeset é usado em outros shells (ksh, de onde se originou, e zsh, por exemplo) para definir atributos de variáveis.


Olhando mais de perto o exemplo de referência de nome em a questão:

A função shell que você mostra, com um pouco de código adicionado que a 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" 

Executando isto:

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

Isso mostra que a variável foo não está sendo definida para nada quando o usuário insere dois diferentes strings.

Isso mostra que a variável foo é definida como a string que o usuário inseriu quando inseriu a mesma string duas vezes .

A maneira como $foo obtém o valor hello na parte principal do script é pelas seguintes linhas na função shell:

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

onde $tmp1 é a string hello inserida pelo usuário e $1 é a string foo passada na linha de comando da função a partir da parte principal do script.

Observe que a variável ref é declarada com declare -n como uma variável de referência de nome e que o valor foo é fornecido como o valor nessa declaração. Isso significa que daquele ponto em diante, até que a variável saia do escopo, qualquer uso da variável ref será o mesmo que usar foo. A variável ref é uma variável de referência de nome fazendo referência a foo 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 a foo.

O valor hello está então disponível em $foo na parte principal do script.

Deixe uma resposta

O seu endereço de email não será publicado. Campos obrigatórios marcados com *