Outro dia, um dos membros do grupo Curso GNU, no Telegram estava batendo cabeça para alterar as cores do seu TTY (o terminal no popular “modo texto”). Por se tratar de algo raramente comentado, eu resolvi deixar registrada a minha solução em Bash aqui no blog da comunidade debxp.
Para começar, existe uma diferença fundamental entre definir uma cor e definir uma paleta: a primeira diz respeito a invocar o nome de uma cor, enquanto a segunda refere-se ao que nós veremos quando a cor invocada for exibida.
Repare bem, é bastante popular o uso de sequências ANSI para estilizar a cor dos textos no terminal, por exemplo:
printf '\e[32m%s\e[31m%s\e[0m' 'verde' 'vermelho'
Que resulta em:
Aqui, as sequências \e[32m
e \e[31m
foram
utilizadas para alterar a cor do texto (foreground) para verde
e vermelho, respectivamente. A sequência \e[0m
, por sua
vez, restaura a cor que está definida por padrão para os textos do
terminal. Contudo, quem diz o que nós perceberemos como verde, vermelho
ou qualquer outra cor com os nossos olhos é uma paleta de
cores.
A maioria dos terminais gráficos oferece uma interface para definirmos a paleta. No caso do terminal que eu uso, o xfce4-terminal, esta é a interface:
Observe que os botões da seção Paleta me permitem escolher 16 cores conforme a minha preferência – basta clicar em qualquer um deles para fazer a minha definição:
Além dos elementos gráficos que me permitem escolher a cor com o
mouse, existe uma caixa de texto onde eu posso escrever a cor em
hexadecimal: no caso, #2A94E0
.
A representação em hexadecimal de uma cor corresponde aos níveis de intensidade das três cores básicas que participarão de uma mistura aditiva: vermelho, verde e azul, nesta ordem, ou RGB.
R=Vermelho
G=Verde
B=Azul
R |G |B
#2A|94|E0
Com os dois dígitos hexadecimais de cada cor, nós podemos definir até
256 níveis de intensidade, de 00
a FF
.
Apenas para efeito ilustrativo, no Bash, nós podemos converter facilmente os valores em hexa para os valores equivalentes em base decimal e, assim, ter uma noção melhor dos níveis de intensidade de cada componente de cor:
:~$ printf '%d %d %d\n' '0x2A' '0x94' '0xE0'
42 148 224
↑ ↑ ↑
R G B
Adiantando-me à sua curiosidade, a conversão contrária também é possível:
:~$ printf '%x %x %x\n' '42' '148' '224'
2a 94 e0
Como eu ia dizendo, a interface do meu terminal me permite definir até 16 cores, apresentadas em duas fileiras de 8 botões.
A fileira de cima, refere-se às 8 cores “normais”, enquanto
a de baixo são as cores “brilhantes”. Em ambas as fileiras, os
botões são apresentados na ordem exata da numeração dessas cores para o
sistema operacional: números de 0
a 7
.
Nas sequências ANSI, a fileira de cima recebe o dígito 3
antes do número de cada cor, enquanto a numeração da fileira de baixo é
precedida do dígito 9
. Deste modo, quando queremos aplicar
a terceira cor da paleta “normal” a um texto, por exemplo, nós
nos referimos a ela com o número 32
(verde). Se quisermos
utilizar a quinta cor da paleta “brilhante”, nós utilizamos
94
(azul brilhante), e assim por diante.
Alguns terminais ainda oferecem a opção de exibir as cores brilhantes como versões em negrito (bold) das cores normais.
As 16 cores da paleta do terminal podem ser obtidas em uma sequência
de escape (uma sequencia de caracteres iniciados com o caractere
ESC
, representado por \e[
) segundo a tabela
abaixo:
Cor | Frente | Fundo |
---|---|---|
Preto | 30 |
40 |
Vermelho | 31 |
41 |
Verde | 32 |
42 |
Amarelo | 33 |
43 |
Azul | 34 |
44 |
Magenta | 35 |
45 |
Ciano | 36 |
46 |
Cinza claro | 37 |
47 |
Importante! Uma mesma sequência de escape pode receber vários comandos separados por
;
e deve ser sempre terminada com o caracterem
.
Agora ficou fácil entender o que estava acontecendo no nosso primeiro exemplo:
printf '\e[32m%s\e[31m%s\e[0m' 'verde' 'vermelho'
Já as cores “brilhantes” podem ser utilizadas conforme esta tabela:
Cor | Frente | Fundo |
---|---|---|
Cinza escuro | 90 |
100 |
Vermelho | 91 |
101 |
Verde | 92 |
102 |
Amarelo | 93 |
103 |
Azul | 94 |
104 |
Magenta | 95 |
105 |
Ciano | 96 |
106 |
Branco | 97 |
107 |
Mas, não se confunda, esses nomes e números são apenas isso: nomes e números de cores – o que nós veremos no terminal são as definições da paleta! Isso é importante porque, como o membro do grupo do Curso GNU queria, nós podemos modificar a paleta de modo que todas as cores sejam, por exemplo, apenas diferentes intensidades e tonalidades de verde.
Os terminais TTY (os consoles) não possuem uma interface
gráfica para facilitar a definição da paleta de cores e não é uma tarefa
trivial fazer essa alteração. Para a nossa sorte, porém, nós também
temos sequências de escape para controlar a paleta do TTY: o detalhe é
que sequências de escape são executadas a partir de um
shell. No caso do Bash, nós podemos utilizar o comando interno
printf
para executar as sequências ANSI, como já fizemos no
terminal gráfico, só que, desta vez, nós vamos manipular a paleta de
cores.
Sequências de escape OSC são, como o nome diz,
Comandos para o Sistema Operacional (Operating
System Commands, em inglês). O console do Linux (e aqui é Linux
mesmo, o kernel) utiliza a sequência \e]PXRRGGBB
para
definir a combinação dos componentes RGB na paleta de cada cor de índice
X
(um dígito hexadecimal de 0
a
F
).
Importante! as sequências OSC para definição da paleta do TTY devem ser executadas no TTY, ou poderão causar o travamento de alguns terminais gráficos!
Portanto, para alterar a paleta da cor de índice 2
(verde normal), nós podemos fazer, por exemplo:
:~$ printf '%b' '\e]P2009900'
A tabela completa fica assim:
Índice | Cor normal | Índice | Cor brilhante |
---|---|---|---|
P0 | Preto | P8 | Cinza escuro |
P1 | Vermelho | P9 | Vermelho brilhante |
P2 | Verde | PA | Verde brilhante |
P3 | Amarelo | PB | Amarelo brilhante |
P4 | Azul | PC | Azul brilhante |
P5 | Magenta | PD | Magenta brilhante |
P6 | Ciano | PE | Ciano brilhante |
P7 | Cinza claro | PF | Branco |
Como as sequências OSC para definição da paleta do TTY devem ser
executadas apenas no TTY, nós podemos fazer um teste, antes de
utilizá-las, exapandindo a variável TERM
:
:~$ echo $TERM
linux
Em um script, seria algo assim:
[[ $TERM = 'linux' ]] && printf '%b' '\e]P2009900'
As alterações da paleta do TTY afetam apenas o console em que foram
feitas e irão durar até o próximo reboot. Para que elas sejam
aplicadas novamente a cada início de uma sessão do Bash no console, será
necessário incluir os comandos no arquivo ~/.bashrc
.
Contudo, o .bashrc
é carregado indiscriminadamente em todos
os terminais, gráficos ou não, quando o Bash é iniciado. Por este
motivo, é fortemente recomendado criar uma função
e condicionar sua chamada à expansão da variável TERM
.
No meu .bashrc
, eu inclui esta função:
# ------------------------------------------------------------------------------
# Paleta de cores do TTY
# ------------------------------------------------------------------------------
paleta_de_cores() {
local -a paleta
paleta=(
'\e]P00A0B1E'
'\e]P1ED4646'
'\e]P24E9743'
'\e]P3DDA434'
'\e]P42A94e0'
'\e]P59F33BA'
'\e]P61BA797'
'\e]P7C1C2C6'
'\e]P8585A6C'
'\e]P9EB6E6E'
'\e]PA7BC86F'
'\e]PBE6C758'
'\e]PC6BC2FA'
'\e]PDC990DC'
'\e]PE70D9C8'
'\e]PFFBFCFE'
)
printf '%b' ${paleta[@]}
# O 'clear' é opcional...
clear
}
[[ $TERM = 'linux' ]] && paleta_de_cores
# ------------------------------------------------------------------------------
Assim que a sessão do Bash for iniciada em um console TTY, a função
será executada e a minha paleta de cores será redefinida. Porém, ela não
afetará os caracteres que já tiverem sido exibidos nem a cor de fundo do
console. Por este motivo, opcionalmente, nós podemos incluir o comando
clear
, que aparece no código da função, mas isso implica em
perder todas as mensagens anteriormente exibidas. Se isso não for um
problema para você, mantenha o clear
na função. Caso
contrário, basta executar o atalho Ctrl+L
quando for mais
conveniente – a escolha é sua!