New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

printf e UTF-8 #180

Open
aureliojargas opened this Issue Mar 12, 2015 · 16 comments

Comments

Projects
None yet
2 participants
@aureliojargas
Member

aureliojargas commented Mar 12, 2015

A codificação UTF-8 é multibyte, ou seja, cada caractere pode ser composto por um ou mais bytes.

  • A letra a é composta por um byte
  • A letra á é composta por dois bytes
  • O símbolo é composto por três bytes

Confira:

$ printf 'a' | od -t x1
0000000    61                                                            
0000001
$ printf 'á' | od -t x1
0000000    c3  a1                                                        
0000002
$ printf '' | od -t x1
0000000    e2  99  a5                                                    
0000003
$

Quando você usa o printf para alinhar colunas, definindo uma largura fixa, as coisas não funcionam como se espera, pois o printf %<número>s conta bytes e não caracteres:

$ printf '|%5s|\n' a m z á ú ♥ ★      # Comportamento esperado 
|    a|
|    m|
|    z|
|    á|
|    ú|
|    ♥|
|    ★|

$ printf '|%5s|\n' a m z á ú ♥ ★      # Vida real :(
|    a|
|    m|
|    z|
|   á|
|   ú|
|  ♥|
|  ★|

Por isso a saída de algumas funções aparece desalinhada quando há letras acentuadas no resultado. Cada letra acentuada do português vale por dois :/ Exemplos de funções problemáticas:

  • zzfutebol
  • zzpais
  • zzquimica

Talvez no futuro o printf seja atualizado para levar em conta a contagem de caracteres e não de bytes, mas como Unicode é um assunto bem complexo, isso pode demorar bastante. Para ter uma ideia do tamanho do problema, veja esta resposta: http://stackoverflow.com/a/9325750/1623438

Bem, este é o problema. Abri este issue para discutirmos possíveis soluções. Lembrando que nosso universo se restringe ao português, então uma solução meia-boca somente para caracteres Latin-1 já é suficiente.

  • Como garantir uma saída alinhada das funções que hoje usam printf?
  • Como contar caracteres e não bytes?
@aureliojargas

This comment has been minimized.

Show comment
Hide comment
@aureliojargas

aureliojargas Mar 12, 2015

Member

Uma solução alternativa que funciona para alguns casos, é separar os campos por tabs e usar o expand para trocar os tabs por espaços. Isso é feito na zzestado e funciona no BSD.

# trecho no final da zzestado
zzestado --formato '{sigla}\t{nome}\t{capital}\n' | expand -t 6,29

Exemplo de execução:

$ zzestado
AC    Acre                   Rio Branco
AL    Alagoas                Maceió
AP    Amapá                  Macapá
AM    Amazonas               Manaus
BA    Bahia                  Salvador
CE    Ceará                  Fortaleza
DF    Distrito Federal       Brasília
ES    Espírito Santo         Vitória
GO    Goiás                  Goiânia
MA    Maranhão               São Luís
MT    Mato Grosso            Cuiabá
MS    Mato Grosso do Sul     Campo Grande
MG    Minas Gerais           Belo Horizonte
PA    Pará                   Belém
PB    Paraíba                João Pessoa
PR    Paraná                 Curitiba
PE    Pernambuco             Recife
PI    Piauí                  Teresina
RJ    Rio de Janeiro         Rio de Janeiro
RN    Rio Grande do Norte    Natal
RS    Rio Grande do Sul      Porto Alegre
RO    Rondônia               Porto Velho
RR    Roraima                Boa Vista
SC    Santa Catarina         Florianópolis
SP    São Paulo              São Paulo
SE    Sergipe                Aracaju
TO    Tocantins              Palmas
$
Member

aureliojargas commented Mar 12, 2015

Uma solução alternativa que funciona para alguns casos, é separar os campos por tabs e usar o expand para trocar os tabs por espaços. Isso é feito na zzestado e funciona no BSD.

# trecho no final da zzestado
zzestado --formato '{sigla}\t{nome}\t{capital}\n' | expand -t 6,29

Exemplo de execução:

