ширина поля printf не поддерживает многобайтовые символы? - PullRequest
2 голосов
/ 31 июля 2011

Я хочу, чтобы printf распознавал многобайтовые символы при расчете ширины поля, чтобы столбцы располагались правильно ... Я не могу найти ответ на эту проблему, и мне было интересно, есть ли у кого-нибудь здесь какие-либо предложения или, возможно, функция / скрипт, который решает эту проблему.

Вот быстрый и грязный пример:

printf "## %5s %5s %5s ##\n## %5s %5s %5s ##\n" '' '*' '' '' "•" ''
>##           *       ##
>##         •       ##

Очевидно, я хочу получить результат:

>##           *       ##
>##           •       ##

Есть ли способ достичь этого?

Ответы [ 5 ]

1 голос
/ 31 июля 2011

Такой язык, как python, вероятно, решит ваши проблемы более простым и управляемым способом ...

#!/usr/bin/python
# coding=utf-8

import sys
import codecs
import unicodedata

out = codecs.getwriter('utf-8')(sys.stdout)

def width(string):
    return sum(1+(unicodedata.east_asian_width(c) in "WF")
        for c in string)

a1=[u'する', u'します', u'trazan', u'した', u'しました']
a2=[u'dipsy', u'laa-laa', u'banarne', u'po', u'tinky winky']

for i,j in zip(a1,a2):
    out.write('%s %s: %s\n' % (i, ' '*(12-width(i)), j))
1 голос
/ 31 июля 2011

Я, вероятно, буду использовать GNU awk:

awk 'BEGIN{ printf "## %5s %5s %5s ##\n## %5s %5s %5s ##\n", "", "*", "", "", "•", "" }'
##           *       ##
##           •       ##

Вы даже можете написать функцию оболочки оболочки с именем printf поверх awk, чтобы сохранить тот же интерфейс:

tr2awk() { 
    FMT="$1"
    echo -n "gawk 'BEGIN{ printf \"$FMT\""
    shift
    for ARG in "$@"
        do echo -n ", \"$ARG\""
    done
    echo " }'"
}

, а затем переопределить printf с помощью простой функции:

printf() { eval `tr2awk "$@"`; }

Проверьте это:

# buggy printf binary test:
/usr/bin/printf "## %5s %5s %5s ##\n## %5s %5s %5s ##\n" '' '*' '' '' "•" ''
##           *       ##
##         •       ##
# buggy printf shell builin test:
builtin printf "## %5s %5s %5s ##\n## %5s %5s %5s ##\n" '' '*' '' '' "•" ''
##           *       ##
##         •       ##

# fixed printf function test:
printf "## %5s %5s %5s ##\n## %5s %5s %5s ##\n" '' '*' '' '' "•" ''
##           *       ##
##           •       ##
1 голос
/ 31 июля 2011

Лучшее, что я могу придумать:

function formatwidth
{
  local STR=$1; shift
  local WIDTH=$1; shift
  local BYTEWIDTH=$( echo -n "$STR" | wc -c )
  local CHARWIDTH=$( echo -n "$STR" | wc -m )
  echo $(( $WIDTH + $BYTEWIDTH - $CHARWIDTH ))
}

printf "## %5s %*s %5s ##\n## %5s %*s %5s ##\n" \
    '' $( formatwidth "*" 5 ) '*' '' \
    '' $( formatwidth "•" 5 ) "•" ''

Вы используете спецификатор ширины *, чтобы принять ширину в качестве аргумента, и рассчитать необходимую ширину, добавив количество дополнительных байтов в многобайтовых символах.

Обратите внимание, что в GNU wc -c возвращает байты, а -m возвращает (возможно, многобайтовые) символы.

0 голосов
/ 31 июля 2011

Чистое решение оболочки

