printf가 분음 부호를 "축소"하는 이유는 무엇입니까?

printf가 분음 부호를 "축소"하는 이유는 무엇입니까?

다음과 같은 간단한 스크립트를 실행하면:

#!/bin/bash
printf "%-20s %s\n" "Früchte und Gemüse"   "foo"
printf "%-20s %s\n" "Milchprodukte"        "bar"
printf "%-20s %s\n" "12345678901234567890" "baz"

다음과 같이 인쇄됩니다.

Früchte und Gemüse foo
Milchprodukte        bar
12345678901234567890 baz

즉, 발음 구별 부호가 있는 텍스트(예: ü)는 발음 구별 부호당 한 문자씩 "축소"됩니다.

물론 어딘가에 잘못된 설정이 있지만 어떤 설정인지 알 수 없습니다.

이는 파일 인코딩이 UTF-8인 경우에 발생합니다.

인코딩을 latin-1로 변경하면 정렬은 정확하지만 움라우트 렌더링 오류가 발생합니다.

Fr�chte und Gem�se   foo
Milchprodukte        bar
12345678901234567890 baz

답변1

POSIX필요 printf%-20s20개를 세어 보자바이트아니요수치printf인쇄와는 관련이 없지만텍스트, 서식 지정(토론 참조오스틴 그룹에서(POSIX) 및bash메일링 리스트).

내장 printfPOSIX 쉘 bash과 대부분의 다른 POSIX 쉘은 이를 따릅니다.

zsh이 어리석은 요구 사항(시뮬레이션에서도 sh)을 무시하면 printf예상대로 작동합니다. printf내장된 것과 동일 합니다 fish(POSIX와 유사한 쉘이 아님).

UTF-8로 인코딩되면 문자 ü(U+00FC)는 2바이트(0xc3 및 0xbc)로 구성되며, 이는 이러한 차이점을 설명합니다.

$ printf %s 'Früchte und Gemüse' | wc -mcL
    18      20      18

문자열은 18자로 구성되고 너비는 18열( 입력에서 가장 넓은 줄의 표시 너비를 보고하기 위한 -LGNU 확장)이지만 20바이트로 인코딩됩니다.wc

zsh또는 에서는 fish텍스트가 올바르게 정렬됩니다.

이제 너비가 0인 문자(예: U+0308과 같은 문자 결합, 분음 기호 결합)나 많은 아시아 스크립트(Tab과 같은 제어 문자는 말할 것도 없고)처럼 너비가 두 배인 문자도 있어 zsh올바르게 정렬되지도 않습니다.

예를 들면 다음과 같습니다 zsh.

$ printf '%3s|\n' u ü $'u\u308' $'\u1100'
  u|
  ü|
 ü|
  ᄀ|

존재하다 bash:

$ printf '%3s|\n' u ü $'u\u308' $'\u1100'
  u|
 ü|
ü|
ᄀ|

ksh93%Ls너비를 계산하는 형식 사양이 있습니다.전시하다너비.

$ printf '%3Ls|\n' u ü $'u\u308' $'\u1100'
  u|
  ü|
  ü|
 ᄀ|

아직은작동하지 않습니다텍스트에 TAB과 같은 제어 문자가 포함되어 있는 경우(어떻게 이것이 가능합니까? printf출력 장치에서 탭 정지가 얼마나 떨어져 있는지, 그리고 인쇄가 시작되는 위치를 알아야 합니다). 백스페이스 문자와 함께 작동합니다( roff출력과 마찬가지로).X(굵게 X)는 )로 작성되지만 X\bXksh93모든 제어 문자를 너비로 간주합니다 -1.

다른 옵션

에서는 zsh패딩 매개변수 확장 플래그( l왼쪽 패딩, r오른쪽 패딩)를 사용할 수 있습니다. m이 플래그와 결합하면 문자열의 문자 수 대신 문자의 표시 너비를 고려합니다.

