Java Linux: Krasse Sonderzeichen im Pfad, Umbenennen scheitert (Java)

@Piktogramm Danke für den Tipp. Ja, es geht um ein ... Abspielgerät (Radio), das keine seltsamen Zeichen mag. Und aus dem Tiësto könnte man auch Tiesto oder Tieisto machen, aber ich glaube, das geht auch nur in einem kleinen Umfang.
 
jb_alvarado schrieb:
Das ist keine Lösung, sonder ein Workaround. Du limitierst damit den Zeichensatz, so dass nicht alle Zeichen mehr unterstützt und in deinem Fall ersetzt werden. Das sieht du an den Zeichen ��.
Was meinst du damit? In UTF-8 kann ganz Unicode kodiert werden.
jb_alvarado schrieb:
In deinem oberen Beispiel hattest du Fragezeichen im Dateinamen drin. Die werden aber nicht von allen Systemen unterstützt, weshalb hier gerne zu Unicodezeichen gegriffen wird
Wo ist dir denn im echten Leben schonmal was anderes als Unicode begegnet?

Weil es da oft Verwirrung gibt und man Online nicht unbedingt gute Erklärungen findet:
Unicode ist die eigentliche Kodierung von 'Zeichen'. Legt also z.B. fest, welche Bitsequenz dem Buchstaben 'A' oder dem Smilie '🙂' entspricht. UTF-8 ist quasi das Cointainerformat für (üblicherweise) Unicode. Das braucht man, weil eine Unicode 'Einheit' (=Codepoint) nicht immer die selbe Bitlänge hat.
 
Zuletzt bearbeitet:
  • Gefällt mir
Reaktionen: CyborgBeta
Danke für die Korrektur @BeBur, habe da wohl was durcheinander gebracht.

Ich wusste nur, dass es Zeichen gibt, die je nach Betriebssystem nicht im Dateinamen sein dürfen, wie z.B. Doppelpunkt bei MacOS, oder Fragezeichen bei Linux. Um das zu umgehen bedienen sich manche mit Zeichen die gleich aussehen, aber einen anderen Unicode-Code haben.
 
BeBur schrieb:
Wo ist dir denn im echten Leben schonmal was anderes als Unicode begegnet?
Windows! Generell viele Microsoftprodukte vor 2019. Das ist so eine Leidensgeschichte, dass es da gar einen eigenen Wikipediaartikel dazu gibt:
https://en.wikipedia.org/wiki/Unicode_in_Microsoft_Windows
Und die ganzen Altlasten von Ms und der Software Dritter. Ich könnte da gaaaaanz übel schimpfen..

Oder Java, das arbeitet mit modifiziertem UTF-8 intern, das hat zwar akzeptable Gründe aber…

Bei so Zeichenencoding wäre ich sehr vorsichtig anzunehmen, dass wirklich UTF-8 zum Einsatz kommt und mit dem Wort "Unicode" ebenso, Microsoft versteht da ja explizit UTF-16. Bei MacOS ist es dann auch eine Frage, welches Dateisystem zum Einsatz kommt bzw. ob das Dateisystem aus einem älteren Dateisytem heraus transformiert wurde, und ob Dateinamen normalisiert sind bzw. werden müssen.
 
  • Gefällt mir
Reaktionen: andy_m4 und BeBur
Piktogramm schrieb:
Das ist so eine Leidensgeschichte, dass es da gar einen eigenen Wikipediaartikel dazu gibt:
Ah, interessant. Die Artikel zu dem Themenkomplex sind etwas schwer zu lesen, weil dort UTF-8 und Unicode ziemlich viel durcheinander geworfen wird.
https://en.wikipedia.org/wiki/Unicode sagt z.B. das Unicode ein character encoding ist, der Artikel zu character encoding sagt jedoch, dass UTF-8 das häufigste Character encoding ist... Im von dir verlinkten Artikel sind auch nen Haufen solcher Verwirrungen drin.

Aber ich konnte mitnehmen, dass Windows früher mal "Windows Code Pages" anstelle von Unicode verwendet hat und UCS-2 anstelle von UTF-16.

