Qual seria a maneira mais próxima de uma maneira portátil de obter a largura de exibição (pelo menos em um terminal (um que exibe caracteres no local atual com a largura correta)) de uma sequência de caracteres de um script de shell.
Estou interessado principalmente na largura de caracteres que não são de controle, mas as soluções que levam em conta caracteres de controle como backspace, retorno de carro e tabulação horizontal também são bem-vindas.
Em outras palavras, estou procurando uma API de shell em torno dowcswidth()
função POSIX.
Esse comando deve retornar:
$ that-command 'unix' # 4 fullwidth characters
8
$ that-command 'Stéphane' # 9 characters, one of which zero-width
8
$ that-command 'もで 諤奯ゞ' # 5 double-width Japanese characters and a space
11
Pode-se usar ksh93
's' printf '%<n>Ls'
que levam em consideração a largura dos caracteres para preenchimento de <n>
colunas, ou o col
comando (com por exemplo printf '++%s\b\b--\n' <character> | col -b
) para tentar derivar isso, existe um Text :: CharWidthperl
pelo menos um módulo , mas existem abordagens mais diretas ou portáteis.
Isso é mais ou menos um acompanhamento dessa outra pergunta que era sobre a exibição de texto à direita da tela, para a qual você precisaria ter essas informações antes de exibir o texto.
fonte
Respostas:
Em um emulador de terminal, pode-se usar o relatório de posição do cursor para obter posições antes / depois, por exemplo, de
e descubra a largura dos caracteres impressos no terminal. Como é uma sequência de controle ECMA-48 (assim como VT100) suportada por quase todos os terminais que você provavelmente usa, é bastante portátil.
Para referência
Por fim, o emulador de terminal determina a largura imprimível, devido a esses fatores:
wcswidth
sozinho não diz como os caracteres combinados são tratados; O POSIX não menciona esse aspecto na descrição dessa função.wcswidth
sozinho (consulte, por exemplo, o Capítulo 2. Configurando o Cygwin ).xterm
por exemplo, tem provisão para a seleção de caracteres de largura dupla para as configurações necessárias.As chamadas de APIs do Shell
wcswidth
são suportadas em vários graus:Essas são mais ou menos diretas: simulando
wcswidth
no caso do Perl, chamando o tempo de execução C do Ruby e Python. Você pode até usar maldições, por exemplo, do Python (que trataria da combinação de caracteres):filter
função (para linhas únicas)addstr
, verificando se há erro (caso seja muito longo) e depois para a posição finalendwin
(que não deve fazer arefresh
)Usar maldições para a saída (em vez de alimentar as informações de volta para um script ou chamar diretamente
tput
) limparia a linha inteira (filter
limita-a a uma linha).fonte
wcswidth()
que dizer sobre algo.plink
que é definido,TERM=xterm
mesmo que não responda a nenhuma sequência de controle. Mas eu não uso terminais muito exóticos.fold
aparentemente é específico para lidar com caracteres de vários bytes e largura estendida . Eis como deve lidar com o backspace: A contagem atual da largura da linha deve ser diminuída em um, embora a contagem nunca se torne negativa. O utilitário de dobra não deve inserir um <newline> imediatamente antes ou depois de qualquer <backspace>, a menos que o caractere a seguir tenha uma largura maior que 1 e faça com que a largura da linha exceda a largura. talvezfold -w[num]
epr +[num]
poderia ser unido de alguma forma?Para strings de uma linha, a implementação GNU de
wc
tem uma opção-L
(aka--max-line-length
) que faz exatamente o que você está procurando (exceto os caracteres de controle).fonte
tab
também (assume que a tabulação é interrompida a cada 8 colunas).wc -L <<< 'unix'
→ 8,wc -L <<< 'Stéphane'
→ 8 ewc -L <<< 'もで 諤奯ゞ'
→ 11. PS Você considera "Stéphane" com nove caracteres, um dos quais com largura zero? Parece-me oito caracteres, um dos quais é de vários bytes.No meu
.profile
, eu chamo um script para determinar a largura de uma string em um terminal. Uso isso ao efetuar login no console de uma máquina em que não confio no conjunto do sistemaLC_CTYPE
ou quando faço logon remotamente e não posso confiarLC_CTYPE
para corresponder ao lado remoto. Meu script consulta o terminal, em vez de chamar qualquer biblioteca, porque esse era o ponto principal do meu caso de uso: determinar a codificação do terminal.Isso é frágil de várias maneiras:
plink
método, e eu o resolvi usando oplinkx
método .)Isso pode ou não corresponder ao seu caso de uso.
O script retorna a largura em seu status de retorno, cortada para 100. Uso de amostra:
fonte
printf "\r%*s\r" $((${#text}+8)) " ";
ao final decleanup
(adicionar 8 é arbitrário; ele precisa ser longo o suficiente para cobrir a saída mais ampla de locais mais antigos, mas estreito o suficiente para evitar uma quebra de linha). Isso faz com que o teste invisível, embora também assume nada foi impresso na linha (que é bom em um~/.profile
)text="Éé"
e${#text}
fornecer a largura de exibição (eu entro4
em um terminal não unicode e2
em um terminal compatível com unicode). Isso não é verdade para o bash.${#text}
não oferece a largura de exibição. Fornece o número de caracteres na codificação usada pelo código de idioma atual. O que é inútil para o meu propósito, pois quero determinar a codificação do terminal. É útil se você deseja a largura da tela por algum outro motivo, mas não é preciso porque nem todos os caracteres têm uma unidade de largura. Por exemplo, combinar acentos tem uma largura de 0 e ideogramas chineses têm uma largura de 2.Eric Pruitt escreveu uma implementação impressionante de
wcwidth()
ewcswidth()
no Awk disponível em wcwidth.awk . Fornece principalmente 4 funçõesonde
wcscolumns()
também tolera caracteres não imprimíveis.Eu abri um problema perguntando sobre o manuseio de TABs, pois
wcscolumns($'My sign is\t鼠鼠')
deveria ser maior que 14. Atualização: Eric adicionou a funçãowcsexpand()
para expandir os TABs aos espaços:fonte
Para expandir as dicas de possíveis soluções usando
col
eksh93
na minha pergunta:Usando o
col
frombsdmainutils
no Debian (pode não funcionar com outrascol
implementações), para obter a largura de um único caractere não-controle:Exemplo:
Estendido para uma sequência:
Usando
ksh93
'sprintf '%Ls'
:Usando
perl
'sText::CharWidth
:fonte