De acordo com as Leis 12.965/2014 e 13.709/2018, que regulam o uso da Internet e o tratamento de dados pessoais no Brasil, ao me inscrever na newsletter do portal DICAS-L, autorizo o envio de notificações por e-mail ou outros meios e declaro estar ciente e concordar com seus Termos de Uso e Política de Privacidade.


Usos interessantes de algumas variáveis do sistema

Colaboração: Julio Cezar Neves

Data de Publicação: 19 de agosto de 2019

O Shell utiliza inúmeras variáveis que já são predefinidas. Para você ter uma ideia, faça:

$ env | more

e aparecerão variáveis aos borbotões e note, todas com os nomes em caixa alta. Por isso recomendo a todos não usarem variáveis dos seus aplicativos em letras maiúsculas. Dificulta a vida de quem está depurando e pode te levar a um erro dificílimo de identificar, devido a você ter usado inadvertidamente um nome de variável do sistema.

A seguir mostro umas pouco, que têm duas particularidades:

  1. Poucos conhecem;
  2. São muito úteis quando bem utilizadas.

Vamulá:

Variável Descrição
IFS Entender o funcionamento desta variável é muito importante, mas como é um tema extenso, este artigo ficaria muito longo no caso de entrar em muitos detalhes, Mas a grosso modo podemos dizer que diversos comandos (entre eles o read, set, ...) aceitam seus argumentos separados pelos caracteres definidos nesta variável, cujos defaults são espaço, <TAB> e <ENTER>.
CDPATH Contém os caminhos que serão pesquisados para tentar localizar um diretório especificado. Apesar desta variável ser pouco conhecida, seu uso deve ser incentivado por poupar muito trabalho, principalmente em instalações com estrutura de diretórios com muitos níveis.
PIPESTATUS É uma variável do tipo vetor (array) que contém uma lista de valores de código de retorno do último pipeline executado, isto é, um vetor que abriga cada um dos $? de cada instrução do último pipeline.
PROMPT_COMMAND Se esta variável receber uma instrução, toda vez que você der um <ENTER> direto no prompt principal ($PS1), este comando será executado. É útil quando se está repetindo muito uma determinada instrução.
REPLY Quando fazemos uma leitura com o comando read e não associamos nenhuma variável para receber a entrada, esses dados serão guardados nessa variável. Normalmente a usamos para leituras sem interesse futuro.

IFS

Veja isso:

$ read a b c <<< "1 2 3"
$ echo $a - $b - $c
1 - 2 - 3

Como a entrada veio separada por espaços em branco, que faz parte dos valores default da $IFS, pudemos ler seu conteúdo para diversas variáveis em um só read. Vou fazer o mesmo com <ENTER> que também é default.

$ Var=$(echo -e "1\n2\n3")
$ echo "$Var"
1
2
3
$ read a b c <<< $Var
$ echo $a - $b - $c
1 - 2 - 3

Agora vamos separar os campos por porralouquice:

$ IFS=-^ read a b c <<< 1^2-3
$ IFS=-:^ read a b c <<< 1^2-3
$ echo $a - $b - $c
1 - 2 - 3

Umas observações sobre o uso do IFS:

1. Nesse último exemplo, repare que entre a atribuição do IFS e o comando read não existe nada, sequer ponto e vírgula. Isso significa que ele foi alterado somente durante a execução do read. Só para te mostrar que isso pode ocorrer com outras duplas de comando+variável, veja a dupla date+LANG:

$ echo $LANG       # Idioma inicial
pt_BR.UTF-8
$ date
Dom Ago 18 14:56:52 -03 2019
$ LANG=C date      # Alterei o idioma só para o date
Sun Aug 18 14:57:29 -03 2019
$ echo $LANG       # Idioma final é o mesmo
pt_BR.UTF-8

2. Sempre que um Shell puder "enxergar" um IFS, ele o transformará num espaço em branco. Veja:

$ Var=$(grep root /etc/passwd)
$ echo $Var
root:x:0:0:root:bla-bla-bla:/root:/bin/bash
$ IFS=:
$ echo $Var
root x 0  root bla-bla-bla /root /bin/bash
$ echo "$Var"
root:x:0:0:root:bla-bla-bla:/root:/bin/bash

Como você pode notar, quando eu não protegi $Var da interpretação do Shell, os dois pontos (que era o IFS da vez) sumiram.

Quando coloquei $Var entre aspas para proteger seu conteúdo da interpretação do Shell, os dois pontos voltaram a aparecer.

3. Sempre que você alterar um IFS, é recomendável que você o restitua ao seu valor default após usá-lo para evitar erros difíceis de serem localizados.

CDPATH

Assim como a variável $PATH contém os caminhos nos quais o Shell procurará os arquivos, a $CDPATH possui os caminhos a serem seguidos para se fazer um cd.

