Elixir bietet eine elegante Syntax für die Arbeit mit Binärdaten, wie wir bei der <<>>-Spezialform gesehen haben, die für die Arbeit mit Bitstrings bereitgestellt wird.
Der Binärtyp ist eine Spezialisierung des Bitstring-Typs. Während Bitstrings jede Länge haben können (jede Anzahl von Bits), ist bei Binärdaten die Anzahl der Bits ohne Rest durch 8 teilbar. Das heißt, wenn wir mit Binärdaten arbeiten, denken wir oft in Bytes (8 Bits). Ein Byte kann Ganzzahlen von 0 bis 255 darstellen. Es ist üblich, Byte-Werte in hexadezimaler Schreibweise zu verwenden, 0x00 - 0xFF.
Binärliterale werden mit der Bitstring-Spezialform <<>> definiert. Wenn du ein Binärliteral definierst, kannst du Ganzzahlliterale und String-Literale verwenden. Ganzzahlwerte größer als 255 laufen über, und nur die letzten 8 Bits der Ganzzahl werden verwendet. Standardmäßig wendet Elixir den ::binary-Modifier auf den Wert an. Wir können Binärdaten mit dem Operator <>/2 verketten.
<<255>> == <<0xFF>>
# Overflowing bits are truncated
<<256>> == <<0>>
<<2, 4, 6, 8, 10, 12, 14, 16>> == <<0x02, 0x04, 0x06, 0x08, 0x0A, 0x0C, 0x0E, 0x10>>
Ein Null-Byte ist ein anderer Name für <<0>>.
Musterabgleich lässt sich sogar auf Binärdaten ausweiten, und wir können einen Teil der Binärdaten ganz ähnlich wie bei einer Liste abgleichen.
# Ignore the first 8 bytes, match and bind the remaining to `body`
<<_::binary-size(8), body::binary>>
Wie bei anderen Arten des Musterabgleichs können wir dies in Funktionssignaturen verwenden, um beim Auswählen aus mehreren Funktionsklauseln das passende Muster zu erkennen.
Du arbeitest an einem Projekt, mit dem Nutzer Dateien auf den Server hochladen können, um sie mit anderen zu teilen. Du wurdest damit beauftragt, eine Funktion zu schreiben, die überprüft, ob ein Upload zu seinem Medientyp passt. Bei deiner Recherche entdeckst du, dass die ersten paar Bytes einer Datei meist einzigartig für diesen Dateityp sind, was ihr so etwas wie eine Signatur verleiht.
Die folgende Tabelle dient dir als Referenz:
| Dateityp | Übliche Endung | Medientyp | binäre „Signatur“ |
|---|---|---|---|
| ELF | "exe" |
"application/octet-stream" |
0x7F, 0x45, 0x4C, 0x46 |
| BMP | "bmp" |
"image/bmp" |
0x42, 0x4D |
| PNG | "png" |
"image/png" |
0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A |
| JPG | "jpg" |
"image/jpg" |
0xFF, 0xD8, 0xFF |
| GIF | "gif" |
"image/gif" |
0x47, 0x49, 0x46 |
Implementiere die Funktion type_from_extension/1. Sie soll eine Dateiendung (String) entgegennehmen und den Medientyp (String) zurückgeben, oder nil, wenn die Endung nicht zu den erwarteten passt.
FileSniffer.type_from_extension("exe")
# => "application/octet-stream"
FileSniffer.type_from_extension("txt")
# => nil
Implementiere die Funktion type_from_binary/1. Sie soll eine Datei (Binary) entgegennehmen und den Medientyp (String) zurückgeben, oder nil, wenn die Dateisignatur nicht zu den erwarteten passt.
file = File.read!("application.exe")
FileSniffer.type_from_binary(file)
# => "application/octet-stream"
file = File.read!("example.txt")
FileSniffer.type_from_binary(file)
# => nil
Mach dir keine Gedanken darüber, die Datei als Binary zu lesen. Geh davon aus, dass das bereits für dich erledigt wurde und die Tests sie dir als Argument übergeben.
Implementiere die Funktion verify/2. Sie soll eine Datei (Binary) und eine Endung (String) entgegennehmen und ein :ok- oder :error-Tupel zurückgeben.
file = File.read!("application.exe")
FileSniffer.verify(file, "exe")
# => {:ok, "application/octet-stream"}
FileSniffer.verify(file, "png")
# => {:error, "Warning, file format and file extension do not match."}
Melde dich bei Exercism an, um Elixir mit 58 Konzepte168 Übungen und echtem menschlichen Mentoring zu lernen und zu meistern, alles kostenlos.