다음과 같은 간단한 스크립트를 실행하면:
#!/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
%-20s
20개를 세어 보자바이트아니요수치printf
인쇄와는 관련이 없지만텍스트, 서식 지정(토론 참조오스틴 그룹에서(POSIX) 및bash
메일링 리스트).
내장 printf
POSIX 쉘 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열( 입력에서 가장 넓은 줄의 표시 너비를 보고하기 위한 -L
GNU 확장)이지만 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\bX
은 ksh93
모든 제어 문자를 너비로 간주합니다 -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 시스템에서는 awk
GNU의 문자 수를 문자 단위로 사용할 수 있습니다 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 |
편집하다:
- ksh-93 구현 추가 |
- 더 많은 POSIXness를 위해
expr
이제 다음을 사용하여 테스트했습니다.
- 재(Busybox 1.x)
- ksh93 버전 A 2020.0.0
- zsh 5.8
- 제안스티븐 차제라스:
expr length "$2"
대신에expr " $2" : '.*' - 1
. - 업데이트된 소개이삭의 의견.
${#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 -