$ cd bin
bash: cd: bin: Arquivo ou diretório não encontrado
$ CDPATH=$CDPATH:/usr/local	# Adiciona /usr/local nos caminhos
$ echo $CDPATH
.:..:~:/usr/local
$ pwd
/home/jneves/LM
$ cd bin
$ pwd
/usr/local/bin

Como /usr/local estava na minha variável $CDPATH, e não existia o diretório bin em nenhum dos seus antecessores (., .. e ~), o cd foi executado para /usr/local/bin.

PIPESTATUS

Um vetor (array) em que cada elemento possui o código de retorno ($?) de cada uma uma das instruções que compoem um pipeline.

$ who
jneves   pts/0        Apr 11 16:26 (10.2.4.144)
jneves   pts/1        Apr 12 12:04 (10.2.4.144)
$ who | grep ^botelho
$ echo ${PIPESTATUS[*]}
0 1

Neste exemplo mostramos que o usuário botelho não estava "logado", em seguida executamos um pipeline que procurava por ele. Usa-se a notação [*] (ou [@]) em um vetor para listar todos os seus elementos, e desta forma vimos que a primeira instrução (who) foi bem sucedida (código de retorno 0) e a seguinte (grep), não foi (código de retorno 1).

PROMPT_COMMAND

Nunca dei muita bola para essa variável porque nunca havia encontrado nenhuma aplicabilidade para ela. Por outro lado, diversas vezes já dei um <CTRL>+r para pesquisar no arquivo de histórico de comandos uma instrução muito longa ou alguma que já havia esquecido e não encontrei o que queria porque este arquivo é circular, isto é, depois de um determinado número de registros gravados (essa quantidade é definida pela variável $HISTSIZE) começa a haver superposição.

Para ter os meus preciosos comandos por mais tempo, comecei tentando aumentar o valor de $HISTSIZE até o limite, mas, mesmo assim, após meses, pesquisava o histórico e a linha de comando que me interessava não estava mais lá.

Foi então que li um artigo que me chamou a atenção sobre a variável PROMPT_COMMAND e veja só o que inseri no ~/.bashrc:

PROMPT_COMMAND='echo "$(history 1)" >> history4ever'

Como o comando history 1 lista a última linha executada, e como a função de $PROMPT_COMMAND é executar o seu conteúdo antes de devolver o prompt, todo comando executado é gravado no arquivo history4ever e, quando preciso de um comando, basta pesquisá-lo usando todas as facilidades que o grep provê.

Ainda sobre o history, sempre que estou dando um treinamento, logo no primeiro minuto de aula aconselho que os treinandos ponham no seu ~/.bashrc a seguinte linha:

$ PROMPT_COMMAND="echo -n \[$(date +%d/%m-%H:%M)\] "

Supondo que o $PS1 na minha máquina seja um cifrão ($), a cada vez que eu der um <ENTER>, ele me devolverá um prompt com a data e a hora, seguido pelo cifrão ($). Assim:

$ PROMPT_COMMAND="echo -n \[$(date +%d/%m-%H:%M)\]"
[12/09-02:56]$

Faço isso porque ao final do curso, junto com todo o material que entrego, o aluno ainda levará um pendrive com o histórico de tudo que ele fez ao longo das 40 horas de treinamento, com a data e a hora devidamente registradas.

Uma forma fácil de você ver a $PROMPT_COMMAND em ação é fazendo, por exemplo:

$ PROMPT_COMMAND=ls

Agora a saída de cada comando que você executar será seguida pela lista dos arquivos do diretório. Ficou um saco, né? Para desfazer faça:

$ unset PROMPT_COMMAND

Eu disse que seria executado antes do prompt, mas isso é o mesmo que dizer que será executado após cada comando. Então poderíamos fazer:

$ PROMPT_COMMAND='(( $? )) && {
    tput bold
    echo instrução errada
    tput sgr0
    }'
$ ./nãoexiste 
bash: ./nãoexiste: Arquivo ou diretório não encontrado 
instrução errada

Ou seja, após cada instrução, seu código de retorno é testado e, sendo diferente de zero, a mensagem instrução errada será dada em ênfase e logo após será restaurado o modo normal do terminal (sem negrito).

REPLY

Quando você lê algo com importância somente pontual para seu programa, use esta variável para receber o dado lido.

read -n1 -p "Deseja sair? "; [[ ${REPLY^} == S ]] && exit

Repare que não armazenei em nenhuma variável o que foi teclado, pois após esse local, ele não teria nenhum interesse para o meu programa. Usei então o $REPLY dentro de uma Expansão de Parâmetros que passa seu conteúdo para caixa alta.

Adicionar comentário

* Campos obrigatórios
5000
Powered by Commentics

Comentários

Nenhum comentário ainda. Seja o primeiro!


Veja a relação completa dos artigos de Julio Cezar Neves