printf: caracteres multibyte

6

Ao tentar formatar printf output envolvendo strings contendo caracteres de múltiplos bytes, ficou claro que printf não conta caracteres literais, mas o número de bytes, o que dificulta a formatação de texto se for byte único ou multi-byte caracteres são misturados. Por exemplo:

$ cat script
#!/bin/bash
declare -a a b
a+=("0")
a+=("00")
a+=("000")
a+=("0000")
a+=("00000")
b+=("0")
b+=("├─00")
b+=("├─000")
b+=("├─0000")
b+=("└─00000")
printf "%-15s|\n" "${a[@]}" "${b[@]}"

$ ./script
0              |
00             |
000            |
0000           |
00000          |
0              |
├─00       |
├─000      |
├─0000     |
└─00000    |

Eu encontrei várias alternativas sugeridas (principalmente wrappers usando outro idioma ou utilitário para imprimir o texto). Existe alguma solução bash nativa? Nenhuma das sequências de formato documentadas printf parece ajudar. As configurações de locale seriam relevantes nessa situação, por exemplo, para usar uma codificação de caracteres de largura fixa como UTF-32?

    
por user001 16.11.2017 / 22:22

4 respostas

4

Você poderia contornar isso dizendo ao terminal para mover o cursor para a posição desejada, em vez de ter printf count os caracteres.

$ printf "%s3[10G-\n" "abc" "├─cd" "└──ef"
abc      -
├─cd     -
└──ef    -

Bem, supondo que você esteja imprimindo em um terminal, isso é ...

A sequência de controle existe <ESC>[nnG , onde nn é a coluna para onde mover, em decimal.

É claro que, se a primeira coluna for maior que o espaço alocado, o resultado não será muito bom:

$ printf "%s3[10G-\n" "abcdefghijkl"
abcdefghi-kl

Para contornar isso, você pode limpar explicitamente o restante da linha ( <ESC>[K ) antes de imprimir a coluna a seguir.

$ printf "%s3[10G3[K-\n" "abcdefghijkl"
abcdefghi-

Outra maneira seria fazer o preenchimento manualmente, assumindo que temos algo que pode determinar o comprimento da string em caracteres. Isso parece funcionar no Bash para personagens simples, mas é claro que é um pouco feio. Caracteres de largura zero e dupla provavelmente irão quebrá-lo, e eu não testei a combinação de caracteres.

#!/bin/bash
pad() { 
    # parameters:
    #  1: name of variable to pad
    #  2: length to pad to
    local string=${!1}
    local len=${#string}
    printf -v "$1" "%s%$(($2 - len))s" "$string" ""
}
echo "1234567890"
for x in "abc" "├─cd" "└──ef" ; do
    pad x 9
    printf "%s-\n" "$x"
done

E a saída é:

1234567890
abc      -
├─cd     -
└──ef    -
    
por 26.11.2017 / 18:14
2

Eu fiz uma pequena pesquisa na web, mas não consegui encontrar uma solução para o seu problema no Bash puro, e acho que pode não haver uma. Eu me deparei com a seguinte postagem do StackOverflow:

A resposta mais votada (postada pelo usuário tchrist ) inclui o seguinte:

Yes, this is a problem with all versions of printf that I am aware of. I briefly discuss the matter in this answer and also in this one.

Eu também me deparei com o seguinte post no Unix StackExchange:

A solução aceita inclui a seguinte explicação:

POSIX requires printf's %-20s to count those 20 in terms of bytes not characters even though that makes little sense as printf is to print text, formatted (see discussion at the Austin Group (POSIX) and bash mailing lists).

Parece que o que você deseja fazer pode não ser possível com printf e que você terá que implementar sua própria solução.

Consegui produzir a saída desejada usando um script Python. Talvez você ache útil:

#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""script.py"""

# Set the default character encoding to UTF-8
import sys
reload(sys)
sys.setdefaultencoding("utf-8")

# Array of ASCII characters
a=[("0")]
a+=[("00")]
a+=[("000")]
a+=[("0000")]
a+=[("00000")]

# Array of UTF-8 Characters
b=[("0")]
b+=[("├─00")]
b+=[("├─000")]
b+=[("├─0000")]
b+=[("└─00000")]

# Print the elements from both arrays
for x in a + b:
    print (u"%-15s|" % x).encode('utf-8')

Aqui está o que recebo quando executo o script:

user@host:~$ python script.py

0              |
00             |
000            |
0000           |
00000          |
0              |
├─00           |
├─000          |
├─0000         |
└─00000        |
    
por 26.11.2017 / 17:43
2

Por que o printf "shrinking" umlaut? tem algumas soluções adequadas, invocando ferramentas adequadas para isso, já que bash perde a capacidade internamente ou mudando para diferentes shells, mas se você realmente quisesse implementá-la em bash com apenas comandos incorporados, existem maneiras de caracteres de largura única (potencialmente com bytes múltiplos).

No bash, como em todos os shells POSIX, você pode obter a largura em caracteres de um $string com ${#string} e ${#string} , mas no código do idioma C para a largura em bytes.

Então você pode explicar a discrepância com algo como:

clength() { clength=${#1}; }
blength() { local LC_ALL=C; blength=${#1}; }
align() {
  local format="$1" width="$2" arg blength clength
  shift 2
  for arg do
    clength "$arg"; blength "$arg"
    printf "$format" "$((width + blength - clength))" "$arg"
  done
}

a=(0 00 000 0000 00000)
b=(0 ├─00 ├─000 ├─0000 └─00000)
align '%-*s|\n' 12 "${a[@]}" "${b[@]}"

Para considerar largura zero (como as marcas de combinação) ou caracteres de largura dupla, não há solução com bash , a menos que você esteja pronto para codificar a lista desses caracteres em seu script (ou use escape de terminal seqüências para dizer ao terminal para alinhar o texto (último exemplo , ou ) e código rígido as seqüências de escape para todos os terminais suportados como bash não possuem uma interface embutida para terminfo / termcap também). O ksh93 é o único shell que eu conheço que tem suporte para alinhar caracteres de largura de exibição variável (por exemplo, também em o Q & linkado ).

    
por 26.11.2017 / 23:42
2

aqui está uma solução que usa wc -L .

for i in "${a[@]}" "${b[@]}"
do printf "%s%*s|\n" "$i" "$[15 - $(wc -L <<< "$i")]" ""
done

0              |
00             |
000            |
0000           |
00000          |
0              |
├─00           |
├─000          |
├─0000         |
└─00000        |

wc -L imprime a largura de exibição da entrada, portanto, ela funciona para caracteres de largura dupla e assim por diante

    
por 15.01.2018 / 01:42