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.


IFS e variáveis de localidade

Colaboração: Julio Cezar Neves

Data de Publicação: 16 de maio de 2021

Algumas variáveis do sistema têm um comportamento muito interessante que no meu entender, merece a atenção daqueles que gostam de usar o Shell. Neste artigo mostrarei uma "façanha" diferente, que é você alterar o valor de uma variável somente para executar uma única instrução, voltando ao conteúdo anterior ao término deste comando. As mais importantes são o uso da IFS e das variáveis de localidade. Primeiro uma breve descrição delas:

IFS (Inter Field Separator) é a variável que estipula os delimitadores de campo para diversos comandos. Por default seu valor é formado por espaço em branco, <TAB> e <ENTER>, mas este valor pode ser alterado. Para exemplificar usarei o comando read, com o qual eu posso ler de uma só vez diversos campos, desde que sejam separados por algum caractere da IFS. Veja:

$ read Cpo1 Cpo2 Cpo3 <<< "A B C"
$ echo +$Cpo1+ -- +$Cpo2+ -- +$Cpo3+
+A+ -- +B+ -- +C+

Ou seja, cada uma das variáveis recebeu um valor já que os dados da entrada eram separados por espaços em branco. Mas e se a entrada vier delimitada por dois pontos (:), assim:

$ read Cpo1 Cpo2 Cpo3 <<< "A:B:C"
$ echo +$Cpo1+ -- +$Cpo2+ -- +$Cpo3+
+A:B:C+ -- ++ -- ++

Xiii! Não funcionou porque os dois pontos (:) não fazer parte do IFS. Então vamos fazê-lo:

$ IFS=: read Cpo1 Cpo2 Cpo3 <<< "A:B:C"
$ echo +$Cpo1+ -- +$Cpo2+ -- +$Cpo3+
+A+ -- +B+ -- +C+

Viu!? Agora funcionou e veja:

$ read Cpo1 Cpo2 Cpo3 <<< $(echo -e "F\tG\nH")
$ echo +$Cpo1+ -- +$Cpo2+ -- +$Cpo3+
+F+ -- +G+ -- +H+

Desta vez os delimitadores eram <TAB> (\t) e <ENTER> (\n) mostrando que o IFS voltou ao seu valor inicial, ou seja, foi alterado somente para executar um único read.

As variáveis de localidade, especificam as padronizações dependentes de cada país. Elas podem ser vistas pelo comando locale.

$ locale		# Suporte a multi linguagem
LANG=pt_BR.UTF-8
LANGUAGE=
LC_CTYPE="pt_BR.UTF-8"
LC_NUMERIC="pt_BR.UTF-8"
LC_TIME="pt_BR.UTF-8"
LC_COLLATE="pt_BR.UTF-8"
LC_MONETARY="pt_BR.UTF-8"
LC_MESSAGES="pt_BR.UTF-8"
LC_PAPER="pt_BR.UTF-8"
LC_NAME="pt_BR.UTF-8"
LC_ADDRESS="pt_BR.UTF-8"
LC_TELEPHONE="pt_BR.UTF-8"
LC_MEASUREMENT="pt_BR.UTF-8"
LC_IDENTIFICATION="pt_BR.UTF-8"
LC_ALL=

A LC_ALL normalmente está vazia porque ela tem prioridade sobre as outras e se fosse necessário manter uma variável de localidade fora do formato padrão, informando um valor para LC_ALL, todas receberiam essa máscara de local.

Sobre isso, devemos também saber que a localidade C é um locale especial que se destina a ser a localidade mais simples. Pode-se dizer que, as outras localidades são para humanos, mas a C é para computadores. No local C, os caracteres são bytes únicos, o conjunto de caracteres é ASCII. Exceto LC_MONETARY, todas as outras definições são iguais a en_US, ou seja, tudo no padrão americano.

Vejamos então alguns exemplos de mudanças temporárias no valor das variáveis locais:

$ cat arq
1
a
B
2
b
A

$ sort arq                 # Ordenação natural
1
2
a
A
b
B

$ LC_COLLATE=C sort arq    # Ordenação ascii
1
2
A
B
a
b

$ man

Qual a página de manual desejada?

$ LANG=C man
What manual page do you want?
$ echo $LANG				# Mudou linguagem só para o man
pt_BR.UTF-8

O bc sempre dá a saída com ponto decimal, isto é, no padrão americano, veja:

$ bc <<< "scale=2 ; 100/6" 
16.66

Mas se desejarmos formatar esta saída com 3 decimais, deveríamos fazer:

$ printf '%.3f\n' $(bc <<< "scale=2 ; 100/6")
bash: printf: 16.66: número inválido
0,000

Esse erro ocorreu porque como você pode reparar no comando locale LC_NUMERIC=pt_BR.UTF-8 o que é incompatível com ponto decimal, só aceita vírgula (,), então é muito comum vermos construções do tipo:

$ printf "%.3f\n" $(bc <<< "scale=2 ; 100/6" | tr . ,)
16,660

Ou seja: usando o comando tr para trocar o ponto decimal por vírgula, mas se o resultado impresso não necessitasse da vírgula, a operação seria bem mais rápida se fosse feita da seguinte forma:

$ LC_NUMERIC=C printf '%.3f\n' $(bc <<< "scale=2 ; 100/6")
16.660

E agora se olharmos o conteúdo daquela variável, podemos ver que continua o que era antes:

$ locale | grep -F LC_NUMERIC
LC_NUMERIC="pt_BR.UTF-8"

Ou ainda, mais detalhadamente:

$ locale -k LC_NUMERIC
decimal_point=","
thousands_sep="."
grouping=3;3
numeric-decimal-point-wc=44
numeric-thousands-sep-wc=46
numeric-codeset="UTF-8"

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