jb_alvarado schrieb:
Um das zu umgehen bedienen sich manche mit Zeichen die gleich aussehen, aber einen anderen Unicode-Code haben.
Das klingt ja gruselig. Wird das Zeichen im Hintergrund dann ausgetauscht, wenn ich es kopiere? Oder habe ich dann ggf. einen kaputten String weil das Betriebssystem Zeichen ausgetauscht hat? [Vermutlich letzteres] Gut zu wissen jedenfalls, das ist ja was ganz fieses womit man nicht unbedingt rechnet.
 
  • Gefällt mir
Reaktionen: andy_m4
Also ... in Java ist alles UTF-16 (jedes Zeichen hat 16-bit, 0 bis 2^16 - 1) ... nur, wenn es mit dem OS kommunizieren soll, also zum Beispiel Dateioperationen, Umbenennen, Pfadmanipulationen usw., dann ist es schon wichtig, welches Encoding vorliegt. Das ist logisch, weil das low level(iger) ist.
 
@jb_alvarado
Python:
def replace_insane(char):
Sehr schön :D. Aber ich hatte deinen Beitrag falsch gelesen. Dachte du schriebst, dass Betriebssysteme Dateinamen umschreiben. Wenn so eine App das macht ist das ja deutlich unkritischer. Der Nutzer wird die Datei schon finden, auch ohne "insane chars".
 
  • Gefällt mir
Reaktionen: jb_alvarado
Habe gerade diesen Kommentar gefunden: https://github.com/yt-dlp/yt-dlp/issues/5014#issuecomment-1256928588 und dieses Issue dazu: https://github.com/yt-dlp/yt-dlp/issues/8962.

Summary of all solutions:

Use --restrict-filenames if you don't want any special characters
Use --compat-option filename-sanitization to use youtube-dl's behavior
Use --replace-in-metadata to control how each character should be replaced

Offenbar sind diese Optionen/Funktionen noch nicht so gut dokumentiert.

Aber prinzipiell macht meine Anwendung bereits so etwas Ähnliches wie der --restrict-filenames-Schalter.
 
CyborgBeta schrieb:
Also ... in Java ist alles UTF-16 (jedes Zeichen hat 16-bit,
... nein :D. Bei UTF-16 hat jede code unit 16 bit. 1-2 UTF-16 Code Units kodieren einen Unicode code point. Ein "Zeichen" kann aus diversen Unicode code points bestehen. Zusätzlich hat nicht jedes "Zeichen" eine unique Zuordnung in Unicode, sondern kann mehrere haben.

Kein bisschen verwirrend. String Klassen verwenden für Operatoren wie "<" meist bytes, vermutlich auch um diesem Wahnsinn zu entgehen. Letztendlich wird der Wahnsinn aber auf den Entwickler geschoben, weil string1 < string2 sagt theoretisch bzw. letztendlich nichts über die Anzahl der "Zeichen" im String aus.
 
Zuletzt bearbeitet:
  • Gefällt mir
Reaktionen: andy_m4
BeBur schrieb:
Doch. Ob du es jetzt "Zeichen" oder "Code Unit" nennst, ist eigentlich egal ...

Zum Beispiel hat der "Blitz" ↯ (Downwards Zigzag Arrow) immer den UTF-16-Dezimal-Wert 8623. Dieser ändert sich auch nicht (es sei denn, die Unicode-Definition würde sich ändern). 8623 ist größer 2^8, aber kleiner 2^16, 8623 liegt also in der UTF-16-Menge. Ob und wie die Bash-Konsole (oder ls) das Zeichen anzeigt, ist dann eine Darstellungsfrage.
 
CyborgBeta schrieb:
Doch. Ob du es jetzt "Zeichen" oder "Code Unit" nennst, ist eigentlich egal ...
Dir persönlich kann natürlich alles egal sein, aber es ist falsch was du schreibst. Und ein einzelnes Beispiel zu nennen ist bei einer "Für alle x gilt y" Aussage generell nicht sinnvoll. Formate mit fester Bitbreite hat man versucht, haben sich aber nicht durchgesetzt (siehe UCS-2, das ist anscheinend auch das, was Windows zum Teil verwendet hatte früher).

Unicode kennt 7 verschiedene Ebenen und nur Ebene 0 wird in UTF-16 mithilfe von 2 Byte dargestellt, der Rest mithilfe von 4 Byte. Spontan rausgesuchte Beispiele sind "😀" oder "𠜎".
Hier hast du einmal "ä" als Kombination der beiden Unicode Codepoints 0061 und 0308 und in UTF-16 kodiert mithilfe von 4 Bytes. Und hier ist das selbe "Zeichen" als einzelner codepoint "ä" und UTF-16 kodiert mithilfe von 2 Bytes. Man kann z.B. auf diese Webseite gehen Link und sich die Zeichen aus meinem Beitrag einzeln rauskopieren, dann sieht man den Unterschied.

In UTF-16 kodiert hat der String "Räte" daher 10 bytes, der String "Räte" aber nur 8 Bytes.

Das sind auch keine unterschiedlichen "Zeichen". Das was man intuitiv mit dem Begriff "Zeichen" meint nennt Unicode Grapheme und sowohl "ä" als auch "ä" stellen das selbe Grapheme dar, sind also nicht ähnlich, sondern müssen von einem Font identisch dargestellt werden, weil sie das selbe Grapheme, "Zeichen" repräsentieren und damit auch das selbe Sprachkonstrukt repräsentieren.
 
Zuletzt bearbeitet:
  • Gefällt mir
Reaktionen: andy_m4, Piktogramm und simpsonsfan
Das ändert doch nichts daran, dass jedes Zeichen in Java intern 16-bit hat. ;)
 
