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

つまり、発音区別符号付きのテキスト(例:)は、ü発音区別符号ごとに1文字ずつ「縮小」されます。

もちろん、どこかに間違った設定がありますが、どの設定がわかりません。

これは、ファイルエンコーディングが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などの制御文字は言うまでもありません)のように、幅が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システムでは、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. 提案スティーブン・チャジェラスA: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

関連情報