Truques com variáveis no Bash

Criar shellscript para automatizar tarefas é uma das coisas mais úteis que um sysadmin pode e deve aprender a fazer para facilitar sua rotina diária e não é raro precisarmos usar variáveis para referenciar valores que serão usados em diversas partes do código. Vamos ver então alguns “truques” que podem melhorar o seu código.

Começando “do começo”

Bom, se você caiu de paraquedas no “planeta da programação” e não sabe o que são variáveis, eu recomendo fortemente que procure algum material de introdução a programação, mas em curtas palavras podemos dizer que variável é o nome que damos a um pequeno lugar na memória onde guardaremos algum dado relevante.

Por exemplo, eu posso guardar em uma variável chamada “cor”, o nome da cor que será usada para escrever uma mensagem; ou ainda em uma variável chamada “mensagem”, um texto de aviso que será passado para diversas funções diferentes (digamos, enviar por e-mail, Telegram, exibir na tela etc).

Esta é uma explicação bem simples e abstrata, passando longe do que ocorre dentro do hardware da máquina, mas nos atende para o que precisamos no momento.

Na prática e considerando o shellscript como referência a criação de uma variável seria assim:

cor="azul"
mensagem="Arquivos excluídos com sucesso"

Já a leitura do conteúdo da variável seria algo assim:

echo $cor
echo ${mensagem}
cat ${arquivo}

Já passamos do começo e agora?

Se você já escreveu alguns scripts então é provável que o que escrevi acima esteja mais do que entendido então vamos ao que interessa, mas antes de prosseguir é importante observar a sintaxe de leitura dos valores contidos na variável.

Para a maioria dos exemplos que vou colocar abaixo você notará que ao invés de $variavel, eu vou usar ${variavel} (observe o nome da variável dentro de {}) e você entenderá o porque tão logo comece a ver os exemplos.

Verificando o tamanho de uma variável

Digamos que você tenha feito uma função para enviar mensagens pelo Telegram, no entanto – por qualquer motivo que não vem ao caso – você quer limitar a mensagem ao tamanho de 70 caracteres.

Antes de qualquer verificação você precisará saber quantos caracteres tem sua variável. A sintaxe que usaremos para descobrir esta informação usa o símbolo “#” antes do nome da variável, como no exemplo seguinte:

mensagem="Um novo usuário acaba de acessar o sistema XYZ"
tamanho_mensagem=${#mensagem}
[ $tamanho_mensagem -lt 70 ] && send_telegram "$mensagem"

No exemplo acima, o valor da variável “tamanho_mensagem” é “46” e então a função hipotética “send_telegram” seria invocada e a mensagem enviada.

O mesmo não ocorreria neste outro exemplo abaixo, em que a mensagem tem tamanho de 87 caracteres (mais do que os 70 esperados):

mensagem="Um novo usuário acaba de acessar o Sistema de Controle de Acesso Restritivo Ultraseguro"
tamanho_mensagem=${#mensagem}
[ $tamanho_mensagem -lt 70 ] && send_telegram "$mensagem"

Se você quiser fazer experimentos use o código abaixo e altere o texto da variável “mensagem” como quiser. O comando “echo” exibirá o tamanho do texto naquela variável:

mensagem="Testando"
echo "${#mensagem}"

Textos em maiúsculas e minúsculas

Esta atividade é comum de ser executada em certas rotinas que envolvem textos recebidos pelo usuário: Você recebe uma mensagem de texto qualquer, porém você quer exibí-la totalmente em maiúsculas, ou totalmente em minúsculas.

Para realizar esta modificação nós usaremos os símbolos “^” e “,”.

Encare o “^” como se fosse uma setinha para cima (o que transforma os caracteres em maiúsculas) e a “,” como se fosse o braço de alguém puxando para baixo (de “caixa-baixa”/minúsculas).

Veja o resultado disso com os exemplos a seguir:

mensagem="título em maiúsculas"
echo ${mensagem^}
echo ${mensagem^^}

Observe que eu usei o comando “echo” duas vezes. Uma vez com apenas um símbolo “^” e outra vez, com dois. Eu vou deixar sua curiosidade dizer qual será diferença entre as duas formas.

Analogamente com a “,” teriamos o texto em minúsculas.

mensagem="TÍTULO EM MINÚSCULAS"
echo ${mensagem,}
echo ${mensagem,,}

Execute-os no seu terminal e veja o que muda de uma forma para outra.

Removendo caracteres do início ou do final da sua variável

Algumas vezes você recebe uma informação com caracteres que podem ser irrelevantes para o que esteja fazendo, e saber removê-los é sempre importante.

Por exemplo vamos supor que você tem o caminho para um determinado arquivo e você quer remover o “/var/” inicial, caso ele exista.

Para isso nós usaremos o símbolo “#”, como no exemplo abaixo:

arquivo="/var/www/index.html"
echo "${arquivo#/var/}"

O resultado disso deverá ser apenas “www/index.html” (sem o “/var/” inicial). Este é um procedimento bastante comum para implementar algumas rotinas de cópia, ou para corrigir algumas informação sobre este arquivo que está registrada incorretamente, ou qualquer outra situação onde aquela parte inicial não é desejada.

obs: Embora o símbolo “#” seja o mesmo usado para retornar o tamanho da variável, compare a forma de uso em ambos os casos que você verá que elas são diferentes.

Para remover os caracteres do final do arquivo o processo é parecido mas o símbolo que usamos é o “%”. Veja como fariamos para remover a extensão “.html” de um arquivo.

arquivo="/var/www/index.html"
echo "${arquivo%.html}"

Este é um recurso legal quando queremos trocar a extensão do arquivo de forma rápida. Veja o exemplo abaixo como eu troquei a extensão de “.jpeg” para “.jpg”.

arquivo="/media/imagens/fotografia.jpeg"
echo "${arquivo%.jpeg}.jpg"

Alterando uma parte da sua variável

Vamos supor que em determinada rotina você precise trocar os caracteres acentuados por suas versões básicas e não acentuadas, ou remover espaços etc.

Para isso nós usaremos o símbolo “/” que funciona como uma versão simplificada do comando “sed” (caso você já o tenha usado).

arquivo="copia 01.txt"
echo "${arquivo/ /_}"

No exemplo acima, nós estamos trocando o caracter ” ” (espaço) por um “_”.

Pegando parte da sua variável

Algumas vezes nós queremos pegar parte de um texto, não para trocar, mas para verificar, mostrar, registrar para uso futuro no script etc. Isso é feito com o símbolo “:”.

Para este exemplo vamos considerar que eu receba uma variável onde os 4 primeiros caracteres é a matrícula de um indivíduo, os 6 seguintes é o seu login e todo o restante seria o nome completo. Algo exótico assim:

caracteres="1902cdrummCarlos Drummond de Andrade"
echo "Matricula: ${caracteres:0:4}"
echo "Login: ${caracteres:4:6}"
echo "Nome: ${caracteres:10}"

No exemplo acima, a partir da posição “0” (zero) nós pegamos 4 caracteres, que correspondem a matrícula (1902). A partir da posição 4 (quinto caracter) nós pegamos 6 caracteres que corresponde ao login; e por fim, a partir da posição 10 que é o 11º caractere até o final, nós pegamos o seu nome.

Cuidado com a contagem das posições dos caracteres pois, como na maioria das linguagens de programação, ela começa em “zero”, não em “um”.

Uma última dica sobre este ponto é que pode surgir a necessidade de pegar apenas os caracteres do final da linha. Por exemplo, se quisermos pegar a extensão do arquivo com 4 caracteres que está em uma variável.

Para este recurso basta nós contarmos negativamente para que o Bash entenda que você está contando de trás para frente. Assim:

arquivo="prova.docx"
echo "Extensão: ${arquivo: -4}"

Esteja atento a uma pegadinha neste recurso. Perceba que eu deixei propositalmente um ” ” (espaço) entre o “:” e o “-“. Isso é obrigatório para que o sinal do nosso número negativo não se confunda com o “:-” que formam um outro símbolo que veremos a seguir.

Definindo valores padrão

Durante blocos condicionais como os formados por if/else/fi, nós precisamos validar o conteúdo de variáveis e muitas vezes isso é precedido de algumas modificações em variáveis para definir valores padrão (para o caso de não encontrarmos o valor que esperamos).

O Bash nos oferece quatro possibilidades de tratativa para estes casos mas mesmo sendo pouco, isso é de envergonhar grandes linguagens de programação que nem isso possui.

Vamos tomar como exemplo uma pequena função que apenas exibe os usuários do sistema através do comando gentent.

showuser() {
usuario="$1"
getent passwd ${usuario}
}

Ao executar estes comandos no seu terminal você apenas estará criando a função na sua sessão.

Para invocá-la você deverá logo em seguida digitar o seu nome:

showuser

ou então o seu nome seguido de algum login existente:

showuser daniel

No primeiro caso, como você não especificou nenhum usuário, o comando getent entende que é para listar todos, enquanto no segundo ele mostrará as informações apenas do usuário “daniel” (ou outro que você tenha especificado, desde que exista).

Uma vez que você já tenha testado a rotina acima, vamos modificar como o Bash trata os valores padrão.

Opção 1: Sem usuário, retorne com erro

No exemplo que usei o comando getent pode lidar perfeitamente com o caso de não informarmos um usuário, mas nem sempre isso ocorre, e o comando que espera pelo nome do usuário poderia gerar um erro e quebrar todo seu script.

Para evitar isso nós podemos forçar a função a sair caso não haja um usuário informado e para isso não precisamos nem usar estrutura condicional

showuser() {
usuario="$1"
getent passwd ${usuario:?Você esqueceu de informar o usuário. Saindo}
} 

Perceba que a mudança que fiz na função anterior, foi colocar uma mensagem de erro após o símbolo de “?”.

Quando o bash tentar resolver o valor da variável “usuario” e detectar que ela não tem valor, imediatamente exibirá a mensagem e encerrará a rotina, sem que eu pusesse qualquer estrutura condicional. No entanto, caso você informe um usuário válido ele será exibido normalmente.

Opção 2: Sem usuário, use o root como padrão (sem atribuição)

Em alguns casos será desejável que ao invés de exibir um erro e sair, a sua rotina considere um determinado valor como padrão.

Nesta nossa mudança. Caso o usuário não especifique um usuário, a nossa função vai considerar que é para exibir as informações do usuário “root” que está disponível em todos os sistemas Linux.

Veja como ficaria:

showuser() {
usuario="$1"
getent passwd ${usuario:-root}
} 

Tal como na mudança anterior isso só afetará o resultado caso não seja informado o usuário a ser visualizado.

Perceba aqui que não podemos usar ” ” (espaço) entre o “:” e o “-” pois o Bash entenderá que queremos pegar uma parte negativa (final) da variável “$usuario”, porém “root” não é número e nem variável, o que causará um erro, portanto, digite “:-” sem espaços entre eles.

Opção 3: Sem usuário, use o root como padrão (COM atribuição)

Uma outra forma de se fazer a mesma coisa seria usando o símbolo “=” ao invés do “-“. Nossa função agora seria assim:

showuser() {
usuario="$1"
getent passwd ${usuario:=root}
} 

Você provavelmente não notará qualquer diferença no resultado de ambas as versões da função (usando “:-” e “:=”), mas há uma diferença importantíssima ao escolher entre um e outro, que você verá nos dois exemplos a seguir.

Comparando as opções 2 e 3

Perceba abaixo que eu peguei ambas as funções mostradas anteriormente e apenas troquei seus nomes para que elas possam coexistir na mesma sessão, além de mostrar a mensagem “Estes foram os dados do usuário xxxxxx” logo após exibir os dados do getent.

showuser1() {
usuario="$1"
getent passwd ${usuario:-root}
echo "Estes foram os dados do usuário ${usuario}"
}
showuser2() {
usuario="$1"
getent passwd ${usuario:=root}
echo "Estes foram os dados do usuário ${usuario}"
}

Carregue as duas funções, invoque-as uma de cada vez e veja a diferença.

Visualmente a função showuser1 (a que usa “:-” ) mostra apenas “Estes foram os dados do usuário” , enquanto a função showuser2 (a que usa “:=” ) mostra “Estes foram os dados do usuário root”.

Mas na prática mesmo o que houve ai é que ao usarmos da primeira forma, se a variável “usuario” não tem valor, o Bash retorna “root” para quem solicitou, mas não atualiza o conteúdo da variável “usuario”. O resultado disso é que quando precisamos consultar o valor daquela variável em comandos seguintes (o que fizemos com o comando “echo”), ela ainda estará vazia.

Ao passo que usando da segunda forma, não só o Bash retorna o valor “root” como já atribui este valor à variável, de forma que quando consultamos seu valor logo em seguida nós temos o nome do usuário correto.

E quando você deverá usar uma forma ou outra!? Não existe uma resposta fixa para isso. Depende do que você esteja fazendo, por isso há as duas formas.

Eu tenho a tendência a dizer que na maioria dos casos você usará a segunda forma (com “:=” ) mas isso é apenas uma verdade para minha forma de trabalhar e para os exemplos que me vem a mente na hora que escrevo este texto. Talvez você descubra que nos seus casos seja o inverso. Não se se sinta acanhado e nem julgue seu código errado por isso.

Opção 4: Sem usuário, está OK

Enquanto nas opções anteriores o problema era se a variável estivesse vazia ou não existisse. Esta agora é uma forma inversa e se o usuário for informado eu quero que ele seja alterado

showuser() {
usuario="$1"
getent passwd ${usuario:+root}
}

Desta vez, caso você não informe usuário nenhum verá a listagem de todos os usuários do sistema, no entanto se informar algum usuário, não importa qual, você sempre verá os dados do usuário root.

Este é um exemplo ruim para o símbolo “+”, mas eu fiz questão de usá-lo para que percebessem a diferença entre todos eles na mesma aplicação.

Um uso prático para ele seria no caso de validar o recebimento de qualquer valor.

showuser() {
usuario="$1"
[ "${usuario:+sim}" == "sim" ] && getent passwd ${usuario}
}

Na forma como a função acima foi escrita, se a variável “usuario” receber qualquer valor o bash retornará a palavra “sim”, o que consequentemente vai ser igual (==) a “sim” e portanto (&&) invocará o comando getent.

Caso a variável “usuario” não tenha nenhum valor a comparação não seria verdadeira e o comando getent jamais seria invocado.

Por fim…

Scripts são como programas, só que embora sejam bastante limitados e lentos, é inegável que qualquer tarefa automatizada por eles será muito mais rápida do que se fosse executada com uma série de comandos manualmente, mesmo pelo mais exímio e ágil digitador.

Para que seus scripts funcionem bem é extremamente importante saber lidar com variáveis. As manipulações que mostrei aqui apesar de não esgotarem o assunto são um bom avanço no conhecimento básico que a maioria dos usuários iniciantes possuem quando caem de cara com a necessidade de desenvolver uma rotina em shellscript.

Se necesário, leia e releia, favorite o texto e leia novamente quando necessário e se quiser poste ai a sua dúvida, pode ser que ela vire um novo artigo ou apenas seja respondida aqui mesmo.

Até a próxima e obrigado pelo prestígio.

Deixe um comentário

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *

Esse site utiliza o Akismet para reduzir spam. Aprenda como seus dados de comentários são processados.