CyborgBeta schrieb:
dass jedes Zeichen in Java intern 16-bit
CyborgBeta schrieb:
in Java ist alles UTF-16
Erstens sind diese zwei Behauptungen nicht äquivalent.
Und zweitens ist die Aussage, jedes Zeichen in Java hätte intern 16 bit halt auch nicht wahr. Stumpfes Wiederholen macht das auch nicht wahr. @BeBur hat einiges dazu erklärt und hat Gegenbeispiele gebracht. Ohne es selbst explizit zu überprüfen, glaube ich ihm, dass bspw. das Zeichen "𠜎" in UTF-16 mit vier Bytes kodiert wird, und anhand von weiteren externen Quellen (die ich hier nicht aufführen werde), glaube ich, dass es in Java der neuesten Version ebenfalls 4 Byte sind.

Beweise mir doch, dass du dieses eine Zeichen in Java intern mit 2 Byte repräsentiert hast, dann halte ich mich aus dem Thread zurück.

Und als Ergänzung/Wiederholung: Eine etwaige konstante Länge eines Datentyps wie bspw. char bedeutet nicht auch eine konstante Länge für die Darstellung von Zeichen. Das ist ja gerade der Knackpunkt bei den verschiedenen UTF Kodierungen.
 
  • Gefällt mir
Reaktionen: BeBur
simpsonsfan schrieb:
Beweise mir doch, dass du dieses eine Zeichen in Java intern mit 2 Byte repräsentiert hast, dann halte ich mich aus dem Thread zurück.
Das sind JDK Interna (Implementierungsdetails der Hersteller), das kann ich schlecht beweisen. Aber es kann auch sein, dass @BeBur recht hat.

Btw. ... Ich möchte nun alle Dots (.) bis auf den letzten aus dem Dateinamen entfernen:

Java:
      String newName = file.getName();
      newName = newName.replaceAll(" \\[[A-Za-z0-9_-]+]\\.", ".");
      newName = newName.replaceAll("\\[", "(");
      newName = newName.replaceAll("]", ")");
      newName = newName.replaceAll("[^0-9A-Za-z (),.-]", "");
      {
        StringBuilder sb = new StringBuilder(newName);
        boolean firstDot = true;
        for (int i = sb.length() - 1; i >= 0; i--) {
          char c = sb.charAt(i);
          if (c == '.') {
            if (firstDot) {
              firstDot = false;
            } else {
              sb.deleteCharAt(i);
            }
          }
        }
        newName = sb.toString();
      }
      newName = newName.replaceAll(" +\\.", ".");
      newName = newName.replaceAll(" {2,}", " ");
      newName = newName.replaceAll(" +,", ",");
      newName = newName.replaceAll("\\( +", "(");
      newName = newName.replaceAll(" +\\)", ")");

Geht dabei ein Weg an der (rückwärtslaufenden) Schleife in Zeile 7 bis 19 vorbei?

Ich hatte versucht, den Offset zum String-Ende des letzten Dots zu ermitteln, dann alle Dots zu entfernen, und dann den letzten Dot am Offset wieder einzufügen... aber das erscheint mir nicht mehr sinnvoll.
 
