Lucrul cu sistemul de fișiere în Elixir

Lucrul cu sistemul de fișiere din Elixir nu diferă cu adevărat de a face acest lucru folosind alte limbi de programare populare. Există trei module pentru rezolvarea acestei sarcini: IO, Fişier, și cale. Ele oferă funcții pentru a deschide, a crea, a modifica, a citi și a distruge fișiere, a extinde căile, etc. Există, totuși, câteva interesante că trebuie să știi.

În acest articol vom vorbi despre lucrul cu sistemul de fișiere în Elixir, în timp ce ne uităm la câteva exemple de cod.

Modulul căii

Modulul Path, așa cum sugerează și numele, este folosit pentru a lucra cu căile sistemului de fișiere. Funcțiile acestui modul întoarcă întotdeauna șiruri codate UTF-8.

De exemplu, puteți extinde o cale și apoi puteți genera ușor o cale absolută:

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

Rețineți, apropo, că în Windows, backslash-urile sunt înlocuite automat cu slash-uri înainte. Calea rezultată poate fi trecută la funcțiile Fişier modul, de exemplu:

Path.expand ('./ text.txt') |> Path.absname |> File.write ("conținut nou!", [: Write]) # =>: ok

Aici construim o cale completă spre fișier și apoi scriem niște conținuturi.

Toate în toate, lucrul cu cale modulul este simplu, iar majoritatea funcțiilor sale nu interacționează cu sistemul de fișiere. Vom vedea câteva cazuri de utilizare pentru acest modul mai târziu în articol.

IO și module de fișiere

IO, așa cum sugerează și numele, este modulul de lucru cu intrare și ieșire. De exemplu, acesta oferă funcții precum puts și inspecta. IO are un concept de dispozitive, care pot fi fie identificatori de proces (PID), fie atomi. De exemplu, există : stdio și : stderr dispozitive generice (care sunt de fapt comenzi rapide). Dispozitivele din Elixir își mențin poziția, astfel încât operațiile ulterioare de citire sau scriere încep de la locul unde dispozitivul a fost accesat anterior.

Modulul File, la rândul său, ne permite să accesăm fișierele ca dispozitive IO. Fișierele sunt deschise în mod binar în mod implicit; cu toate acestea, ați putea trece : utf8 ca opțiune. De asemenea, când un nume de fișier este specificat ca o listă de caractere ('Some_name.txt'), este întotdeauna tratată ca UTF-8.

Acum, să vedem câteva exemple de utilizare a modulelor menționate mai sus.

Deschiderea și citirea fișierelor cu IO

Cea mai obișnuită sarcină este, desigur, deschiderea și citirea fișierelor. Pentru a deschide un fișier, se poate utiliza o funcție numită open / 2. Acceptă o cale către fișier și o listă opțională de moduri. De exemplu, să încercăm să deschidem un fișier pentru citire și scriere:

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

Apoi puteți citi acest fișier utilizând funcția read / 2 din IO modul:

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

Aici citim linia fișierului după linie. Rețineți : EOF atom înseamnă asta "sfârșitul fișierului".

Puteți trece, de asemenea :toate in loc de :linia pentru a citi întregul fișier deodată:

: ok, file = File.open ("test.txt", [: read,: write]) IO.read (fișier,: toate) ,: toate) |> IO.inspect # => "" 

În acest caz, : EOF nu vom fi returnate - în schimb, vom obține un șir gol. De ce? Ei bine, deoarece, așa cum am spus mai devreme, dispozitivele își mențin poziția și începem să citim de pe locul accesat anterior.

Există, de asemenea, o funcție open / 3, care acceptă o funcție ca al treilea argument. După terminarea funcției de trecere, fișierul se închide automat:

File.open "test.txt", [: citire], fn (fișier) -> IO.read (fișier,: toate) |> IO.inspect end

Citirea fișierelor cu modul de fișiere

În secțiunea anterioară am arătat cum să folosesc IO.read în scopul de a citi fișiere, dar se pare că Fişier modul are de fapt o funcție cu același nume:

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

Această funcție returnează o tuplă care conține rezultatul operației și un obiect de date binare. În acest exemplu conține "test", care este conținutul fișierului.

Dacă operația a eșuat, atunci tuplul va conține o :eroare atom și motivul erorii:

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

Aici, : enoent înseamnă că dosarul nu există. Există și alte motive, cum ar fi : eacces (nu are permisiuni).

Tuplul returnat poate fi utilizat în potrivirea modelului pentru a gestiona diferitele rezultate:

în cazul în care fișierul nu a fost găsit, fișierul ".read" ("test.txt") nu : ok, body -> IO.puts (corp) Sfârșit

În acest exemplu, fie imprimați conținutul fișierului, fie afișați un motiv de eroare.

