Bash

Bourne shell 兼容的${#string} 中字元串的“長度”是多少?

  • January 10, 2022

來自這個討論:

當我有(zsh 5.8,bash 5.1.0)

var="ASCII"
echo "${var} has the length ${#var}, and is $(printf "%s" "$var"| wc -c) bytes long"

答案很簡單:這些是 5 個字元,佔 5 個字節。

現在,var=Müller產量

Müller has the length 6, and is 7 bytes long

這表明${#}運營商計算程式碼點,而不是字節。這在 POSIX中有點不清楚,他們說它計算“字元”。char如果POSIX C 中的演員通常不是八位字節,這會更清楚。

總之:不錯!不錯,看到LANG==en_US.utf8

現在,

var='🧜🏿‍♀️'
echo "${var} has the length ${#var}, and is $(printf "%s" "$var"| wc -c) bytes long"
🧜🏿‍♀️ has the length 5, and is 17 bytes long

太好了,我們將“深色皮膚的美人魚”分解為 Unicode 程式碼點

  1. 人魚
  2. 深色膚色
  3. 零寬度連接器
  4. 女性
  5. 列印將前一個字元列印為表情符號

好的,所以我們真的在計算 Unicode 程式碼點!

var="e\xcc\x81"
echo "${var} has the length ${#var}, and is $(printf "%s" "$var"| wc -c) bytes long"
é has the length 9, and is 9 bytes long

(當然,我的控制台字型決定´了與下面的空格組合,而不是前面e的。後者是正確的。但讓我們把我的憤怒留到其他時候。)

嗯,這裡有一個輕微的“wat”。

> printf "e\xcc\x81"|wc -c
3
> printf "%s" "${var}" |wc -c
9
> echo -n ${var} |wc -c
3
> echo "${var} has the length ${#var}, and is $(printf "%s" "$var"| wc -c) bytes long"
é has the length 9, and is 9 bytes long
> printf "%s" "${var}" |xxd
00000000: 655c 7863 635c 7838 31                   e\xcc\x81

這就是我放棄的地方。

echo $varecho ${var}並且echo "${var}"所有“正確”發出三個字節。但是,echo ${#var}告訴我它是 9 個字元。

這在哪裡記錄/標準化,這一切的規則是什麼?

在符合 POSIX 的 shell(不是 Bourne shell,該功能來自 Korn shell)中,${#var}likewc -m計算字元¹的數量,並且如果儲存的字節序列無法解碼為目前語言環境中的字元$var,則行為未指定。$var

LC_CTYPE根據目前語言環境(其類別)將字節解碼為字元。在使用 UTF-8 作為字元編碼的語言環境中,0xc3 0xa9 序列將被解碼為一個é字元,而在使用 ISO8859-1 的語言環境中,它將被解碼é.

無論如何,它與 Unicode 程式碼點幾乎沒有關係。當終端或任何其他顯示設備顯示時,它也與計算字素群的數量或字元串的寬度不同。

在:

var="e\xcc\x81"

$var包含 9 個字節和 9 個字元:e, \, x, c, c, \, x,81.

一些printf(在格式參數或%b格式指令的參數中)和echo實現將擴展\xcc到 0xcc 字節,並非所有都這樣做。根據 POSIX,\x在對這些的爭論中會導致未指定的行為。(在格式參數和/雖然\351擴展為 0xe9 字節)。printf``\0351``echo``%b

如果你想在/ /$var中包含0x65, 0xcc,0x81字節(現在越來越多的 shell),你會這樣做:ksh93``zsh``bash

var=$'e\xcc\x81'

或者你總是可以這樣做:

var=$(printf 'e\314\201')

然後在 output 的語言環境中,locale charmap將包含 3 個字節(如 所示)、2 個字元(如or所示)、1 個字素群(如 GNU 所示),通常以寬度 1 顯示(如 GNU 所示)。UTF-8``$var``wc -c``wc -m``${#var}``grep -Po '\X'``wc -L

如果呼叫 shell 時的語言環境以及在解析和執行程式碼時使用 UTF-8 作為字元集,那麼在多個 shell 中,您還可以執行以下操作:

var=$'e\u0301'

用於$var包含 UTF-8 編碼e和 U+0301(組合重音符號)字元。

如果語言環境的字元集不是 UTF-8,那麼 shell 之間的行為會有所不同。此外,將 Unicode 程式碼點擴展為字元時考慮到的是在解析程式碼時還是在執行程式碼時有效的語言環境取決於 shell。如果角色不在區域設置的charmap 中,您還會發現行為的變化。

在 Bourne shell 中,要獲取字元串的字元長度,您必須求助於其他實用程序,例如:

length=`expr "x$var" : '.*' - 1` || :

或者:

length=`printf %s "$var" | wc -m`

雖然如果你發現一個系統足夠老,仍然有一個 Bourne shell,它很wc可能不支持-m或者沒有printf命令。


¹ POSIX 本身沒有指定字節序列和字元序列之間的映射,即使在 POSIX 語言環境中也沒有,只有一些 API 來定義和檢索該映射或將字節序列轉換為字元序列 ( wchar_t)。系統通常為charmap 使用標準字元集,如UTF-8,它是另一種ISO 標準(ISO/IEC 10646 aka Unicode)定義的字元集的轉換格式。某些系統(如 GNU 系統)實際上使用 Unicode 程式碼點作為wchar_t值,而不管區域設置如何。

引用自:https://unix.stackexchange.com/questions/685644