$ () { printf '%s|\n' "${(ml[3])@}"; } u ü $'u\u308' $'\u1100'
  u|
  ü|
  ü|
 ᄀ|

그리고 expand:

printf '%s\t|\n' u ü $'u\u308' $'\u1100' | expand -t3

이는 일부 expand구현에서 작동합니다(GNU에서는 작동하지 않음).

GNU 시스템에서는 awkGNU의 문자 수를 문자 단위로 사용할 수 있습니다 printf(바이트가 아니라 표시 너비가 아니므로 너비가 0이거나 너비가 2인 문자에는 여전히 적합하지 않지만 예에서는 괜찮습니다).

gawk 'BEGIN {for (i = 1; i < ARGC; i++) printf "%-3s|\n", ARGV[i]}
     ' u ü $'u\u308' $'\u1100'

터미널로 출력하는 경우 커서를 사용하여 이스케이프 시퀀스의 위치를 ​​지정할 수도 있습니다. 좋다:

forward21=$(tput cuf 21)
printf '%s\r%s%s\n' \
  "Früchte und Gemüse"    "$forward21" "foo" \
  "Milchprodukte"         "$forward21" "bar" \
  "12345678901234567890"  "$forward21" "baz"

답변2

${#var}bash3.0+부터 문자 계산이 정확합니다.

다음을 시도해 보세요(모든 버전의 bash 사용).

bash -c "a="$'aáíóuúüoözu\u308\u1100'';printf "%s\n" "${a} ${#a}"'

bash 3.0부터는 올바른 개수가 제공됩니다.

하지만 이를 위해서는 $'u\u308'bash 버전 4.2 이상이 필요합니다.

이를 통해 적절한 패딩을 계산할 수 있습니다.

#!/usr/bin/env bash

strings=(
  'Früchte und Gemüse'
  'Milchprodukte'
  '12345678901234567890'
)

# Initialize column width
cw=20

for str in "${strings[@]}"
do
  # Format column1 with computed padding
  printf -v col1string '%s%*s' "$str" $((cw-${#str})) ''

  # Print column1 with computed padding, followed by column2
  printf "%s %s\n" "$col1string" 'col2string'
done

산출:

Früchte und Gemüse   col2string
Milchprodukte        col2string
12345678901234567890 col2string

주요 정렬 기능을 사용하십시오:

#!/usr/bin/env bash

# Space pad align string to width
# @params
# $1: The alignment width
# $2: The string to align
# @stdout
# aligned string
# @return:
# 1: If a string exceeds alignment width
# 2: If missing arguments
align_left ()
{
  (($#==2)) || return 2
  ((${#2}>$1)) && return 1
  printf '%s%*s' "$2" $(($1-${#2})) ''
}
align_right ()
{
  (($#==2)) || return 2
  ((${#2}>$1)) && return 1
  printf '%*s%s' $(($1-${#2})) '' "$2"
}
align_center ()
{
  (($#==2)) || return 2
  ((${#2}>$1)) && return 1
  l=$((($1-${#2})/2))
  printf '%*s%s%*s' $l '' "$2" $(($1-${#2}-l)) ''
}

strings=(
  'Früchte und Gemüse'
  'Milchprodukte'
  '12345678901234567890'
)

echo 'Left-aligned:'
for str in "${strings[@]}"
do
  printf "| %s |\n" "$(align_left 20 "$str")"
done
echo
echo 'Right-aligned:'
for str in "${strings[@]}"
do
  printf "| %s |\n" "$(align_right 20 "$str")"
done
echo
echo 'Center-aligned:'
for str in "${strings[@]}"
do
  printf "| %s |\n" "$(align_center 20 "$str")"
done

산출:

Left-aligned:
| Früchte und Gemüse   |
| Milchprodukte        |
| 12345678901234567890 |

Right-aligned:
|   Früchte und Gemüse |
|        Milchprodukte |
| 12345678901234567890 |

Center-aligned:
|  Früchte und Gemüse  |
|    Milchprodukte     |
| 12345678901234567890 |

편집하다:

  1. ksh-93 구현 추가 |
  2. 더 많은 POSIXness를 위해 expr이제 다음을 사용하여 테스트했습니다.
  • 재(Busybox 1.x)
  • ksh93 버전 A 2020.0.0
  • zsh 5.8
  1. 제안스티븐 차제라스: expr length "$2"대신에 expr " $2" : '.*' - 1.
  2. 업데이트된 소개이삭의 의견.

    ${#var}bash3.0+부터 문자 계산이 정확합니다.

이것은 ksh 또는 POSIX 구문에서 작동하는 것 같습니다.

#!/usr/bin/env sh

# Space pad align or truncate string to width
# @params
# $1: The alignment width
# $2: The string to align
# @stdout
# The aligned string
# @return:
# 1: If the string was truncated alignment width
# 2: If missing arguments
__align_check ()
{
  if [ $# -ne 2 ]; then return 2; fi
  if [ "$(expr " $2" : '.*' - 1)" -gt "$1" ]; then
    printf '%s' "$(expr substr "$2" 1 $1)"
    return 1
  fi
}
align_left ()
{
  __align_check "$@" || return $?
  printf '%s%*s' "$2" $(($1-$(expr " $2" : '.*' - 1))) ''
}
align_right ()
{
  __align_check "$@" || return $?
  printf '%*s%s' $(($1-$(expr " $2" : '.*' - 1))) '' "$2"
}
align_center ()
{
  __align_check "$@" || return $?
  tpl=$(($1-$(expr " $2" : '.*' - 1)))
  lpl=$((tpl/2))
  rpl=$((tpl-lpl))
  printf '%*s%s%*s' $lpl '' "$2" $rpl ''
}

main ()
{
  hr="+----------------------+----------------------+----------------------\
+------+"
  echo "$hr"
  printf '| %s | %s | %s | %s |\n' \
    "$(align_left 20 'Left-aligned')" \
    "$(align_center 20 'Center-aligned')" \
    "$(align_right 20 'Right-aligned')" \
    "$(align_center 4 'RC')"
  echo "$hr"

  for str
  do
    printf '| %s | %s | %s | %s |\n' \
      "$(align_left 20 "$str")" \
      "$(align_center 20 "$str")" \
      "$(align_right 20 "$str")" \
      "$(align_right 4 "$?")"
  done
  echo "$hr"
}

main \
  'Früchte und Gemüse' \
  'Milchprodukte' \
  '12345678901234567890' \
  'This string is much too long'

산출:

+----------------------+----------------------+----------------------+------+
| Left-aligned         |    Center-aligned    |        Right-aligned |  RC  |
+----------------------+----------------------+----------------------+------+
| Früchte und Gemüse   |  Früchte und Gemüse  |   Früchte und Gemüse |    0 |
| Milchprodukte        |    Milchprodukte     |        Milchprodukte |    0 |
| 12345678901234567890 | 12345678901234567890 | 12345678901234567890 |    0 |
| This string is much  | This string is much  | This string is much  |    1 |
+----------------------+----------------------+----------------------+------+

답변3

인코딩을 latin-1로 변경하면 정렬은 정확하지만 움라우트 렌더링 오류가 발생합니다.

Fr�chte und Gem�se   foo
Milchprodukte        bar
12345678901234567890 baz

실제로는 아닙니다. 하지만 터미널이 latin-1을 지원하지 않으므로 움라우트 대신 쓰레기가 표시됩니다.

iconv를 사용하여 이 문제를 해결할 수 있습니다.

printf foo bar | iconv -f ISO8859-1 -t UTF-8

(또는 iconv에 파이프된 전체 쉘 스크립트를 실행하십시오.)

답변4

다음 답변을 찾게 되어 기쁩니다.

문자 수를 세는 대신 터미널에 원하는 위치로 커서를 이동하도록 지시하여 이 문제를 해결할 수 있습니다 printf.

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

신용 거래:https://unix.stackexchange.com/a/407135

관련 정보