O altă funcție de a citi fișiere se numește citire! / 1. Dacă ați venit din lumea Ruby, probabil ați ghicit ce face. Practic, această funcție deschide un fișier și returnează conținutul acestuia sub forma unui șir (nu tuple!):

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

Cu toate acestea, dacă ceva nu merge bine și fișierul nu poate fi citit, se generează o eroare în schimb:

File.read! ("Non_existent.txt") # => (File.Error) nu a putut citi fișierul "non_existent.txt": nici un astfel de fișier sau director

Deci, pentru a fi în siguranță, puteți folosi, de exemplu, funcția? / 1 pentru a verifica dacă există un fișier: 

defmodule Exemplu def defect_file (fișier) face în cazul în care File.exists? (fișier) face File.read! (fișier) |> IO.inspect capăt sfârșit sfârșitul Exemplu.read_file ("non_existent.txt")

Excelent, acum știm cum să citim fișierele. Cu toate acestea, putem face mult mai mult, așa că trecem la următoarea secțiune!

Scrierea în fișiere

Pentru a scrie ceva într-un fișier, utilizați funcția write / 3. Acceptă o cale spre un fișier, conținutul și o listă opțională de moduri. Dacă fișierul nu există, acesta va fi creat automat. Dacă totuși există, tot conținutul său va fi suprascris în mod implicit. Pentru a împiedica acest lucru, setați :adăuga mod:

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

În acest caz, conținutul va fi atașat fișierului și :O.K va fi returnat ca rezultat. Dacă ceva nu merge bine, veți obține o tuplă : eroare, motiv, ca și cu citit funcţie.

De asemenea, există o scriere! care face aproape același lucru, dar ridică o excepție dacă conținutul nu poate fi scris. De exemplu, putem scrie un program Elixir care creează un program Ruby care, la rândul său, tipărește "salut!":

File.write! ("Test.rb", "pune" salut! "")

Streaming Files

Fișierele pot fi într-adevăr destul de mari, iar când se utilizează citit funcția în care încărcați tot conținutul în memorie. Vestea bună este că fișierele pot fi difuzate destul de ușor:

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

În acest exemplu, deschidem un fișier, îl direcționăm în linie rând și inspectăm fiecare linie. Rezultatul va arăta astfel:

"test \ n" "linia 2 \ n" "linia 3 \ n" "o altă linie ... \ n"

Rețineți că noile simboluri de linie nu sunt eliminate automat, deci este posibil să doriți să scăpați de ele utilizând funcția String.replace / 4.

Este un pic obositor să transmiteți o linie de fișier după linie, așa cum se arată în exemplul anterior. În schimb, puteți să vă bazați pe funcția flux! / 3, care acceptă o cale către fișier și două argumente opționale: o listă de moduri și o valoare care explică modul în care un fișier trebuie citit (valoarea implicită este :linia):

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

În această bucată de cod suntem streaming un fișier în timp ce eliminați caractere noi linie și apoi imprimarea fiecare linie. File.stream! este mai lent decât File.read, dar nu trebuie să așteptăm până când toate liniile sunt disponibile - putem începe imediat procesarea conținutului. Acest lucru este util în special când trebuie să citiți un fișier dintr-o locație la distanță.

Să aruncăm o privire la un exemplu ceva mai complex. Aș dori să trimit un fișier cu scriptul meu Elixir, să eliminați caracterele de linie nouă și să afișați fiecare rând cu un număr de linie de lângă el:

File.stream! ("Test.exs") |> Stream.map (& (String.replace (& 1, "\ n", "")))> Stream.with_index |> Enum.each , line_num) -> IO.puts "# line_num + 1 # conținut" sfârșit)

Stream.with_index / 2 acceptă un enumerable și returnează o colecție de tupluri, în care fiecare tuplă conține o valoare și indexul acesteia. Apoi, vom repeta această colecție și vom imprima numărul liniei și linia însăși. Ca rezultat, veți vedea același cod cu numerele de linie:

1 Stream.map (& (String.replace (& 1, "\ n", "")))> 3 Stream.with_index |> 4 Enum.each ( fn (conținut, linie_num) -> 5 IO.puturi "# line_num + 1 # contents" 6 sfârșit)

Mutarea și eliminarea fișierelor

Acum, să analizăm, de asemenea, pe scurt cum să manipulam fișierele - în special, să le mutați și să le eliminăm. Funcțiile de care suntem interesați sunt rename / 2 și rm / 1. Nu te voi plictisi descriind toate argumentele pe care le acceptă, pe măsură ce poți să citești singur documentația și nu e absolut nimic despre ei. În schimb, să aruncăm o privire la câteva exemple.

Mai întâi, aș dori să cod o funcție care să ia toate fișierele din directorul curent pe baza unei condiții și apoi să le mute într-un alt director. Funcția ar trebui să fie numită astfel:

Copycat.transfer_to "texte", fn (fișier) -> Path.extname (file) == ".txt" sfârșit

