次の簡単なスクリプトを実行すると:
#!/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
つまり、発音区別符号付きのテキスト(例:)は、ü
発音区別符号ごとに1文字ずつ「縮小」されます。
もちろん、どこかに間違った設定がありますが、どの設定がわかりません。
これは、ファイルエンコーディングが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などの制御文字は言うまでもありません)のように、幅が2倍の文字もあり、正しく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
- 提案スティーブン・チャジェラスA:
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 -