CyborgBeta schrieb:
Das sind JDK Interna (Implementierungsdetails der Hersteller),
Da kommt dann eher noch ein Layer an Komplexität oben drauf, da sollte man sich daher nicht so sicher sein, vor allemm wenn man schon diverse andere Dinge durcheinander gewürfelt hat.

simpsonsfan schrieb:
Und als Ergänzung/Wiederholung: Eine etwaige konstante Länge eines Datentyps wie bspw. char bedeutet nicht auch eine konstante Länge für die Darstellung von Zeichen.
Ja, das könnte das Verständnisproblem sein.
 
Zuletzt bearbeitet:
BeBur schrieb:
Die werden vermutlich UTF-16 nutzen
Dann verstehe ich den Widerspruch zu meiner zuletzt geposteten Aussage nicht, dass jedes Zeichen intern 16 Bit hat ... außer, dass das letztlich keiner von uns beweisen kann. Aber das ist doch auch alles nebensächlich. Ich kann als Programmierer davon ausgehen, dass ein bestimmtes Zeichen immer den gleichen "Zahlen"-Wert besitzt, und darauf kommt es doch an (imho).
 
Wenn du es jetzt noch nicht verstehst werde ich es sicherlich nicht noch einmal erklären :D.

CyborgBeta schrieb:
außer, dass das letztlich keiner von uns beweisen kann.
So schwer wäre das nicht zu beweisen. Musst du halt schauen, was konkret im memory liegt.

CyborgBeta schrieb:
Programmierer davon ausgehen, dass ein bestimmtes Zeichen immer den gleichen "Zahlen"-Wert besitzt, und darauf kommt es doch an (imho).
Wenn dem so wäre, dann gäbe es diesen Thread gar nicht, ist schließlich alles jederzeit Unicode gewesen bei dir.
 
Zuletzt bearbeitet:
  • Gefällt mir
Reaktionen: CyborgBeta
CyborgBeta schrieb:
Ich möchte nun alle Dots (.) bis auf den letzten aus dem Dateinamen entfernen:
[...]
Geht dabei ein Weg an der (rückwärtslaufenden) Schleife in Zeile 7 bis 19 vorbei?
Kannst public int lastIndexOf(int ch) nutzen, um den letzten (einen) Punkt zu suchen und alle Punkte im Substring bis dahin ersetzen.

Die Doku zu der Methode verdeutlich übrigens das angerissene Thema wieder (nämlich, dass ein CodePoint und ein char nicht deckungsgleich sein müssen) Und natürlich ruft diese Methode eine Schleife auf, die rückwärts durch den String geht.
 
Zuletzt bearbeitet: (BB-Code entfernt)
  • Gefällt mir
Reaktionen: CyborgBeta
CyborgBeta schrieb:
Dann verstehe ich den Widerspruch zu meiner zuletzt geposteten Aussage nicht, dass jedes Zeichen intern 16 Bit hat ... außer, dass das letztlich keiner von uns beweisen kann. Aber das ist doch auch alles nebensächlich. Ich kann als Programmierer davon ausgehen, dass ein bestimmtes Zeichen immer den gleichen "Zahlen"-Wert besitzt, und darauf kommt es doch an (imho).
Ich glaube, Du hast den Unterschied zwischen Zeichen, Code Point und Code Unit nicht verstanden.
Ein Zeichen besteht aus einem oder mehreren Code Points.
Ein Code Point wird mit einer oder mehreren Code Units codiert.
Die Zahl in UTF-8, UTF-16 bzw. UTF-32 gibt die Größe der Code Unit in Bits an. Je nach Code Point und Größe der Code Unit reicht eine Code Unit nicht aus, um einen Code Point zu codieren. UTF-8 und UTF-16 sind daher Encodings mit variabler Breite, wobei die Breite eines Code Points in Bits ein Vielfaches der Größe der Code Unit ist.

Hier ist ein sehr sehenswerter, unterhaltsamer Talk zum Thema:

Timestamps:
Aus mehreren Code Points zusammengesetzte Zeichen (wie das Beispiel von BeBur): 31:25
UTF-8 Encoding: 39:00
Aus mehreren Code Points zusammengesetzte Emojis: 42:02
 
Zuletzt bearbeitet:
  • Gefällt mir
Reaktionen: Piktogramm, CyborgBeta und BeBur
Zurück
Oben