Deci, aici vreau să apuc toate .txt fișiere și le mutați la texte director. Cum putem rezolva această sarcină? În primul rând, să definim un modul și o funcție privată pentru a pregăti un director de destinație:

defmodule Copycat face def transfer_to (dir, distracție) nu prepare_dir! dir end defp prepare_dir! (dir) nu cu excepția cazului în File.exists? (dir) do File.mkdir! (dir) end end end

mkdir !, așa cum ați ghicit deja, încearcă să creeze un director și returnează o eroare dacă această operație nu reușește.

Apoi, trebuie să luăm toate fișierele din directorul curent. Acest lucru se poate face folosind ls! care returnează o listă de nume de fișiere:

File.ls!

În cele din urmă, trebuie să filtrați lista rezultată pe baza funcției furnizate și să redenumiți fiecare fișier, ceea ce înseamnă în mod efectiv mutarea în alt director. Iată versiunea finală a programului:

defmodule Copycat face def transfer_to (dir, distracție) nu prepare_dir! (dir) File.ls! |> Stream.filter (& (fun. (& 1)))> Enum.each (& (File.rename (& 1, "# dir / # 1")) end defp prepare_dir! face cu excepția cazului în File.exists? (dir) face File.mkdir! (dir) end end end

Acum să vedem rm în acțiune prin codarea unei funcții similare care va elimina toate fișierele pe baza unei condiții. Funcția va fi apelată în modul următor:

Copycat.remove_if fn (fișier) -> Path.extname (fișier) == ".csv" sfârșit

Iată soluția corespunzătoare:

defmodule Copycat la def remove_if (distracție) nu File.ls! |> Stream.filter (& (fun. (& 1)))>> Enum.each (& File.rm! / 1) end end

rm! / 1 va ridica o eroare dacă fișierul nu poate fi eliminat. Ca întotdeauna, are un rm / 1 omolog care va returna o tuplă cu motivul erorii dacă ceva nu merge bine.

Puteți observa că remove_if și transfer către funcțiile sunt foarte asemănătoare. Deci, de ce nu eliminăm dublarea codului ca exercițiu? Voi adăuga încă o altă funcție privată care să ia toate fișierele, să le filtreze pe baza condiției furnizate și apoi să le aplice o operație:

defp filter_and_process_files (condiție, funcționare) fa File.ls! |> Stream.filter (& (condiție. (& 1)))> Enum.each (& (operație. (& 1))) sfârșit

Acum pur și simplu utilizați această funcție:

(fișier, "# dir / # file") sfârșitul def remove_if (fișier, fișier, fișier, fișier) distracție) nu filtra_and_process_files (distracție, fn (fișier) -> File.rm! (fișier) sfârșit) sfârșitul # ... sfârșit

Soluții terțe

Comunitatea lui Elixir este în creștere, iar noi biblioteci fanteziste care rezolvă diferite sarcini sunt în curs de dezvoltare. Reputația minunată a Elixir GitHub listează câteva soluții populare și, bineînțeles, există o secțiune cu biblioteci pentru a lucra cu fișiere și directoare. Există implementări pentru încărcarea fișierelor, monitorizarea, dezinfectarea numelor de fișiere și multe altele.

De exemplu, există o soluție interesantă numită Librex pentru conversia documentelor dvs. cu ajutorul LibreOffice. Pentru ao vedea în acțiune, puteți crea un nou proiect:

$ amestecați un convertor nou

Apoi adăugați o nouă dependență la fișierul mix.exs:

 defp deps do [: librex, "~> 1.0"] sfârșit

După aceea, executați:

$ mix face deps.get, deps.compile

Apoi, puteți include biblioteca și puteți efectua conversii:

defmodule Converter nu import Librex def convert_and_remove (dir) nu converti "some_path / file.odt", "other_path / 1.pdf" end end

Pentru ca aceasta să funcționeze, executabilul LibreOffice (soffice.exe) trebuie să fie prezent în CALE. În caz contrar, va trebui să oferiți o cale către acest fișier ca al treilea argument:

defmodule Convertizor nu import Librex def convert_and_remove (dir) nu converti "some_path / file.odt", "other_path / 1.pdf", "path / soffice" end end

Concluzie

Asta e tot pentru ziua de azi! În acest articol, am văzut IO, Fişier și cale module în acțiune și au discutat câteva funcții utile, cum ar fi deschis, citit, scrie, si altii. 

Există o mulțime de alte funcții disponibile pentru utilizare, așa că nu uitați să căutați documentația lui Elixir. De asemenea, există un tutorial introductiv pe site-ul oficial al limbii, care poate fi util și în acest sens.

Sper că v-ați bucurat de acest articol și acum vă simțiți puțin mai încrezători în lucrul cu sistemul de fișiere din Elixir. Vă mulțumesc că ați rămas cu mine și până la data viitoare!

Cod