Code: Alles auswählen
$ foo=Blödmann ; echo ${#foo} ${foo:4} ; echo -n $foo | hd
8 mann
00000000 42 6c c3 b6 64 6d 61 6e 6e |Bl..dmann|
00000009
Durch Ausprobieren bin ich auf folgendes Phänomen gekommen.
Code: Alles auswählen
$ foo=$(echo -ne "\xf4\x8f\xbf\xbf") ; echo ${#foo} $foo
1
$ foo=$(echo -ne "\xf4\x8f\xbf\x4f") ; echo ${#foo} $foo
4 ���O
$ foo=$(echo -ne "\xf4\x90\x80\x80") ; echo ${#foo} $foo
1 ����
$ foo=$(echo -ne "\xf7\xbf\xbf\xbf") ; echo ${#foo} $foo
1 ����
$ foo=$(echo -ne "\xfd\xbf\xbf\xbf\xbf\xbf") ; echo ${#foo} $foo
1 ������
$ foo=$(echo -ne "\xfe\x82\x80\x80\x80\x80\x80") ; echo ${#foo} $foo
7 �������
Beispiel1: Das soll ein 4-Byte-Zeichen werden, aber das 3. (von 0 gezählt) ist ein Ascii -> 0. Zeichen kaputt. Das nächste Byte ist ein Folgebyte -> 1. Zeichen kaputt. Das nächste Byte ist ein Folgebyte -> 2. Zeichen kaputt. Das nächste Byte ist Ascii. Macht 4 Zeichen. Wie erwartet.
Beispiel2: Das ist nicht mehr Unicode. Das sollten, wie im Beispiel1, vier kaputte werden. Aber ${#foo} zählt 1.
Beispiel3: ${#foo} zählt alle 4-Byte-Zeichen durch, egal ob es dazu Unicode gibt.
Beispiel4: ${#foo} erkennt auch 6-Byte-Zeichen an.
Beispiel5: Bei 7-Byte-Zeichen macht auch ${#foo} nicht mehr mit.
Wenn ${#foo} so zählt, dann wird das wohl auch bei Substring so sein.
Code: Alles auswählen
$ foo=$(echo -ne "\xfd\xbf\xbf\xbf\xbf\xbf") ; echo ${#foo} $foo ; bar=${foo:2:2} ; echo ${#bar} $bar|hd
1 ������
00000000 30 0a |0.|
00000002
$ foo=$(echo -ne "\xfe\x82\x80\x80\x80\x80\x80") ; echo ${#foo} $foo ; bar=${foo:2:2} ; echo ${#bar} $bar|hd
7 �������
00000000 32 20 80 80 0a |2 ...|
00000005
Ist ${#foo} und ${foo:2:2} falsch, oder ist die Ausgabe auf dem Terminal falsch?
Was ist korrekt? Gibt es U+110000 bis U+3FFFFFFF in UTF-8 oder nicht?
Wenn man schon zur Definition von Zeichen das UTF-8-Schema nimmt, dann soll man doch bitteschön das ganze Spektrum nehmen, und das geht bis \xfe\xbf\xbf\xbf\xbf\xbf\xbf. Damit wäre sowohl ${#foo} und ${foo:2:2} als auch die Ausgabe auf dem Terminal falsch.
Das Ganze spielt eine fundamentale Rolle bei Regex.
Wenn Bash mit kaputten UTF-8-Zeichen nicht konsistent umgeht, dann muss man vorher sicher sein, dass keine kaputten UTF-8-Zeichen drin sind. Das wäre ja fürchterlich.
Für was ist das Zählen in Zeichen statt in Bytes überhaupt gut?
Für mich ist ein chinesisches Zeichen, das 4 Bytes lang ist, oder ein deutscher Umlaut, der zwei Byte lang ist, so etwas wie die Bytefolge keit oder schaft. Und genau so würde ich das in einer Regex verwendet, nämlich in Klammern.
Mit welcher Begründung ist das ph in Stephan etwas anderes als das ß in Fußball? Beides sind zwei Bytes, die zusammengehören.
Hier ist das Pferd von hinten aufgezäumt worden. Die Bytefolgen, die im UTF-8-Schema zusammengehören, bilden ein Zeichen.
Jetzt kann man den Mandarin-Frieden durch das Inuktitut-mu in Regex ohne Klammern ersetzen, aber man hat die Kontrolle über die Datengröße in Bytes verloren.
Jetzt kann man mit nur einem ^H ein ganzes ä entfernen. Dafür hat man keine Möglichkeit mehr, nur das \xa4 vom ä zu entfernen. Was spricht eigentlich dagegen, zum Entfernen von einem zwei-Bytes-Zeichen, ^H^H zu machen?