Arbeiten mit dem Dateisystem in Elixir

Das Arbeiten mit dem Dateisystem in Elixir unterscheidet sich nicht wirklich von anderen gängigen Programmiersprachen. Es gibt drei Module, um diese Aufgabe zu lösen: IO, Datei, und Pfad. Sie bieten Funktionen zum Öffnen, Erstellen, Ändern, Lesen und Zerstören von Dateien, zum Erweitern von Pfaden usw. Es gibt jedoch einige interessante Themen, die Sie beachten sollten.

In diesem Artikel werden wir über die Arbeit mit dem Dateisystem in Elixir sprechen und einige Codebeispiele betrachten.

Das Pfadmodul

Das Path-Modul wird, wie der Name schon sagt, für die Arbeit mit Dateisystempfaden verwendet. Die Funktionen dieses Moduls geben immer UTF-8-codierte Zeichenfolgen zurück.

Sie können zum Beispiel einen Pfad erweitern und dann einfach einen absoluten Pfad generieren:

Path.expand ('./ text.txt') |> Path.absname # => "f: /elixir/text.txt"

Beachten Sie übrigens, dass in Windows Backslashes automatisch durch Schrägstriche ersetzt werden. Der resultierende Pfad kann an die Funktionen des übergeben werden Datei Modul, zum Beispiel:

Path.expand ('./ text.txt') |> Path.absname |> File.write ("Neuer Inhalt!", [: Schreiben]) # =>: ok

Hier erstellen wir einen vollständigen Pfad zur Datei und schreiben dann einige Inhalte in die Datei.

Alles in allem mit der Pfad Das Modul ist einfach und die meisten Funktionen interagieren nicht mit dem Dateisystem. Einige Anwendungsfälle für dieses Modul werden später in diesem Artikel beschrieben.

IO- und Dateimodule

IO ist, wie der Name schon sagt, das Modul, das mit Eingabe und Ausgabe arbeitet. Zum Beispiel bietet es Funktionen wie setzt und prüfen. IO hat ein Konzept von Geräte, Dies können entweder Prozesskennungen (PID) oder Atome sein. Zum Beispiel gibt es : stdio und : stderr generische Geräte (die eigentlich Verknüpfungen sind). Geräte in Elixir behalten ihre Position bei, sodass nachfolgende Lese- oder Schreibvorgänge an der Stelle beginnen, an der zuvor auf das Gerät zugegriffen wurde.

Das File-Modul wiederum ermöglicht den Zugriff auf Dateien als IO-Devices. Dateien werden standardmäßig im Binärmodus geöffnet. Sie könnten jedoch passieren : utf8 als eine Option. Auch wenn ein Dateiname als Zeichenliste angegeben ist ('some_name.txt') wird immer als UTF-8 behandelt.

Sehen wir uns nun einige Beispiele für die Verwendung der oben genannten Module an.

Dateien mit IO öffnen und lesen

Die häufigste Aufgabe ist natürlich das Öffnen und Lesen von Dateien. Um eine Datei zu öffnen, kann eine Funktion namens open / 2 verwendet werden. Es akzeptiert einen Pfad zur Datei und eine optionale Liste von Modi. Versuchen wir zum Beispiel, eine Datei zum Lesen und Schreiben zu öffnen:

: ok, file = File.open ("test.txt", [: read,: write]) Datei |> IO.inspect # => #PID<0.72.0>

Sie können diese Datei dann mit der Funktion read / 2 aus dem Ordner lesen IO Modul auch:

: ok, file = File.open ("test.txt", [: read,: write]) IO.read (file,: line) |> IO.inspect # => "test" IO.read (Datei ,: line) |> IO.inspect # =>: eof

Hier lesen wir die Datei Zeile für Zeile. Beachten Sie das : eof Atom bedeutet das "Ende der Datei".

Sie können auch passieren :alles anstatt :Linie um die ganze Datei auf einmal zu lesen:

: ok, file = File.open ("test.txt", [: lesen,: schreiben]) IO.read (file,: all) |> IO.inspect # => "test" IO.read (file ,: all) |> IO.inspect # => "" 

In diesem Fall, : eof wird nicht zurückgegeben - stattdessen erhalten wir eine leere Zeichenfolge. Warum? Nun, da, wie bereits erwähnt, die Geräte ihre Position beibehalten, und wir beginnen an der Stelle, auf die zuvor zugegriffen wurde, zu lesen.