$ zzestado
AC    Acre                   Rio Branco
AL    Alagoas                Maceió
AP    Amapá                  Macapá
AM    Amazonas               Manaus
BA    Bahia                  Salvador
CE    Ceará                  Fortaleza
DF    Distrito Federal       Brasília
ES    Espírito Santo         Vitória
GO    Goiás                  Goiânia
MA    Maranhão               São Luís
MT    Mato Grosso            Cuiabá
MS    Mato Grosso do Sul     Campo Grande
MG    Minas Gerais           Belo Horizonte
PA    Pará                   Belém
PB    Paraíba                João Pessoa
PR    Paraná                 Curitiba
PE    Pernambuco             Recife
PI    Piauí                  Teresina
RJ    Rio de Janeiro         Rio de Janeiro
RN    Rio Grande do Norte    Natal
RS    Rio Grande do Sul      Porto Alegre
RO    Rondônia               Porto Velho
RR    Roraima                Boa Vista
SC    Santa Catarina         Florianópolis
SP    São Paulo              São Paulo
SE    Sergipe                Aracaju
TO    Tocantins              Palmas
$
@aureliojargas

This comment has been minimized.

Show comment
Hide comment
@aureliojargas

aureliojargas Mar 12, 2015

Member

No BSD, o awk sofre do mesmo problema:

$ awk 'BEGIN { printf "|%5s|\n", "a" }'
|    a|
$ awk 'BEGIN { printf "|%5s|\n", "á" }'
|   á|
$ awk 'BEGIN { printf "|%5s|\n", "♥" }'
|  ♥|
$
Member

aureliojargas commented Mar 12, 2015

No BSD, o awk sofre do mesmo problema:

$ awk 'BEGIN { printf "|%5s|\n", "a" }'
|    a|
$ awk 'BEGIN { printf "|%5s|\n", "á" }'
|   á|
$ awk 'BEGIN { printf "|%5s|\n", "♥" }'
|  ♥|
$
@aureliojargas

This comment has been minimized.

Show comment
Hide comment
@aureliojargas

aureliojargas Mar 12, 2015

Member

No BSD, algumas ferramentas acertam a contagem de caracteres.

$ printf "aá♥" | cut -c 2     # cut OK
á
$ printf "aá♥" | cut -c 3

$ printf "aá♥" | wc -c        # wc -c NÃO
       6
$ printf "aá♥" | wc -m        # wc -m OK (mas não sei se -m é portável)
       3
$ echo "aá♥" | sed 's/././2'  # sed OK
a.♥
$ echo "aá♥" | sed 's/././3'
aá.
$ echo "aá♥" | sed 's/././g'
...
$
Member

aureliojargas commented Mar 12, 2015

No BSD, algumas ferramentas acertam a contagem de caracteres.

$ printf "aá♥" | cut -c 2     # cut OK
á
$ printf "aá♥" | cut -c 3

$ printf "aá♥" | wc -c        # wc -c NÃO
       6
$ printf "aá♥" | wc -m        # wc -m OK (mas não sei se -m é portável)
       3
$ echo "aá♥" | sed 's/././2'  # sed OK
a.♥
$ echo "aá♥" | sed 's/././3'
aá.
$ echo "aá♥" | sed 's/././g'
...
$
@aureliojargas

This comment has been minimized.

Show comment
Hide comment
@aureliojargas

aureliojargas Mar 12, 2015

Member

Dentro do universo ZZ, acredito que a zzpad seja a função ideal para concentrar os esforços de imprimir com o padding correto, incluindo UTF-8. Se conseguirmos arrumá-la, as outras funções que hoje usam printfpara alinhamento poderiam passar a usá-la.

Mas tem que arrumar, pois hoje ela sofre do mesmo problema:

$ zzpad -l 5 _ a 
____a
$ zzpad -l 5 _ á
___á
$ zzpad -l 5 _ ♥
__♥
$
Member

aureliojargas commented Mar 12, 2015

Dentro do universo ZZ, acredito que a zzpad seja a função ideal para concentrar os esforços de imprimir com o padding correto, incluindo UTF-8. Se conseguirmos arrumá-la, as outras funções que hoje usam printfpara alinhamento poderiam passar a usá-la.

Mas tem que arrumar, pois hoje ela sofre do mesmo problema:

$ zzpad -l 5 _ a 
____a
$ zzpad -l 5 _ á
___á
$ zzpad -l 5 _ ♥
__♥
$
@aureliojargas

This comment has been minimized.

Show comment
Hide comment
@aureliojargas

aureliojargas Mar 12, 2015

Member

Consegui um exemplo funcional em sed:

$ echo a | sed -e ':loop' -e 's/^/_/' -e '/^.\{5\}/! b loop' 
____a
$ echo á | sed -e ':loop' -e 's/^/_/' -e '/^.\{5\}/! b loop' 
____á
$ echo| sed -e ':loop' -e 's/^/_/' -e '/^.\{5\}/! b loop' 
____♥
$
Member

aureliojargas commented Mar 12, 2015

Consegui um exemplo funcional em sed:

$ echo a | sed -e ':loop' -e 's/^/_/' -e '/^.\{5\}/! b loop' 
____a
$ echo á | sed -e ':loop' -e 's/^/_/' -e '/^.\{5\}/! b loop' 
____á
$ echo| sed -e ':loop' -e 's/^/_/' -e '/^.\{5\}/! b loop' 
____♥
$

aureliojargas added a commit that referenced this issue Mar 12, 2015

zzpad: adicionados testes para UTF-8
A função ainda não consegue passar nestes testes. Vide issue #180
@aureliojargas

This comment has been minimized.

Show comment
Hide comment
@aureliojargas

aureliojargas Mar 12, 2015

Member

Mais um teste, a contagem de caracteres do bash ${#var} funcionou corretamente:

$ x="aá♥"
$ echo ${#x}
3
$ bash --version
GNU bash, version 3.2.53(1)-release (x86_64-apple-darwin14)
Copyright (C) 2007 Free Software Foundation, Inc.
$

Mas não sei desde qual versão do bash isso funciona.

Member

aureliojargas commented Mar 12, 2015

Mais um teste, a contagem de caracteres do bash ${#var} funcionou corretamente:

$ x="aá♥"
$ echo ${#x}
3
$ bash --version
GNU bash, version 3.2.53(1)-release (x86_64-apple-darwin14)
Copyright (C) 2007 Free Software Foundation, Inc.
$

Mas não sei desde qual versão do bash isso funciona.

aureliojargas added a commit that referenced this issue Mar 12, 2015

zzpad: agora suporta UTF-8
Veja issue #180 para mais informações.
@aureliojargas

This comment has been minimized.

Show comment
Hide comment
@aureliojargas

aureliojargas Mar 12, 2015

Member

@itamarnet desculpe remover o teu awk da zzpad, mas com o sed agora ela funciona corretamente com UTF-8. Pelo menos aqui na minha máquina. Confirma que na tua ficou OK também?

$ zzpad -l 5 _ a 
____a
$ zzpad -l 5 _ á
____á
$ zzpad -l 5 _ ♥
____♥
$

Agora, a zzpad pode substituir o printf para o alinhamento de colunas na saída.

E mais, como ao usar uma subshell a quebra de linha no final é removida, podemos usar esta função diretamente, várias vezes, num único echo:

$ echo "$(zzpad -l 5 _ á) $(zzpad -l 5 _ ★)"
____á ____★
$
Member

aureliojargas commented Mar 12, 2015

@itamarnet desculpe remover o teu awk da zzpad, mas com o sed agora ela funciona corretamente com UTF-8. Pelo menos aqui na minha máquina. Confirma que na tua ficou OK também?

$ zzpad -l 5 _ a 
____a
$ zzpad -l 5 _ á
____á
$ zzpad -l 5 _ ♥
____♥
$

Agora, a zzpad pode substituir o printf para o alinhamento de colunas na saída.

E mais, como ao usar uma subshell a quebra de linha no final é removida, podemos usar esta função diretamente, várias vezes, num único echo:

$ echo "$(zzpad -l 5 _ á) $(zzpad -l 5 _ ★)"
____á ____★
$
@itamarnet

This comment has been minimized.

Show comment
Hide comment
@itamarnet

itamarnet Mar 12, 2015

Contributor

Grande @aureliojargas não precisa se desculpar.
O uso do awk atendia uma necessidade menos ampla e foi uma solução encontrada num momento.
Ter uma melhora usando o sed não é algo para lamentar, mas a comemorar devido a ampliação da capacidade.
Se eu dominasse o sed da mesma maneira que você teria feito antes, mas não é o caso. Ao menos o que foi feito antes serviu como um ponto de partida da idéia, e a função e eu agradeço.
A noite testo e dou um feedback.
👍

Contributor

itamarnet commented Mar 12, 2015

Grande @aureliojargas não precisa se desculpar.
O uso do awk atendia uma necessidade menos ampla e foi uma solução encontrada num momento.
Ter uma melhora usando o sed não é algo para lamentar, mas a comemorar devido a ampliação da capacidade.
Se eu dominasse o sed da mesma maneira que você teria feito antes, mas não é o caso. Ao menos o que foi feito antes serviu como um ponto de partida da idéia, e a função e eu agradeço.
A noite testo e dou um feedback.
👍

@itamarnet

This comment has been minimized.

Show comment
Hide comment
@itamarnet

itamarnet Mar 12, 2015

Contributor

Com relação ao wc -m, também não sei se é portável, mas parece que sim.
Tomo por base esse link, e apenas alguns sabores é que não suportam essa opção.
http://www.unix.com/man-page/posix/1p/wc/

Acho que podemos considerar que abrange tudo dentro do universo de ação.
Me referencio em especial a ele, pela possibilidade do retorno do tamanho da string de forma limpa e correta.
E é esse número que pode ser usado pela zzpad, auxiliando a priori zzcolunar e zzalinhar

Obs.: Pena que "wc -L" tenha uma abrangência menor nos SO's, seria uma mão na roda e economizaria muito código.

Contributor

itamarnet commented Mar 12, 2015

Com relação ao wc -m, também não sei se é portável, mas parece que sim.
Tomo por base esse link, e apenas alguns sabores é que não suportam essa opção.
http://www.unix.com/man-page/posix/1p/wc/

Acho que podemos considerar que abrange tudo dentro do universo de ação.
Me referencio em especial a ele, pela possibilidade do retorno do tamanho da string de forma limpa e correta.
E é esse número que pode ser usado pela zzpad, auxiliando a priori zzcolunar e zzalinhar

Obs.: Pena que "wc -L" tenha uma abrangência menor nos SO's, seria uma mão na roda e economizaria muito código.

@itamarnet

This comment has been minimized.

Show comment
Hide comment
@itamarnet

itamarnet Mar 13, 2015

Contributor

Essa nova versão do zzpad funcionou perfeitamente.
Teste realizado em várias distros e máquinas.
Show! 👍

Contributor

itamarnet commented Mar 13, 2015

Essa nova versão do zzpad funcionou perfeitamente.
Teste realizado em várias distros e máquinas.
Show! 👍

@itamarnet

This comment has been minimized.

Show comment
Hide comment
@itamarnet

itamarnet Mar 19, 2015

Contributor

Infelizmente no GNU Linux o cut não tem a mesma agilidade que no BSD:

$ printf "aá♥" | cut -c 2         # não funciona
$ echo "aá♥" | cut -c 3           # não funciona
�

# Alinhamento varia em função da ocorrência de caracteres acentuados
$ zzquimica | head | cut -c 1-26 | sed 's/$/~/'
N.º  Nome          Símbo~
1    Hidrogênio    H     ~
2    Hélio         He    ~
3    Lítio         Li    ~
4    Berílio       Be    ~
5    Boro          B      ~
6    Carbono       C      ~
7    Nitrogênio    N     ~
8    Oxigênio      O     ~
9    Flúor         F     ~

O wc -m conta corretamente, e o sed mantém a coerência como no BSD.
Isso justifica a mudança feita no commit f13a655

Contributor

itamarnet commented Mar 19, 2015

Infelizmente no GNU Linux o cut não tem a mesma agilidade que no BSD:

$ printf "aá♥" | cut -c 2         # não funciona
$ echo "aá♥" | cut -c 3           # não funciona
�

# Alinhamento varia em função da ocorrência de caracteres acentuados
$ zzquimica | head | cut -c 1-26 | sed 's/$/~/'
N.º  Nome          Símbo~
1    Hidrogênio    H     ~
2    Hélio         He    ~
3    Lítio         Li    ~
4    Berílio       Be    ~
5    Boro          B      ~
6    Carbono       C      ~
7    Nitrogênio    N     ~
8    Oxigênio      O     ~
9    Flúor         F     ~

O wc -m conta corretamente, e o sed mantém a coerência como no BSD.
Isso justifica a mudança feita no commit f13a655

@aureliojargas

This comment has been minimized.

Show comment
Hide comment
@aureliojargas

aureliojargas Mar 20, 2015

Member

Como está o locale dessa máquina?

Member

aureliojargas commented Mar 20, 2015

Como está o locale dessa máquina?

@itamarnet

This comment has been minimized.

Show comment
Hide comment
@itamarnet

itamarnet Mar 20, 2015

Contributor

Ops.: esqueci de informar.
Locale: pt_BR.UTF-8

Contributor

itamarnet commented Mar 20, 2015

Ops.: esqueci de informar.
Locale: pt_BR.UTF-8

@aureliojargas

This comment has been minimized.

Show comment
Hide comment
@aureliojargas

aureliojargas Mar 20, 2015

Member

Que estranho... As ferramentas GNU em geral são mais avançadas, eram pra suportar melhor o UTF-8. Eu estava tendo problemas também num Debian, até que conferi e o locale não estava definido corretamente em todas as variáveis. Foi só arrumar e daí funcionou.

Dá uma olhada como ficou:

$ locale
LANG=pt_BR.utf8
LANGUAGE=
LC_CTYPE="pt_BR.utf8"
LC_NUMERIC="pt_BR.utf8"
LC_TIME="pt_BR.utf8"
LC_COLLATE="pt_BR.utf8"
LC_MONETARY="pt_BR.utf8"
LC_MESSAGES="pt_BR.utf8"
LC_PAPER="pt_BR.utf8"
LC_NAME="pt_BR.utf8"
LC_ADDRESS="pt_BR.utf8"
LC_TELEPHONE="pt_BR.utf8"
LC_MEASUREMENT="pt_BR.utf8"
LC_IDENTIFICATION="pt_BR.utf8"
LC_ALL=pt_BR.utf8
$

Eu estou desconfiado que possa estar faltando alguma coisa no teu locale.

Member

aureliojargas commented Mar 20, 2015

Que estranho... As ferramentas GNU em geral são mais avançadas, eram pra suportar melhor o UTF-8. Eu estava tendo problemas também num Debian, até que conferi e o locale não estava definido corretamente em todas as variáveis. Foi só arrumar e daí funcionou.

Dá uma olhada como ficou:

$ locale
LANG=pt_BR.utf8
LANGUAGE=
LC_CTYPE="pt_BR.utf8"
LC_NUMERIC="pt_BR.utf8"
LC_TIME="pt_BR.utf8"
LC_COLLATE="pt_BR.utf8"
LC_MONETARY="pt_BR.utf8"
LC_MESSAGES="pt_BR.utf8"
LC_PAPER="pt_BR.utf8"
LC_NAME="pt_BR.utf8"
LC_ADDRESS="pt_BR.utf8"
LC_TELEPHONE="pt_BR.utf8"
LC_MEASUREMENT="pt_BR.utf8"
LC_IDENTIFICATION="pt_BR.utf8"
LC_ALL=pt_BR.utf8
$

Eu estou desconfiado que possa estar faltando alguma coisa no teu locale.

@itamarnet

This comment has been minimized.

Show comment
Hide comment
@itamarnet

itamarnet Mar 21, 2015

Contributor

Pois é Aurélio, deve ser algo que eu ainda não percebi.
Variei a configuração do locale, e o cut sempre teve o mesmo problema.
veja minha última saída do locale.

$ locale
LANG=pt_BR.utf8
LANGUAGE=
LC_CTYPE="pt_BR.utf8"
LC_NUMERIC=pt_BR.utf8
LC_TIME=pt_BR.utf8
LC_COLLATE="pt_BR.utf8"
LC_MONETARY=pt_BR.utf8
LC_MESSAGES="pt_BR.utf8"
LC_PAPER=pt_BR.utf8
LC_NAME=pt_BR.utf8
LC_ADDRESS=pt_BR.utf8
LC_TELEPHONE=pt_BR.utf8
LC_MEASUREMENT=pt_BR.utf8
LC_IDENTIFICATION=pt_BR.utf8
LC_ALL=

Mesmo com LC_ALL=pt_BR.utf8, o problema manteve-se
E antes no lugar do "utf8" estava "UTF-8" e o comportamento não mudou.
Estou sem idéias! Alguma sugestão?

Contributor

itamarnet commented Mar 21, 2015

Pois é Aurélio, deve ser algo que eu ainda não percebi.
Variei a configuração do locale, e o cut sempre teve o mesmo problema.
veja minha última saída do locale.

$ locale
LANG=pt_BR.utf8
LANGUAGE=
LC_CTYPE="pt_BR.utf8"
LC_NUMERIC=pt_BR.utf8
LC_TIME=pt_BR.utf8
LC_COLLATE="pt_BR.utf8"
LC_MONETARY=pt_BR.utf8
LC_MESSAGES="pt_BR.utf8"
LC_PAPER=pt_BR.utf8
LC_NAME=pt_BR.utf8
LC_ADDRESS=pt_BR.utf8
LC_TELEPHONE=pt_BR.utf8
LC_MEASUREMENT=pt_BR.utf8
LC_IDENTIFICATION=pt_BR.utf8
LC_ALL=

Mesmo com LC_ALL=pt_BR.utf8, o problema manteve-se
E antes no lugar do "utf8" estava "UTF-8" e o comportamento não mudou.
Estou sem idéias! Alguma sugestão?

@aureliojargas

This comment has been minimized.

Show comment
Hide comment
@aureliojargas

aureliojargas Mar 22, 2015

Member

É... Está tudo certo com teu locale também. Agora lascou :)

Então deve ser isso mesmo. Algumas ferramentas do Linux ainda estão quebradas no UTF-8 e temos que contornar :/

Member

aureliojargas commented Mar 22, 2015

É... Está tudo certo com teu locale também. Agora lascou :)

Então deve ser isso mesmo. Algumas ferramentas do Linux ainda estão quebradas no UTF-8 e temos que contornar :/

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment