One place for hosting & domains

      Definir

      Como ler e definir variáveis de ambiente e de shell no Linux


      Introdução

      Ao interagir com seu servidor através de uma sessão do shell, há muitas informações compiladas pelo seu shell para determinar o seu comportamento e acesso a recursos. Algumas dessas configurações estão contidas nas definições de configuração enquanto outras são determinadas pela entrada do usuário.

      Uma das maneiras pela qual o shell mantém o controle de todas essas configurações e detalhes é através de uma área mantida por ele chamada ambiente. O ambiente é uma área que o shell compila toda vez que inicia uma sessão que contém variáveis que definem propriedades do sistema.

      Neste guia, vamos discutir como interagir com o ambiente e ler ou definir variáveis de ambiente e de shell interativamente e por arquivos de configuração.

      Como o ambiente e variáveis de ambiente funcionam

      Sempre que uma sessão do shell é gerada, um processo ocorre para coletar e compilar informações que devem estar disponíveis ao processo shell e seus processos filhos. Ele obtém os dados para essas configurações de uma variedade de arquivos e configurações distintas no sistema.

      O ambiente fornece um meio pelo qual o processo shell pode obter ou definir configurações e, por sua vez, passá-las aos seus processos filhos.

      O ambiente é implementado como strings que representam pares de chave-valor. Se múltiplos valores forem passados, eles são normalmente separados por caracteres dois-pontos (:). Cada par será geralmente parecido com isto:

      KEY=value1:value2:...
      

      Se o valor contiver um espaço em branco significativo, as aspas são usadas:

      KEY="value with spaces"
      

      As chaves nesses cenários são variáveis. Elas podem ser de dois tipos, variáveis de ambiente ou variáveis de shell.

      As variáveis de ambiente são variáveis definidas para a sessão atual do shell e são herdades por qualquer shell ou processo filho. As variáveis de ambiente são usadas para passar informações para processos gerados a partir do shell.

      As variáveis de shell são variáveis contidas exclusivamente no shell no qual foram configuradas ou definidas. Geralmente, elas são usadas para manter o controle de dados efêmeros, como o diretório de trabalho atual.

      Por convenção, esses tipos de variáveis são geralmente definidos usando somente letras maiúsculas. Isso ajuda os usuários a distinguir as variáveis de ambiente em outros contextos.

      Imprimindo variáveis de ambiente e de shell

      Cada sessão do shell mantém o controle de suas variáveis de ambiente e de shell. Podemos acessá-las de algumas maneiras diferentes.

      É possível ver uma lista de todas as nossas variáveis de ambiente usando os comandos env ou printenv. Em seu estado padrão, elas devem funcionar exatamente da mesma maneira:

      Output

      SHELL=/bin/bash TERM=xterm USER=demouser LS_COLORS=rs=0:di=01;34:ln=01;36:mh=00:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:su=37;41:sg=30;43:ca:... MAIL=/var/mail/demouser PATH=/usr/local/bin:/usr/bin:/bin:/usr/local/games:/usr/games PWD=/home/demouser LANG=en_US.UTF-8 SHLVL=1 HOME=/home/demouser LOGNAME=demouser LESSOPEN=| /usr/bin/lesspipe %s LESSCLOSE=/usr/bin/lesspipe %s %s _=/usr/bin/printenv

      Isso é algo bastante típico para o resultado tanto de printenv quanto env. A diferença entre os dois comandos só é aparente em suas funcionalidades mais específicas. Por exemplo, com printenv, é possível solicitar os valores de variáveis individuais:

      Output

      /bin/bash

      Por outro lado, o env lhe permite modificar o ambiente no qual os programas são executados passando um conjunto de definições de variável em um comando, como este:

      • env VAR1="value" command_to_run command_options

      Considerando que, assim como aprendemos acima, os processos filhos normalmente herdam as variáveis de ambiente do processo pai, isso lhe dá a oportunidade de substituir valores ou adicionar variáveis adicionais ao filho.

      Como você pode ver no resultado do nosso comando printenv, há uma quantidade razoável de variáveis de ambiente configuradas em nossos arquivos e processos de sistema sem a nossa intervenção.

      Esses comandos mostram as variáveis de ambiente, mas como vemos as variáveis de shell?

      O comando set pode ser usado para isso. Se digitarmos set sem nenhum parâmetro adicional, iremos receber uma lista de todas as variáveis de shell, variáveis de ambiente, variáveis locais e funções shell:

      Output

      BASH=/bin/bash BASHOPTS=checkwinsize:cmdhist:expand_aliases:extglob:extquote:force_fignore:histappend:interactive_comments:login_shell:progcomp:promptvars:sourcepath BASH_ALIASES=() BASH_ARGC=() BASH_ARGV=() BASH_CMDS=() . . .

      Normalmente, essa é uma lista enorme. Provavelmente, é desejável canalizá-la em um programa pager para manusear mais facilmente todo o resultado:

      A quantidade de informações adicionais recebidas de volta pode parecer muito grande. Provavelmente, não é necessário conhecer todas as funções bash que estão definidas, por exemplo.

      Podemos limpar o resultado especificando que set deve operar no modo POSIX, que não irá imprimir as funções shell. Podemos executá-lo em um subshell para que ele não altere nosso ambiente atual:

      Isso irá listar todas as variáveis de ambiente e de shell que estão definidas.

      Podemos tentar comparar esse resultado com o resultado dos comandos env ou printenv para tentar obter uma lista com apenas as variáveis de shell, mas isso não será perfeito, devido às diferentes maneiras pelas quais esses comandos geram os resultados:

      • comm -23 <(set -o posix; set | sort) <(env | sort)

      Isso provavelmente ainda irá incluir algumas variáveis de ambiente, pois o comando set exibe valores entre aspas, enquanto o printenv e env não colocam os valores de strings entre aspas.

      Isso deve lhe dar uma boa noção das variáveis de ambiente e de shell que estão definidas em sua sessão.

      Essas variáveis são usadas para todo tipo de coisa. Elas oferecem uma forma alternativa de definir valores persistentes para a sessão entre processos, sem gravar alterações em um arquivo.

      Variáveis de ambiente e de shell comuns

      Algumas variáveis de ambiente e de shell são muito úteis e são referenciadas com bastante frequência. Aqui estão algumas variáveis de ambiente comuns que você deve encontrar:

      • SHELL: descreve o shell que irá interpretar todos os comandos que você digitar. Geralmente, isso será bash por padrão, mas outros valores podem ser definidos caso prefira outras opções.
      • TERM: especifica o tipo de terminal a ser emulado ao executar o shell. Diferentes terminais de hardware podem ser emulados para diferentes requisitos de operação. Normalmente, não será necessário se preocupar com isso.
      • USER: o usuário que está atualmente conectado.
      • PWD: o diretório de trabalho atual.
      • OLDPWD: o diretório de trabalho anterior. Isso é mantido pelo shell para retornar ao seu diretório anterior executando cd -.
      • LS_COLORS: define códigos de cor que são usados para adicionar um resultado de cor opcional ao comando ls. Isso é usado para distinguir diferentes tipos de arquivos e fornecer mais informações ao usuário em um olhar rápido.
      • MAIL: o caminho para a caixa de correio do usuário atual.
      • PATH: uma lista de diretórios que o sistema irá verificar ao procurar por comandos. Quando um usuário digita um comando, o sistema irá verificar diretórios neste pedido para o executável.
      • LANG: a configuração atual de idioma e localização, incluindo a codificação de caracteres.
      • HOME: o diretório base do usuário atual.
      • _: o comando executado anteriormente mais recente.

      Além dessas variáveis de ambiente, algumas variáveis de shell que você verá com frequência são:

      • BASHOPTS: a lista de opções que foram usadas quando o bash foi executado. Isso pode ser útil para descobrir se o ambiente do shell irá operar da maneira que você deseja.
      • BASH_VERSION: a versão do bash sendo executada, de forma legível por humanos.
      • BASH_VERSINFO: a versão do bash, em saída legível por máquina.
      • COLUMNS: o número de colunas de largura que estão sendo usadas para exibir um resultado na tela.
      • DIRSTACK: a pilha de diretórios que estão disponíveis com os comandos pushd e popd.
      • HISTFILESIZE: número de linhas de histórico de comando armazenadas em um arquivo.
      • HISTSIZE: número de linhas de histórico de comando permitidas na memória.
      • HOSTNAME: o nome de host do computador neste momento.
      • IFS: o separador de campo interno para separar entradas na linha de comando. Por padrão, é um espaço.
      • PS1: a de definição de prompt de comando primário. Isso é usado para definir a aparência do seu prompt quando inicia a sessão do shell. O PS2 é usado para declarar prompts secundários para quando um comando ocupa várias linhas.
      • SHELLOPTS: opções de shell que podem ser definidas com a opção set.
      • UID: o UID do usuário atual.

      Definindo variáveis de ambiente e de shell

      Para entender melhor a diferença entre as variáveis de ambiente e de shell, e para apresentar a sintaxe para definir essas variáveis, faremos uma pequena demonstração.

      Criando variáveis de shell

      Vamos começar definindo uma variável de shell dentro de nossa sessão atual. Isso é de fácil execução. Precisamos especificar apenas um nome e um valor. Vamos aderir à convenção de manter todas as letras maiúsculas para o nome da variável e defini-las em uma string simples.

      Aqui, usamos aspas, pois o valor de nossa variável contém um espaço. Além disso, usamos aspas simples, pois o ponto de exclamação é um caractere especial no bash do shell que normalmente expande para o histórico do bash se não forem colocados caracteres de escape ou colocado entre aspas simples.

      Agora, temos uma variável de shell. Essa variável está disponível em nossa sessão atual, mas não será passada para processos filhos.

      Podemos ver isso usando o grep para nossa nova variável dentro do resultado de set:

      Output

      TEST_VAR='Hello World!'

      Podemos verificar se essa não é uma variável de ambiente testando a mesma coisa com o printenv:

      Nenhum resultado deve ser retornado.

      Vamos aproveitar isso como uma oportunidade para demonstrar uma maneira de acessar o valor de qualquer variável de ambiente ou de shell.

      Output

      Hello World!

      Como você pode ver, faça referência ao valor de uma variável executando-a com um sinal de $. O shell recebe isso e entende que deve substituir o valor da variável quando se depara com isso.

      Então, agora, temos uma variável de shell. Ela não deve ser passada para nenhum processo filho. Podemos gerar um novo shell do bash de dentro do atual para demonstrar isso:

      Se digitarmos bash para gerar um shell filho e então tentarmos acessar o conteúdo da variável, nada será retornado. Isso é exatamente o que esperávamos.

      Volte para o nosso shell original digitando exit:

      Criando variáveis de ambiente

      Agora, vamos transformar nossa variável de shell em uma variável de ambiente. Podemos fazer isso exportando a variável. O comando para isso é nomeado apropriadamente:

      Isso irá alterar nossa variável para uma variável de ambiente. Podemos verificar isso olhando nossa listagem de ambiente novamente:

      Output

      TEST_VAR=Hello World!

      Desta vez, nossa variável aparece. Vamos tentar nosso experimento com o nosso shell filho novamente:

      Output

      Hello World!

      Ótimo! Nosso shell filho recebeu a variável definida pelo seu pai. Antes de sairmos deste shell filho, vamos tentar exportar outra variável. Podemos definir variáveis de ambiente em um único passo, desta forma:

      • export NEW_VAR="Testing export"

      Teste se ele foi exportado como uma variável de ambiente:

      Output

      NEW_VAR=Testing export

      Agora, vamos voltar para o nosso shell original:

      Vamos ver se nossa nova variável está disponível:

      Nada é retornado.

      Isso ocorre porque variáveis de ambiente são passadas apenas para processos filhos. Não existe uma maneira integrada de definir variáveis de ambiente do shell pai. Isso é bom na maioria dos casos e impede que os programas afetem o ambiente operacional de onde foram chamados.

      A variável NEW_VAR foi definida como uma variável de ambiente em nosso shell filho. Essa variável estaria disponível para si mesma e qualquer um de seus shells e processos filhos. Quando saímos e voltamos para o nosso shell principal, aquele ambiente foi destruído.

      Rebaixando e removendo variáveis

      Ainda temos nossa variável TEST_VAR definida como uma variável de ambiente. Podemos revertê-la para uma variável de shell digitando:

      Ela não é mais uma variável de ambiente:

      No entanto, ela ainda é uma variável de shell:

      Output

      TEST_VAR='Hello World!'

      Se quisermos remover completamente uma variável, seja ela de ambiente ou de shell, podemos fazê-lo com o comando unset:

      Podemos verificar se ela não está mais definida:

      Nada é retornado porque a variável foi removida.

      Definindo variáveis de ambiente no login

      Já mencionamos que muitos programas usam variáveis de ambiente para decidir as especificações de como operar. Não é interessante precisar definir variáveis importantes toda vez que iniciarmos uma sessão do shell. Além disso, já vimos quantas já são definidas no login. Sendo assim, como criamos e definimos variáveis automaticamente?

      Esse é realmente um problema mais complexo do que parece, devido aos inúmeros arquivos de configuração que o shell do bash lê, dependendo de como é iniciado.

      A diferença entre as sessões do shell com login, sem login, interativa e não interativa

      O shell do bash lê arquivos de configuração diferentes dependendo da forma como a sessão é iniciada.

      Uma distinção entre sessões diferentes é se o shell está sendo gerado como uma sessão com login ou sem login.

      Um shell com login é uma sessão de shell que começa pela autenticação do usuário. Se você estiver entrando em uma sessão de terminal ou via protocolo SSH e autenticar-se, sua sessão de shell será definida como um shell com login.

      Se você iniciar uma nova sessão de shell dentro de sua sessão autenticada, como fizemos chamando o comando bash do terminal, uma sessão sem login de shell será iniciada. Seus detalhes de autenticação não foram solicitados quando você iniciou seu shell filho.

      Outra distinção que pode ser feita é se uma sessão de shell é ou não interativa.

      Uma sessão de shell interativa é uma sessão que está anexada a um terminal. Uma sessão de shell não interativa é uma que não está anexada a uma sessão de terminal.

      Dessa forma, cada sessão de shell é classificada como sendo com ou sem login e interativa ou não interativa.

      Uma sessão normal que começa com o SSH é geralmente uma sessão de shell interativa com login. Um script executado a partir da linha de comando é geralmente executado em um shell não interativo e sem login. Uma sessão de terminal pode ser qualquer combinação dessas duas propriedades.

      A classificação da sessão de shell como sendo com ou sem login tem implicações sobre quais arquivos são lidos para inicializar a sessão de shell.

      Uma sessão iniciada como sessão com login lerá detalhes de configuração do arquivo /etc/profile primeiro. Em seguida, ela irá procurar pelo primeiro arquivo de configuração de shell de login no diretório base do usuário para obter detalhes de configuração específicos do usuário.

      Ela lê o primeiro arquivo que achar entre ~/.bash_profile, ~/.bash_login e ~/.profile e não lê nenhum arquivo adicional.

      Em contraste, uma sessão definida como um shell sem login lerá /etc/bash.bashrc e então o arquivo ~/.bashrc específico do usuário para compilar seu ambiente.

      Os shells não interativos leem a variável de ambiente chamada BASH_ENV e o arquivo especificado para definir o novo ambiente.

      Implementando variáveis de ambiente

      Como você pode ver, há uma grande variedade de arquivos diferentes que normalmente precisaríamos observar para colocar nossas configurações.

      Isso oferece uma grande flexibilidade que pode ajudar em situações específicas em que queremos certas configurações em um shell com login e outras configurações em um shell sem login. No entanto, na maior parte do tempo, vamos querer as mesmas configurações em ambas as situações.

      Felizmente, a maioria das distribuições Linux configuram os arquivos de configuração de login para originar os arquivos de configuração sem login. Isso significa que é possível definir as variáveis de ambiente que desejar tanto em arquivos com login, quanto nos sem login. Eles serão então lidos em ambos os cenários.

      Normalmente, vamos definir variáveis de ambiente específicas para cada usuário, e geralmente vamos querer que nossas configurações estejam disponíveis tanto em shells com login quanto sem login. Isso significa que o lugar para definir essas variáveis é no arquivo ~/.bashrc.

      Abra esse arquivo agora:

      Provavelmente, ele já irá conter uma quantidade considerável de dados. A maioria das definições aqui presentes são para definir opções do bash, que não estão relacionadas às variáveis de ambiente. É possível definir variáveis de ambiente da mesma forma que você faria da linha de comando:

      Qualquer nova variável de ambiente pode ser adicionada em qualquer lugar no arquivo ~/.bashrc, desde que não seja colocada no meio de outro comando ou loop for. Em seguida, podemos salvar e fechar o arquivo. Da próxima vez que você iniciar uma sessão do shell, sua declaração de variável de ambiente será lida e transmitida para o ambiente do shell. É possível forçar sua sessão atual a ler o arquivo agora digitando:

      Se precisar definir variáveis em todo o sistema, pense em adicioná-las ao /etc/profile, /etc/bash.bashrc ou /etc/environment.

      Conclusão

      As variáveis de ambiente e de shell estão sempre presentes em suas sessões de shell e podem ser muito úteis. Elas são uma maneira interessante para um processo pai definir detalhes de configuração para seus filhos, além de representarem uma maneira de definir opções fora dos arquivos.

      Isso oferece muitas vantagens em situações específicas. Por exemplo, a implantação de alguns mecanismos dependem de variáveis de ambiente para configurar informações de autenticação. Isso é útil, pois não requer a manutenção desses arquivos que podem ser vistos por partes externas.

      Existem muitos outros cenários, mais mundanos e comuns em que você precisará ler ou alterar o ambiente do seu sistema. Essas ferramentas e técnicas devem lhe dar uma boa base para fazer essas alterações e usá-las corretamente.



      Source link

      Como definir e chamar funções em Go


      Introdução

      Uma função é uma seção do código que, uma vez definida, pode ser reutilizada. As funções são usadas para deixar seu código mais fácil de entender: o código é dividido em tarefas menores e compreensíveis, que podem ser usadas mais de uma vez ao longo do programa.

      O Go vem com uma biblioteca padrão poderosa que possui várias funções predefinidas. As funções do pacote fmt, com as quais provavelmente você já está familiarizado, são:

      • fmt.Println() – que imprimirá objetos para a saída padrão (provavelmente o seu terminal).
      • fmt.Printf() – que permite que você formate seu resultado impresso.

      Os nomes das funções incluem parênteses e podem incluir parâmetros.

      Neste tutorial, vamos explicaremos sobre como definir suas próprias funções para usar em seus projetos de código.

      Definindo uma função

      Vamos começar a transformar o programa clássico “Hello, World!” em uma função.

      Criaremos um novo arquivo de texto em nosso editor de textos preferido e chamaremos o programa hello.go. Então, vamos definir a função.

      Uma função é definida usando a palavra-chave func. Depois dessa palavra-chave vem um nome de sua escolha e um conjunto de parênteses que possui quaisquer parâmetros que a função receberá (elas podem ficar vazias). As linhas de código da função ficam entre chaves {}.

      Neste caso, vamos definir uma função chamada hello():

      hello.go

      func hello() {}
      

      Isso define a instrução inicial para a criação de uma função.

      A partir daqui, adicionaremos uma segunda linha para fornecer as instruções para o que a função faz. Neste caso, vamos imprimir Hello, World! para o console:

      hello.go

      func hello() {
          fmt.Println("Hello, World!")
      }
      

      Nossa função agora está totalmente definida; porém, se executarmos o programa neste ponto, nada acontecerá, pois não chamamos a função.

      Assim, dentro do nosso bloco de funções main(), vamos chamar a função com hello():

      hello.go

      package main
      
      import "fmt"
      
      func main() {
          hello()
      }
      
      func hello() {
          fmt.Println("Hello, World!")
      }
      

      Agora, vamos executar o programa:

      Você receberá o seguinte resultado:

      Output

      Hello, World!

      Note que também introduzimos uma função chamada main(). A função main() é uma função especial que diz ao compilador que é aqui que o programa deve iniciar. Para qualquer programa que você queira que seja executável (um programa que pode ser executado a partir da linha de comando), você vai precisar de uma função main(). A função main() deve aparecer apenas uma vez, estar no pacote main() e não deve receber nem retornar argumentos. Isso permite a execução do programa em qualquer programa em Go. De acordo com o exemplo a seguir:

      main.go

      package main
      
      import "fmt"
      
      func main() {
          fmt.Println("this is the main section of the program")
      }
      

      As funções podem ficar mais complicadas do que a função hello() que definimos. Podemos usar loops for, instruções condicionais e mais, dentro do nosso bloco de função.

      Por exemplo, a função a seguir usa uma instrução condicional para verificar se a entrada da variável name contém uma vogal; depois, ela usa um loop for para iterar nas letras da string name.

      names.go

      package main
      
      import (
          "fmt"
          "strings"
      )
      
      func main() {
          names()
      }
      
      func names() {
          fmt.Println("Enter your name:")
      
          var name string
          fmt.Scanln(&name)
          // Check whether name has a vowel
          for _, v := range strings.ToLower(name) {
              if v == 'a' || v == 'e' || v == 'i' || v == 'o' || v == 'u' {
                  fmt.Println("Your name contains a vowel.")
                  return
              }
          }
          fmt.Println("Your name does not contain a vowel.")
      }
      

      A função name() que definimos aqui define uma variável name com entrada e, em seguida, define uma instrução condicional dentro de um loop for. Isso mostra como o código pode ser organizado dentro de uma definição de função. No entanto, dependendo do que pretendemos com nosso programa e como queremos configurar nosso código, podemos querer definir a instrução condicional e o loop for como duas funções separadas.

      Definir funções dentro de um programa torna o nosso código modular e reutilizável para que possamos chamar as mesmas funções sem reescrevê-las.

      Até agora, examinamos funções com parênteses vazios, que não recebem argumentos, mas podemos definir parâmetros nas definições da função dentro de seus parênteses.

      Um parâmetro é uma entidade nomeada na definição de uma função, que especifica um argumento que a função pode aceitar. Na linguagem Go, você deve especificar o tipo de dados de cada parâmetro.

      Vamos criar um programa que repete uma palavra pelo número de vezes especificado. O programa aceitará um parâmetro de string, chamado word e um parâmetro de int chamado reps em relação ao número de vezes a se repetir a palavra.

      repeat.go

      package main
      
      import "fmt"
      
      func main() {
          repeat("Sammy", 5)
      }
      
      func repeat(word string, reps int) {
          for i := 0; i < reps; i++ {
              fmt.Print(word)
          }
      }
      

      Enviamos o valor Sammy para o parâmetro word e 5 para o parâmetro reps. Esses valores correspondem a cada parâmetro na ordem que eles foram dados. A função repeat tem um loop for que irá iterar pelo número de vezes especificado pelo parâmetro reps. Para cada iteração, o valor do parâmetro word será impresso.

      Aqui está o resultado do programa:

      Output

      SammySammySammySammySammy

      Se você tiver um conjunto de parâmetros - todos com o mesmo valor, você pode omitir, especificando o tipo a cada vez. Vamos criar um pequeno programa que recebe os parâmetros x, y e z que estão todos com valores int. Criaremos uma função que adiciona os parâmetros juntos, em configurações diferentes. As somas deles serão impressos pela função. Então, vamos chamar a função e enviar números para a função.

      add_numbers.go

      package main
      
      import "fmt"
      
      func main() {
          addNumbers(1, 2, 3)
      }
      
      func addNumbers(x, y, z int) {
          a := x + y
          b := x + z
          c := y + z
          fmt.Println(a, b, c)
      }
      

      Ao criarmos a assinatura da função para addNumbers, não precisamos especificar o tipo a cada vez, apenas no final.

      Enviamos o número 1 para o parâmetro x, o 2 para o parâmetro y e o 3 para o parâmetro z. Esses valores correspondem a cada parâmetro na ordem em que eles são dados.

      O programa está fazendo a seguinte operação matemática, com base nos valores que enviamos para os parâmetros:

      a = 1 + 2
      b = 1 + 3
      c = 2 + 3
      

      A função também imprime a, b e c e, com base nessa operação matemática, esperamos que a seja igual a 3, b igual a 4 e c igual a 5. Vamos executar o programa:

      Output

      3 4 5

      Quando enviamos 1, 2 e 3 como parâmetros para a função addNumbers(), recebemos o resultado esperado.

      Os parâmetros são argumentos, normalmente definidos como variáveis nas definições da função. Ao executar o método, você pode atribuir valores aos parâmetros, enviando os argumentos para a função.

      Retornando um valor

      Você pode enviar um valor de parâmetro para uma função e uma função também pode produzir um valor.

      Uma função pode produzir um valor com a instrução return, o qual sairá de uma função e enviará, opcionalmente, uma expressão de volta para o chamador. O tipo de dados retornados também deve ser especificado.

      Até agora, usamos a instrução fmt.Println() em vez da instrução return em nossas funções. Vamos criar um programa que, em vez de imprimir, retornará uma variável.

      Em um novo arquivo de texto chamado double.go, vamos criar um programa que duplica o parâmetro x e retorna a variável y. Nós emitimos uma chamada para imprimir a variável result, que é formada por meio da execução da função double() com um 3 enviado para ela:

      double.go

      package main
      
      import "fmt"
      
      func main() {
          result := double(3)
          fmt.Println(result)
      }
      
      func double(x int) int {
          y := x * 2
          return y
      }
      
      

      Podemos executar o programa e ver o resultado:

      Output

      6

      O número inteiro 6 é retornado como o resultado, que se trata do que esperávamos da multiplicação de 3 por 2.

      Se uma função especifica um retorno, você deve fornecer um retorno como parte do código. Se não fizer isso, receberá um erro de compilação.

      Podemos demonstrar isso, comentando a linha (do código) com a instrução de retorno:

      double.go

      package main
      
      import "fmt"
      
      func main() {
          result := double(3)
          fmt.Println(result)
      }
      
      func double(x int) int {
          y := x * 2
          // return y
      }
      
      

      Agora, vamos executar o programa novamente:

      Output

      ./double.go:13:1: missing return at end of function

      Sem usar a instrução return aqui, o programa não pode compilar.

      As funções saem imediatamente quando atingem a instrução return, mesmo se elas não estiverem no final da função:

      return_loop.go

      package main
      
      import "fmt"
      
      func main() {
          loopFive()
      }
      
      func loopFive() {
          for i := 0; i < 25; i++ {
              fmt.Print(i)
              if i == 5 {
                  // Stop function at i == 5
                  return
              }
          }
          fmt.Println("This line will not execute.")
      }
      

      Aqui, iteramos um loop for e dizemos ao loop para executar 25 iterações. No entanto, dentro do loop for, temos uma instrução condicional if que verifica se o valor de i é igual a 5. Se for, emitimos uma instrução return. Como estamos na função loopFive, qualquer return em qualquer ponto na função sairá da função. Consequentemente, nunca chegaremos à última linha dessa função para imprimir a instrução This line will not execute. (Esta linha não será executada.).

      Usar a instrução return dentro do loop for encerra a função, de modo que a linha que está fora do loop não será executada. Se, em vez disso, tivéssemos usado uma instrução break, apenas o loop teria saído naquele momento e a última linha da fmt.Println() seria executada.

      A instrução return sai de uma função e pode retornar um valor se especificado na assinatura da função.

      Retornando valores múltiplos

      Mais de um valor retornado pode ser especificado para uma função. Vamos examinar o programa repeat.go e fazer com que ele retorne dois valores. O primeiro será o valor repetido e o segundo será um erro se o parâmetro reps não for um valor maior que 0:

      repeat.go

      package main
      
      import "fmt"
      
      func main() {
          val, err := repeat("Sammy", -1)
          if err != nil {
              fmt.Println(err)
              return
          }
          fmt.Println(val)
      }
      
      func repeat(word string, reps int) (string, error) {
          if reps <= 0 {
              return "", fmt.Errorf("invalid value of %d provided for reps. value must be greater than 0.", reps)
          }
          var value string
          for i := 0; i < reps; i++ {
              value = value + word
          }
          return value, nil
      }
      

      A primeira coisa que a função repeat faz é verificar se o argumento reps é um valor válido. Qualquer valor que não seja maior do que 0 causará um erro. Como enviamos o valor de -1, essa ramificação do código será executada. Note que, quando retornamos da função, precisamos fornecer os valores retornados string e error. Como os argumentos fornecidos resultaram em um erro, vamos enviar uma string em branco de volta para o primeiro valor retornado e o erro para o segundo valor retornado.

      Na função main() podemos receber ambos valores retornados, declarando duas novas variáveis, value e err. Como pode haver um erro no retorno, vamos verificar se recebemos um erro antes de continuar com nosso programa. Neste exemplo, recebemos um erro. Nós imprimimos o erro e o return da função main() para sair do programa.

      Se não houvesse um erro, imprimiríamos o valor retornado da função.

      Nota: é considerada uma melhor prática retornar apenas dois ou três valores. Além disso, você deve sempre retornar todos os erros como o último valor de retorno de uma função.

      Executar o programa resultará no seguinte resultado:

      Output

      invalid value of -1 provided for reps. value must be greater than 0.

      Nesta seção, analisamos como podemos usar a instrução return para retornar valores múltiplos de uma função.

      Conclusão

      As funções são blocos de código de instruções que realizam ações dentro de um programa, ajudando a tornar o nosso código reutilizável e modular.

      Para aprender mais sobre como tornar seu código mais modular, leia o nosso guia sobre Como escrever pacotes em Go.



      Source link

      Usando ldflags para definir informações de versão em aplivativos Go


      Introdução

      Ao implantar aplicativos em um ambiente de produção, a compilação de binários com informações de versão e outros metadados irá melhorar seu processo de monitoramento, registro e depuração através da adição de informações de identificação para ajudar a rastrear suas compilações ao longo do tempo. Essas informações de versão com frequência podem incluir dados altamente dinâmicos, como o tempo de compilação, a máquina ou o usuário que compila o binário, o ID de confirmação do Sistema de Controle de Versão (VCS) em relação ao qual foi compilado, entre outras coisas. Como esses valores estão em constante mudança, codificar esses dados diretamente no código fonte e modificá-los antes de cada nova compilação é um processo tedioso e propenso a erros: os arquivos fonte podem mover-se e as variáveis/constantes podem trocar arquivos ao longo do desenvolvimento, interrompendo o processo de compilação.

      Uma maneira de resolver isso em Go é usando -ldflags com o comando go build para inserir informações dinâmicas no binário no momento da compilação, sem a necessidade de modificar códigos fonte. Neste identificador, o ld significa linker [vinculador], o programa que vincula os diferentes pedaços do código fonte compilado em um binário final. ldflags, então, significa *linker flags *[identificadores de vinculador]. Ele recebe esse nome porque passa um identificador para o conjunto subjacente do vinculador da cadeia de ferramentas em Go, cmd/link, que permite que você altere os valores de pacotes importados no momento da compilação a partir da linha de comando.

      Neste tutorial, você usará -ldflags para alterar o valor das variáveis no momento da compilação e introduzir suas próprias informações dinâmicas em um binário, usando um aplicativo exemplo que imprime informações de versão para a tela.

      Pré-requisitos

      Para seguir o exemplo neste artigo, será necessário:

      Compilando seu aplicativo exemplo

      Antes de poder usar ldflags para introduzir dados dinâmicos, será necessário primeiro um aplicativo no qual inserir as informações. Neste passo, você criará esse aplicativo, o qual, por enquanto,imprimirá apenas informações sobre o controle de versão estática. Vamos criar esse aplicativo agora.

      No seu diretório src, crie um diretório com o nome do seu aplicativo. Este tutorial usará o nome de aplicativo app:

      Mude seu diretório de trabalho para essa pasta:

      Em seguida, usando o editor de texto de sua escolha, crie o ponto de entrada do seu programa, main.go:

      Agora, faça seu aplicativo imprimir informações de versão, adicionando o seguinte conteúdo:

      app/main.go

      package main
      
      import (
          "fmt"
      )
      
      var Version = "development"
      
      func main() {
          fmt.Println("Version:t", Version)
      }
      

      Dentro da função main(), você declarou a variável Version, em seguida imprimiu a string Version:, seguida de um caractere de guia (tab), t e, na sequência declarou a variável.

      Neste ponto, a variável Version foi definida como development, a qual será a versão padrão desse app. Mais tarde, você trocará esse valor por um número oficial de versão, organizado segundo o formato semântico para controle de versão.

      Salve e saia do arquivo. Assim que terminar, compile e execute o aplicativo para confirmar que ele imprime a versão correta:

      Você verá o seguinte resultado:

      Output

      Agora, você tem um aplicativo que imprime informações da versão padrão, mas ainda não tem como enviar as informações da versão atual no momento da compilação. No próximo passo, você usará -ldflags e go build para resolver esse problema.

      Assim como mencionado anteriormente, ldflags significa identificadores de vinculador e é usado para enviar identificadores para o vinculador subjacente na cadeia de ferramentas Go. Isso funciona de acordo com a seguinte sintaxe:

      • go build -ldflags="-flag"

      Nesse exemplo, transmitimos o flag para o comando go tool link subjacente que executa como parte do go build. Esse comando usa aspas duplas ao redor do conteúdo transmitido para os ldflags para evitar quebrar caracteres nele, ou caracteres que a linha de comando possa interpretar como algo diferente do que queremos. A partir daqui, você poderia enviar muitos e diferentes identificadores de link. Para os fins deste tutorial, usaremos o identificador -X para gravar informações na variável no momento de vincular, seguido do caminho do pacote até a variável e seu novo valor:

      • go build -ldflags="-X 'package_path.variable_name=new_value'"

      Dentro das aspas, há agora a opção -X e um par chave-valor que representa a variável a ser alterada e seu novo valor. O caractere . separa o caminho do pacote e o nome da variável e aspas únicas são usadas para evitar a quebra de caracteres no par chave-valor.

      Para substituir a variável Version no seu aplicativo exemplo, use a sintaxe no último bloco de comando para enviar um novo valor e compilar o novo binário:

      • go build -ldflags="-X 'main.Version=v1.0.0'"

      Neste comando, main é o caminho de pacote da variável Version, uma vez que essa variável está no arquivo main.go. Version é a variável para a qual está gravando, e o v1.0.0 é o novo valor.

      Para usar o ldflags, o valor que você quiser alterar deve existir e ser uma variável do nível de pacote do tipo string. Essa variável pode ser exportada ou não exportada. O valor não pode ser const ou ter seu valor definido pelo resultado de uma chamada de função. Felizmente, Version se encaixa em todos esses requisitos: ela já foi declarada como variável no arquivo main.go, assim como o valor atual (development) e o valor desejado (v1.0.0) são ambos strings.

      Assim que seu novo binário app for compilado, execute o aplicativo:

      Você receberá o seguinte resultado:

      Output

      Usando -ldflags, você mudou com sucesso a variável Version de development para v1.0.0.

      Agora, você modificou uma variável string dentro de um aplicativo simples na hora da compilação. Usando ldflags, você pode inserir detalhes de versão, informações de licenciamento e outras coisas em um binário pronto para a distribuição, usando apenas a linha de comando.

      Neste exemplo, a variável que você mudou estava no programa main, o que reduz a dificuldade em determinar o nome do caminho. No entanto, as vezes, o caminho para essas variáveis é mais complicado de se encontrar. No próximo passo, você irá gravar valores para as variáveis nos subpacotes para demonstrar a melhor maneira de se determinar caminhos de pacotes mais complexos.

      Concentrando-se em variáveis para subpacotes

      Na última seção, você manipulou a variável Version, a qual estava no pacote de nível superior do aplicativo. Mas esse não é sempre o caso. Com frequência, é mais prático colocar essas variáveis em outro pacote, já que o main não é um pacote importável. Para simular isso em seu aplicativo exemplo, você criará um novo subpacote, app/build que armazenará informações sobre a hora em que o binário foi compilado e o nome do usuário que emitiu o comando de compilação.

      Para adicionar um novo subpacote, adicione primeiro um novo diretório ao seu projeto chamado build:

      Depois, crie um novo arquivo chamado build.go para reter as novas variáveis:

      No seu editor de texto, adicione novas variáveis para Time e User:

      app/build/build.go

      package build
      
      var Time string
      
      var User string
      

      A variável Time reterá uma representação de string da hora em que o binário foi compilado. A variável User reterá o nome do usuário que compilou o binário. Como essas duas variáveis sempre terão valores, você não precisa inicializar essas variáveis com valores padrão como você fez para Version.

      Salve e saia do arquivo.

      Em seguida, abra o main.go para adicionar essas variáveis ao seu aplicativo:

      Dentro de main.go, adicione as seguintes linhas destacadas:

      main.go

      package main
      
      import (
          "app/build"
          "fmt"
      )
      
      var Version = "development"
      
      func main() {
          fmt.Println("Version:t", Version)
          fmt.Println("build.Time:t", build.Time)
          fmt.Println("build.User:t", build.User)
      }
      

      Nessas linhas, você importou primeiro o pacote app/build e, então, imprimiu build.Time e build.User da mesma forma que imprimiu Version.

      Salve o arquivo e depois saia do seu editor de texto.

      Em seguida, para atingir essas variáveis com o ldflags, você poderia usar o caminho de importação app/build seguido de . User ou . Time, uma vez que você já sabe o caminho de importação. No entanto, para simular uma situação mais complexa na qual o caminho para a variável não é evidente, em vez disso, vamos usar o comando nm na cadeia de ferramentas Go.

      O comando go tool nm dará como resultado os símbolos envolvidos em um dado executável, arquivo de objeto ou arquivo. Neste caso, um símbolo se refere a um objeto no código, como uma variável ou função definida ou importada. Ao gerar uma tabela de símbolos com nm e usar o grep para procurar por uma variável, você pode rapidamente encontrar informações sobre seu caminho.

      Nota: o comando nm não ajudará você a encontrar o caminho da sua variável se o nome do pacote tiver qualquer caractere não ASCII ou um caractere " ou %, já que essa é uma limitação da ferramenta em si.

      Para usar esse comando, compile primeiro o binário para app:

      Agora que o app foi compilado, aponte a ferramenta nm para ele e examine o resultado:

      • go tool nm ./app | grep app

      Quando executada, a ferramenta nm gerará muitos dados como resultado. Por isso, o comando anterior usou o símbolo | para canalizar o resultado até o comando grep, o qual, na sequência, pesquisou termos que tivessem app de nível elevado no título.

      Você irá receber um resultado parecido com este:

      Output

      55d2c0 D app/build.Time 55d2d0 D app/build.User 4069a0 T runtime.appendIntStr 462580 T strconv.appendEscapedRune . . .

      Neste caso, as duas primeiras linhas do conjunto de resultados contêm os caminhos para as duas variáveis que você está procurando: app/build.Time e app/build.User.

      Agora que você conhece os caminhos, compile o aplicativo novamente, desta vez alterando Version, User e Time no momento de compilar. Para tanto, passe vários identificadores -X para os -ldflags:

      • go build -v -ldflags="-X 'main.Version=v1.0.0' -X 'app/build.User=$(id -u -n)' -X 'app/build.Time=$(date)'"

      Aqui, você enviou o comando Bash id -u -n para listar o usuário atual e o comando date para listar a data atual.

      Assim que o executável estiver compilado, execute o programa:

      Esse comando, quando executado em um sistema Unix, gerará um resultado similar ao seguinte:

      Output

      Version: v1.0.0 build.Time: Fri Oct 4 19:49:19 UTC 2019 build.User: sammy

      Agora, você tem um binário que contém informações do controle de versão e compilação que podem fornecer assistência vital na produção ao resolver problemas.

      Conclusão

      Este tutorial mostrou como, quando aplicado corretamento, o ldflags pode ser uma ferramenta poderosa para injetar informações valiosas em binários no momento da compilação. Dessa forma, você pode controlar identificadores de recursos, informações de ambiente, informações de controle de versão e outras coisas, sem introduzir alterações no seu código fonte. Ao adicionar ldflags ao seu fluxo de trabalho de compilação atual, você pode maximizar os benefícios do formato de distribuição binária independente do Go.

      Se quiser aprender mais sobre a linguagem de programação Go, confira toda a nossa série sobre Como codificar em Go. Se estiver procurando mais soluções para controle de versão, teste nosso guia de referência Como usar o Git.



      Source link