Es gibt auch eine open / 3-Funktion, die als drittes Argument eine Funktion akzeptiert. Nachdem die übergebene Funktion ihre Arbeit beendet hat, wird die Datei automatisch geschlossen:

File.open "test.txt", [: read], fn (file) -> IO.read (file:: all) |> IO.inspect ende

Lesen von Dateien mit Dateimodul

Im vorherigen Abschnitt habe ich gezeigt, wie man es benutzt IO.read um Dateien zu lesen, aber es scheint, dass die Datei Modul hat tatsächlich eine Funktion mit demselben Namen:

File.read "test.txt" # => : ok, "test"

Diese Funktion gibt ein Tupel zurück, das das Ergebnis der Operation und ein binäres Datenobjekt enthält. In diesem Beispiel enthält es "test", dh den Inhalt der Datei.

Wenn die Operation nicht erfolgreich war, enthält das Tupel eine :Error Atom und der Grund des Fehlers:

File.read ("non_existent.txt") # => : error,: enoent

Hier, : Enoent bedeutet, dass die Datei nicht existiert. Es gibt einige andere Gründe wie : eacces (hat keine Berechtigungen).

Das zurückgegebene Tupel kann im Pattern-Matching verwendet werden, um unterschiedliche Ergebnisse zu verarbeiten:

case File.read ("test.txt") do : ok, body -> IO.puts (body) : error, reason -> IO.puts ("Es ist ein Fehler aufgetreten: # reason") Ende

In diesem Beispiel drucken wir entweder den Inhalt der Datei oder zeigen einen Fehlergrund an.

Eine andere Funktion zum Lesen von Dateien heißt Lesen! / 1. Wenn Sie aus der Ruby-Welt gekommen sind, haben Sie wahrscheinlich erraten, was sie tut. Grundsätzlich öffnet diese Funktion eine Datei und gibt ihren Inhalt in Form einer Zeichenfolge zurück (kein Tupel!):

File.read! ("Test.txt") # => "test"

Wenn jedoch etwas schief geht und die Datei nicht gelesen werden kann, wird stattdessen ein Fehler ausgegeben:

File.read! ("Non_existent.txt") # => (File.Error) konnte die Datei "non_existent.txt" nicht lesen: keine solche Datei oder Verzeichnis

Um auf der sicheren Seite zu sein, können Sie zum Beispiel mit der Funktion exists / 1 prüfen, ob tatsächlich eine Datei vorhanden ist: 

defmodule Beispiel: def read_file (Datei) tun, wenn File.exists? (Datei) File.read! (Datei) |> IO.inspect ausführt Ende Ende Ende Example.read_file ("non_existent.txt")

Toll, jetzt wissen wir, wie man Dateien liest. Wir können jedoch noch viel mehr tun, also fahren wir mit dem nächsten Abschnitt fort!

In Dateien schreiben

Um etwas in eine Datei zu schreiben, verwenden Sie die Funktion write / 3. Es akzeptiert einen Pfad zu einer Datei, den Inhalt und eine optionale Liste von Modi. Wenn die Datei nicht vorhanden ist, wird sie automatisch erstellt. Wenn es jedoch vorhanden ist, werden alle Inhalte standardmäßig überschrieben. Um dies zu verhindern, stellen Sie das ein : anhängen Modus:

File.write ("new.txt", "update!", [: Append]) |> IO.inspect # =>: ok

In diesem Fall wird der Inhalt an die Datei und angehängt :OK wird als Ergebnis zurückgegeben. Wenn etwas schief geht, erhalten Sie ein Tupel : Fehler, Grund, genau wie bei der lesen Funktion.

Es gibt auch ein Schreiben! Funktion, die ziemlich dasselbe tut, aber eine Ausnahme auslöst, wenn der Inhalt nicht geschrieben werden kann. Zum Beispiel können wir ein Elixir-Programm schreiben, das ein Ruby-Programm erstellt, das wiederum "Hallo!" Druckt:

File.write! ("Test.rb", "setzt \" Hallo! \ "")

Streaming-Dateien

Die Dateien können in der Tat ziemlich groß sein lesen Funktion Sie laden alle Inhalte in den Speicher. Die gute Nachricht ist, dass Dateien ganz einfach gestreamt werden können:

File.open! ("Test.txt") |> IO.stream (: line) |> Enum.each (& IO.inspect / 1)

In diesem Beispiel öffnen wir eine Datei, streamen sie Zeile für Zeile und prüfen jede Zeile. Das Ergebnis wird so aussehen:

"test \ n" "Zeile 2 \ n" "Zeile 3 \ n" "eine andere Zeile ... \ n"

Beachten Sie, dass die neuen Zeilensymbole nicht automatisch entfernt werden. Daher möchten Sie sie möglicherweise mit der Funktion String.replace / 4 entfernen.

Es ist etwas langweilig, eine Datei Zeile für Zeile zu streamen, wie im vorherigen Beispiel gezeigt. Stattdessen können Sie sich auf die Funktion stream! / 3 verlassen, die einen Pfad zur Datei und zwei optionale Argumente akzeptiert: eine Liste von Modi und einen Wert, der erläutert, wie eine Datei gelesen werden soll (der Standardwert ist :Linie):

File.stream! ("Test.txt") |> Stream.map (& (String.replace (& 1, "\ n", ""))) |> Enum.each (& IO.inspect / 1)

In diesem Code streamen wir eine Datei, entfernen Zeilenumbrüche und drucken jede Zeile aus. Datenfluss! ist langsamer als File.read, Wir müssen jedoch nicht warten, bis alle Zeilen verfügbar sind. Wir können den Inhalt sofort bearbeiten. Dies ist besonders nützlich, wenn Sie eine Datei von einem entfernten Ort aus lesen müssen.

Schauen wir uns ein etwas komplexeres Beispiel an. Ich möchte eine Datei mit meinem Elixir-Skript streamen, Zeilenumbrüche entfernen und jede Zeile mit einer Zeilennummer daneben anzeigen:

File.stream! ("Test.exs") |> Stream.map (& (String.replace (& 1, "\ n", ""))) |> Stream.with_index |> Enum.each (fn (Inhalt , line_num) -> IO.puts "# line_num + 1 # Inhalt" Ende)

Stream.with_index / 2 akzeptiert ein Aufzählungszeichen und gibt eine Sammlung von Tupeln zurück, wobei jedes Tupel einen Wert und seinen Index enthält. Als Nächstes durchlaufen wir diese Sammlung und drucken die Zeilennummer und die Zeile selbst aus. Als Ergebnis sehen Sie denselben Code mit Zeilennummern:

1 File.stream! ("Test.exs") |> 2 Stream.map (& (String.replace (& 1, "\ n", ""))) | 3 Stream.with_index |> 4 Enum.each ( fn (Inhalt, Zeilennummer) -> 5 IO.puts "# Zeilennummer + 1 # Inhalt" 6 Ende)

Dateien verschieben und entfernen

Lassen Sie uns nun auch kurz darauf eingehen, wie Sie Dateien spezifisch bearbeiten, verschieben und entfernen. Die Funktionen, an denen wir interessiert sind, sind umbenennen / 2 und rm / 1. Ich werde Sie nicht langweilen, wenn Sie alle Argumente beschreiben, die sie akzeptieren, da Sie die Dokumentation selbst lesen können, und es gibt absolut nichts Komplexes an ihnen. Schauen wir uns stattdessen einige Beispiele an.

Zunächst möchte ich eine Funktion codieren, die alle Dateien aus dem aktuellen Verzeichnis basierend auf einer Bedingung entnimmt und sie dann in ein anderes Verzeichnis verschiebt. Die Funktion sollte so aufgerufen werden:

Copycat.transfer_to "texts", fn (Datei) -> Pfad.extname (Datei) == ".txt" Ende

Also, hier möchte ich alles packen .TXT Dateien und verschieben Sie sie in die Texte Verzeichnis. Wie können wir diese Aufgabe lösen? Zunächst definieren wir ein Modul und eine private Funktion, um ein Zielverzeichnis vorzubereiten:

defmodule Copycat def transfer_to (dir, fun) do prepar_dir! dir end defp prepar_dir! (dir) do, wenn File.exists? (dir) nicht File.mkdir! (dir) endet

Wie Sie bereits erraten haben, versucht mkdir !, ein Verzeichnis zu erstellen, und gibt einen Fehler zurück, wenn diese Operation fehlschlägt.

Als nächstes müssen wir alle Dateien aus dem aktuellen Verzeichnis holen. Dies kann mit dem ls gemacht werden! Funktion, die eine Liste der Dateinamen zurückgibt:

File.ls!

Als letztes müssen wir die Ergebnisliste anhand der bereitgestellten Funktion filtern und jede Datei umbenennen, was bedeutet, dass sie in ein anderes Verzeichnis verschoben werden muss. Hier ist die endgültige Version des Programms:

defmodule Copycat def transfer_to (dir, fun) do prepar_dir! (dir) File.ls! |> Stream.filter (& (fun. (& 1))) |> Enum.each (& (Dateiname (& 1, "# dir / # & 1"))) end defp prepar_dir! (Dir) do, wenn File.exists? (dir) nicht existiert. File.mkdir! (dir) endet end

Nun wollen wir das sehen rm in Aktion durch Codierung einer ähnlichen Funktion, die alle Dateien basierend auf einer Bedingung entfernt. Die Funktion wird auf folgende Weise aufgerufen:

Copycat.remove_if fn (file) -> Path.extname (file) == ".csv" ende

Hier ist die entsprechende Lösung:

defmodule Copycat do def remove_if (fun) do File.ls! |> Stream.filter (& (fun. (& 1))) |> Enum.each (& File.rm! / 1) Ende

rm! / 1 löst einen Fehler aus, wenn die Datei nicht entfernt werden kann. Wie immer hat es ein Gegenstück rm / 1, das ein Tupel mit dem Fehlergrund zurückgibt, wenn etwas schief geht.

Sie können feststellen, dass die remove_if und transfer_to Funktionen sind sehr ähnlich. Warum entfernen wir nicht die Code-Duplizierung als Übung? Ich füge noch eine private Funktion hinzu, die alle Dateien übernimmt, anhand der angegebenen Bedingung filtert und dann eine Operation auf sie anwendet:

defp filter_and_process_files (Bedingung, Operation) tun File.ls! |> Stream.filter (& (Bedingung. (& 1))) |> Enum.each (& (Vorgang (& 1))) ende

Nutzen Sie jetzt einfach diese Funktion:

defmodule Copycat def transfer_to (dir, fun) do prepar_dir! (dir) filter_and_process_files (fun, fn (file) -> File.rename (file, "# dir / # file") end def remove_if ( fun) do filter_and_process_files (fun, fn (Datei) -> Datei.rm! (Datei) Ende) Ende #… Ende

Lösungen von Drittanbietern

Die Gemeinschaft von Elixir wächst und neue Bibliotheken, die verschiedene Aufgaben lösen, entstehen. Das Awesome Elixir GitHub Repo listet einige beliebte Lösungen auf, und natürlich gibt es einen Abschnitt mit Bibliotheken zum Arbeiten mit Dateien und Verzeichnissen. Es gibt Implementierungen für das Hochladen von Dateien, das Überwachen, die Bereinigung von Dateinamen und mehr.

Zum Beispiel gibt es eine interessante Lösung namens Librex, um Ihre Dokumente mit Hilfe von LibreOffice zu konvertieren. Um es in Aktion zu sehen, können Sie ein neues Projekt erstellen:

$ neuen Konverter mischen

Fügen Sie der mix.exs-Datei dann eine neue Abhängigkeit hinzu:

 defp deps tun [: librex, "~> 1.0"] ende

Danach führen Sie aus:

$ mix do deps.get, deps.compile

Als Nächstes können Sie die Bibliothek einschließen und Konvertierungen durchführen:

Defmodule Converter importieren Librex

Damit dies funktioniert, ist die ausführbare Datei LibreOffice (soffice.exe) muss im vorhanden sein PFAD. Andernfalls müssen Sie als drittes Argument einen Pfad zu dieser Datei angeben:

defmodule Converter importiert Librex def convert_and_remove (dir) konvertiert "some_path / file.odt", "other_path / 1.pdf", "path / soffice" end end

Fazit

Das ist alles für heute! In diesem Artikel haben wir das gesehen IO, Datei und Pfad Module in Aktion und besprachen einige nützliche Funktionen wie öffnen, lesen, schreiben, und andere. 

Es stehen viele weitere Funktionen zur Verfügung. Schauen Sie sich also die Dokumentation von Elixir an. Außerdem gibt es auf der offiziellen Website der Sprache ein einführendes Tutorial, das sich auch als nützlich erweisen kann.

Ich hoffe, Ihnen hat dieser Artikel gefallen und fühlt sich jetzt etwas sicherer, wenn Sie mit dem Dateisystem in Elixir arbeiten. Vielen Dank, dass Sie bei mir bleiben und bis zum nächsten Mal!