Het werken met het bestandssysteem in Elixir verschilt niet echt van het gebruik van andere populaire programmeertalen. Er zijn drie modules om deze taak op te lossen: IO
, het dossier
, en Pad
. Ze bieden functies voor het openen, creëren, wijzigen, lezen en vernietigen van bestanden, het uitbreiden van paden, enz. Er zijn echter enkele interessante roekeloosheden waar u op moet letten..
In dit artikel zullen we het hebben over het werken met het bestandssysteem in Elixir terwijl we enkele codevoorbeelden bekijken.
De Path-module, zoals de naam al doet vermoeden, wordt gebruikt om met bestandssysteempaden te werken. De functies van deze module retourneren altijd UTF-8-gecodeerde reeksen.
U kunt bijvoorbeeld een pad uitvouwen en vervolgens eenvoudig een absoluut pad genereren:
Path.expand ('./ text.txt') |> Path.absname # => "f: /elixir/text.txt"
Merk overigens op dat in Windows backslashes automatisch worden vervangen door schuine strepen naar voren. Het resulterende pad kan worden doorgegeven aan de functies van de het dossier
module, bijvoorbeeld:
Path.expand ('./ text.txt') |> Path.absname |> File.write ("nieuwe inhoud!", [: Write]) # =>: ok
Hier bouwen we een volledig pad naar het bestand en schrijven we er wat inhoud naar toe.
Al met al werkt het met de Pad
module is eenvoudig en de meeste functies werken niet samen met het bestandssysteem. We zullen enkele gebruikscasussen voor deze module later in het artikel bekijken.
IO, zoals de naam al aangeeft, is de module om met invoer en uitvoer te werken. Het biedt bijvoorbeeld functies als puts
en inspecteren
. IO heeft een concept van apparaten, die ofwel proces-identificaties (PID's) of atomen kunnen zijn. Zo zijn er bijvoorbeeld : stdio
en : stderr
generieke apparaten (die eigenlijk snelkoppelingen zijn). Apparaten in Elixir behouden hun positie, dus beginnen de volgende lees- of schrijfbewerkingen vanaf de plaats waar het apparaat eerder werd gebruikt.
De File-module, op zijn beurt, stelt ons in staat om toegang te krijgen tot bestanden als IO-apparaten. Bestanden worden standaard geopend in de binaire modus; je zou echter kunnen slagen : utf8
als een optie. Ook als een bestandsnaam is opgegeven als een tekenlijst ('Some_name.txt'
), het wordt altijd behandeld als UTF-8.
Laten we nu enkele voorbeelden bekijken van het gebruik van de hierboven genoemde modules.
De meest voorkomende taak is natuurlijk het openen en lezen van bestanden. Om een bestand te openen, kan een functie met de naam open / 2 worden gebruikt. Het accepteert een pad naar het bestand en een optionele lijst met modi. Laten we bijvoorbeeld proberen een bestand te openen om te lezen en te schrijven:
: ok, bestand = Bestand.open ("test.txt", [: read,: write]) bestand |> IO.inspect # => #PID<0.72.0>
U kunt dit bestand vervolgens lezen met behulp van de lees- / 2-functie van de IO
module ook:
: ok, file = File.open ("test.txt", [: read,: write]) IO.read (file,: line) |> IO.inspect # => "test" IO.read (bestand ,: regel) |> IO.inspect # =>: eof
Hier lezen we het bestand regel voor regel. Merk op : eof
atoom dat betekent "einde van bestand".
Je kunt ook passen :allemaal
in plaats van :lijn
om het hele bestand in één keer te lezen:
: ok, file = File.open ("test.txt", [: read,: write]) IO.read (file,: all) |> IO.inspect # => "test" IO.read (bestand ,: all) |> IO.inspect # => ""
In dit geval, : eof
wordt niet geretourneerd, in plaats daarvan krijgen we een lege tekenreeks. Waarom? Nou, omdat apparaten, zoals we eerder zeiden, hun positie behouden, en we beginnen te lezen van de eerder geopende plaats.
Er is ook een open / 3-functie, die een functie accepteert als het derde argument. Nadat de verstreken functie is voltooid, wordt het bestand automatisch gesloten:
File.open "test.txt", [: lezen], fn (bestand) -> IO.read (bestand,: alles) |> IO.inzicht einde
In het vorige gedeelte heb ik laten zien hoe te gebruiken IO.read
om bestanden te lezen, maar het lijkt erop dat de het dossier
module heeft eigenlijk een functie met dezelfde naam:
File.read "test.txt" # => : ok, "test"
Deze functie retourneert een tuple die het resultaat van de bewerking en een binair gegevensobject bevat. In dit voorbeeld bevat het "test", wat de inhoud van het bestand is.
Als de bewerking niet succesvol was, bevat het tuple een :fout
atoom en de reden van de fout:
File.read ("non_existent.txt") # => : error,: enoent
Hier, : ENOENT
betekent dat het bestand niet bestaat. Er zijn nog andere redenen zoals : EACCES
(heeft geen rechten).
De geretourneerde tuple kan worden gebruikt in patroonvergelijking om verschillende uitkomsten te verwerken:
case File.read ("test.txt") do : ok, body -> IO.puts (body) : error, reason -> IO.puts ("Er is een fout opgetreden: # reason") einde
In dit voorbeeld drukken we de inhoud van het bestand uit of geven we een foutreden weer.
Een andere functie om bestanden te lezen is read! / 1. Als je uit de Ruby-wereld komt, heb je waarschijnlijk geraden wat het doet. In principe opent deze functie een bestand en retourneert de inhoud ervan in de vorm van een tekenreeks (niet tuple!):
File.read! ("Test.txt") # => "test"
Als er echter iets misgaat en het bestand niet kan worden gelezen, wordt in plaats daarvan een fout gemeld:
File.read! ("Non_existent.txt") # => (File.Error) kan bestand "non_existent.txt" niet lezen: geen bestand of map
Dus om het zekere voor het onzekere te nemen, kunt u bijvoorbeeld de bestaande? / 1-functie gebruiken om te controleren of een bestand daadwerkelijk bestaat:
defmodule Voorbeeld do def read_file (bestand) do if File.exists? (file) do File.read! (file) |> IO.inzicht end end end Example.read_file ("non_existent.txt")
Geweldig, nu weten we hoe we bestanden moeten lezen. Er is echter nog veel meer dat we kunnen doen, dus laten we doorgaan naar de volgende sectie!
Gebruik de schrijf / 3-functie om iets naar een bestand te schrijven. Het accepteert een pad naar een bestand, de inhoud en een optionele lijst met modi. Als het bestand niet bestaat, wordt het automatisch aangemaakt. Als het echter wel bestaat, wordt de hele inhoud ervan standaard overschreven. Om dit te voorkomen, stelt u de : voeg
modus:
File.write ("new.txt", "update!", [: Append]) |> IO.inspect # =>: ok
In dit geval wordt de inhoud toegevoegd aan het bestand en :OK
wordt als resultaat geretourneerd. Als er iets misgaat, krijg je een tuple : error, reason
, net als met de lezen
functie.
Er is ook een schrijven! functie die vrijwel hetzelfde doet, maar een uitzondering oplevert als de inhoud niet kan worden geschreven. We kunnen bijvoorbeeld een Elixir-programma schrijven dat een Ruby-programma maakt dat op zijn beurt "hallo!" Drukt:
File.write! ("Test.rb", "puts \" hallo! \ "")
De bestanden kunnen inderdaad behoorlijk groot zijn, en bij gebruik van de lezen
functie laadt u de volledige inhoud in het geheugen. Het goede nieuws is dat bestanden vrij eenvoudig kunnen worden gestreamd:
File.open! ("Test.txt") |> IO.stream (: regel) |> Enum.each (& IO.inspect / 1)
In dit voorbeeld openen we een bestand, streamen het regel voor regel en inspecteren we elke regel. Het resultaat ziet er als volgt uit:
"test \ n" "regel 2 \ n" "regel 3 \ n" "een andere regel ... \ n"
Merk op dat de nieuwe lijnsymbolen niet automatisch worden verwijderd, dus misschien wilt u ze verwijderen met de functie String.replace / 4.
Het is een beetje vervelend om een bestand regel voor regel te streamen, zoals in het vorige voorbeeld. In plaats daarvan kunt u vertrouwen op de stream! / 3-functie, die een pad naar het bestand en twee optionele argumenten accepteert: een lijst met modi en een waarde die uitlegt hoe een bestand moet worden gelezen (de standaardwaarde is :lijn
):
File.stream! ("Test.txt") |> Stream.map (& (String.replace (& 1, "\ n", ""))) |> Enum.each (& IO.inspect / 1)
In dit stuk coderen we een bestand terwijl we nieuwe-lijntekens verwijderen en vervolgens elke regel afdrukken. Bestandsstroom!
is langzamer dan File.read
, maar we hoeven niet te wachten totdat alle regels beschikbaar zijn - we kunnen meteen beginnen met het verwerken van de inhoud. Dit is vooral handig wanneer u een bestand vanaf een externe locatie moet lezen.
Laten we een iets ingewikkelder exemplaar bekijken. Ik wil een bestand streamen met mijn Elixir-script, newline-tekens verwijderen en elke regel weergeven met een regelnummer ernaast:
File.stream! ("Test.exs") |> Stream.map (& (String.replace (& 1, "\ n", ""))) |> Stream.with_index |> Enum.each (fn (inhoud , regel_getal) -> IO.puts "# line_num + 1 # inhoud" einde)
Stream.with_index / 2 accepteert een opsomcode en retourneert een verzameling tuples, waarbij elke tuple een waarde en de bijbehorende index bevat. Vervolgens herhalen we deze verzameling en drukken we het regelnummer en de regel zelf af. Als gevolg hiervan ziet u dezelfde code met regelnummers:
1 File.stream! ("Test.exs") |> 2 Stream.map (& (String.replace (& 1, "\ n", ""))) |> 3 Stream.with_index |> 4 Enum.each ( fn (contents, line_num) -> 5 IO.puts "# line_num + 1 # content" 6 end)
Laten we nu ook kort ingaan op het manipuleren van bestanden, in het bijzonder verplaatsen en verwijderen. De functies waarin we geïnteresseerd zijn, zijn hernoemen / 2 en rm / 1. Ik zal je niet vervelen door alle argumenten te beschrijven die ze accepteren, omdat je de documentatie zelf kunt lezen en er is absoluut niets ingewikkelds aan. Laten we in plaats daarvan enkele voorbeelden bekijken.
Allereerst wil ik een functie coderen die alle bestanden uit de huidige map neemt op basis van een voorwaarde en deze vervolgens naar een andere map verplaatst. De functie zou als volgt moeten worden genoemd:
Copycat.transfer_to "texts", fn (file) -> Path.extname (file) == ".txt" end
Dus hier wil ik alles pakken .tekst bestanden en verplaats ze naar de teksten directory. Hoe kunnen we deze taak oplossen? Laten we in de eerste plaats een module en een privéfunctie definiëren om een bestemmingsdirectory voor te bereiden:
defmodule Copycat do def transfer_to (dir, fun) do prepare_dir! dir end defp prepare_dir! (dir) do tenzij File.exists? (dir) do File.mkdir! (dir) end end end
mkdir !, probeert, zoals je al hebt geraden, een map te maken en retourneert een fout als deze bewerking mislukt.
Vervolgens moeten we alle bestanden van de huidige map pakken. Dit kan gedaan worden met de ls! functie, die een lijst met bestandsnamen retourneert:
File.ls!
Ten slotte moeten we de resulterende lijst filteren op basis van de opgegeven functie en de naam van elk bestand wijzigen, wat in feite betekent dat het naar een andere map moet worden verplaatst. Hier is de definitieve versie van het programma:
defmodule Copycat do def transfer_to (dir, fun) do prepare_dir! (dir) File.ls! |> Stream.filter (& (fun. (& 1))) |> Enum.each (& (File.rename (& 1, "# dir / # & 1"))) end defp prepare_dir! (Dir) doen tenzij File.exists? (dir) doen File.mkdir! (dir) einde end-end
Laten we nu het rm
in actie door een vergelijkbare functie te coderen die alle bestanden op basis van een voorwaarde gaat verwijderen. De functie wordt op de volgende manier aangeroepen:
Copycat.remove_if fn (bestand) -> Path.extname (bestand) == ".csv" einde
Hier is de bijbehorende oplossing:
defmodule Copycat do def remove_if (fun) do File.ls! |> Stream.filter (& (fun. (& 1))) |> Enum.each (& File.rm! / 1) end end
rm! / 1 zal een foutmelding geven als het bestand niet kan worden verwijderd. Zoals altijd heeft het een rm / 1-tegenhanger die een tuple met de reden van de fout retourneert als er iets misgaat.
U mag opmerken dat de remove_if
en Overzetten naar
functies lijken erg op elkaar. Dus waarom verwijderen we geen codeduplicatie als een oefening? Ik voeg nog een andere privéfunctie toe die alle bestanden opneemt, ze filtert op basis van de opgegeven voorwaarde en vervolgens een bewerking op hen toepast:
defp filter_and_process_files (voorwaarde, werking) do File.ls! |> Stream.filter (& (voorwaarde. (& 1))) |> Enum.each (& (bewerking. (& 1))) einde
Gebruik nu gewoon deze functie:
defmodule Copycat do def transfer_to (dir, fun) do prepare_dir! (dir) filter_and_process_files (fun, fn (bestand) -> File.rename (bestand, "# dir / # file") end) end def remove_if ( leuk) do filter_and_process_files (fun, fn (file) -> File.rm! (file) end) end # ... end
De gemeenschap van Elixir groeit en er zijn nieuwe, innovatieve bibliotheken die verschillende taken oplossen. De Awesome Elixir GitHub repo somt een aantal populaire oplossingen op, en natuurlijk is er een sectie met bibliotheken voor het werken met bestanden en mappen. Er zijn implementaties voor het uploaden van bestanden, monitoring, het opschonen van bestandsnamen en meer.
Er is bijvoorbeeld een interessante oplossing genaamd Librex voor het converteren van uw documenten met behulp van LibreOffice. Om het in actie te zien, kun je een nieuw project maken:
$ mix nieuwe converter
Voeg vervolgens een nieuwe afhankelijkheid toe aan het bestand mix.exs:
defp-deps do [: librex, "~> 1.0"] eindigen
Voer daarna uit:
$ mix do deps.get, deps.compile
Vervolgens kunt u de bibliotheek opnemen en conversies uitvoeren:
defmodule Converter import Librex def convert_and_remove (dir) converteer "some_path / file.odt", "other_path / 1.pdf" end end
Om dit te laten werken, is het LibreOffice-uitvoerbare bestand (soffice.exe) moet aanwezig zijn in de PAD
. Anders moet u als derde argument een pad naar dit bestand opgeven:
defmodule Converter importeer Librex def convert_and_remove (dir) converteer "some_path / file.odt", "other_path / 1.pdf", "path / soffice" end end
Dat is alles voor vandaag! In dit artikel hebben we de IO
, het dossier
en Pad
modules in actie en bespraken enkele handige functies zoals Open
, lezen
, schrijven
, en anderen.
Er zijn veel andere functies beschikbaar voor gebruik, dus zorg ervoor dat u de documentatie van Elixir doorbladert. Ook is er een inleidende tutorial over de officiële website van de taal die ook van pas kan komen.
Ik hoop dat je dit artikel leuk vond en nu een beetje meer vertrouwen hebt in het werken met het bestandssysteem in Elixir. Bedankt dat je bij me bent gebleven, en tot de volgende keer!