right_justify() {
        # parameters: field_width string
        local spaces questions
        spaces=''
        questions=''
        while [ "${#questions}" -lt "$1" ]; do
                spaces=$spaces" "
                questions=$questions?
        done
        result=$spaces$2
        result=${result#"${result%$questions}"}
}

Обратите внимание, что это все еще не работает в тире, потому что у тире нет поддержки локали.

0 голосов
/ 31 июля 2011

Это единственный способ? Нет способа сделать это только с printf?

Хорошо, с примером из ninjalj (thx btw) я написал скрипт для решения этой проблемы и сохранил его как fprintf в /usr/local/bin:

#! /bin/bash

IFS=' '
declare -a Text=("${@}")

## Skip the whole thing if there are no multi-byte characters ##
if (( $(echo "${Text[*]}" | wc -c) > $(echo "${Text[*]}" | wc -m) )); then
    if echo "${Text[*]}" | grep -Eq '%[#0 +-]?[0-9]+(\.[0-9]+)?[sb]'; then
        IFS=$'\n'
        declare -a FormatStrings=($(echo -n "${Text[0]}" | grep -Eo '%[^%]*?[bs]'))
        IFS=$' \t\n'
        declare -i format=0

    ## Check every format string ##
        for fw in "${FormatStrings[@]}"; do
            (( format++ ))
            if [[ "$fw" =~ ^%[#0\ +-]?[1-9][0-9]*(\.[1-9][0-9]*)?[sb]$ ]]; then
                (( Difference = $(echo "${Text[format]}" | wc -c) - $(echo "${Text[format]}" | wc -m) ))

            ## If multi-btye characters ##
                if (( Difference > 0 )); then

                ## If a field width is entered then replace field width value ##
                    if [[ "$fw" =~ ^%[#0\ +-]?[1-9][0-9]* ]]; then
                        (( Width = $(echo -n "$fw" | gsed -re 's|^%[#0 +-]?([1-9][0-9]*).*[bs]|\1|') + Difference ))
                        declare -a Text[0]="$(echo -n "${Text[0]}" | gsed -rne '1h;1!H;${g;y|\n|\x1C|;s|(%[^%])|\n\1|g;p}' | gsed -rne $(( format + 1 ))'s|^(%[#0 +-]?)[1-9][0-9]*|\1'${Width}'|;1h;1!H;${g;s|\n||g;y|\x1C|\n|;p}')"
                    fi

                ## If a precision is entered then replace precision value ##
                    if [[ "$fw" =~ \.[1-9][0-9]*[sb]$ ]]; then
                        (( Precision = $(echo -n "$fw" | gsed -re 's|^%.*\.([1-9][0-9]*)[sb]$|\1|') + Difference ))
                        declare -a Text[0]="$(echo -n "${Text[0]}" | gsed -rne '1h;1!H;${g;y|\n|\x1C|;s|(%[^%])|\n\1|g;p}' | gsed -rne $(( format + 1 ))'s|^(%[#0 +-]?([1-9][0-9]*)?)\.[1-9][0-9]*([bs])|\1.'${Precision}'\3|;1h;1!H;${g;s|\n||g;y|\x1C|\n|;p}')"
                    fi
                fi
            fi
        done
    fi
fi

printf "${Text[@]}"
exit 0

Использование: fprintf "## %5s %5s %5s ##\n## %5s %5s %5s ##\n" '' '*' '' '' '•' ''

Несколько замечаний:
• Я не писал этот скрипт для работы со * (звездочкой) значениями для форматов, потому что я никогда не использую их. Я написал это для себя и не хотел слишком усложнять вещи.
• Я написал это, чтобы проверить только строки формата %s и %b, так как они кажутся единственными, на которые влияет эта проблема. Таким образом, если кому-то удастся извлечь многобайтовый символ Юникода из числа, он может не работать без незначительных изменений.
• Сценарий прекрасно работает для базового использования printf (не для какого-то хакера UNIX старого скулера), не стесняйтесь модифицировать или использовать как есть!

- Aesthir

...