Travailler avec le système de fichiers dans Elixir

Travailler avec le système de fichiers dans Elixir ne diffère pas vraiment de le faire en utilisant d'autres langages de programmation populaires. Il existe trois modules pour résoudre cette tâche: IO, Fichier, et Chemin. Ils fournissent des fonctions pour ouvrir, créer, modifier, lire et détruire des fichiers, développer des chemins, etc. Il existe cependant des pièges intéressants que vous devriez connaître..

Dans cet article, nous allons parler de l'utilisation du système de fichiers dans Elixir tout en regardant quelques exemples de code..

Le module de chemin

Le module Path, comme son nom l'indique, est utilisé pour travailler avec les chemins du système de fichiers. Les fonctions de ce module renvoient toujours des chaînes codées UTF-8.

Par exemple, vous pouvez développer un chemin puis générer facilement un chemin absolu:

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

Notez, en passant, que dans Windows, les barres obliques inverses sont automatiquement remplacées par des barres obliques. Le chemin résultant peut être transmis aux fonctions du Fichier module, par exemple:

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

Ici, nous construisons un chemin complet vers le fichier, puis nous y écrivons du contenu..

Dans l’ensemble, travailler avec le Chemin Le module est simple et la plupart de ses fonctions n’interagissent pas avec le système de fichiers. Nous verrons quelques cas d'utilisation de ce module plus tard dans l'article.

IO et modules de fichiers

IO, comme son nom l'indique, est le module à utiliser avec les entrées et les sorties. Par exemple, il fournit des fonctions telles que met et inspecter. IO a un concept de dispositifs, qui peut être soit des identificateurs de processus (PID), soit des atomes. Par exemple, il y a : stdio et : stderr dispositifs génériques (qui sont en fait des raccourcis). Les périphériques dans Elixir conservent leur position, de sorte que les opérations de lecture ou d'écriture ultérieures commencent à l'endroit où le périphérique était précédemment utilisé.

Le module Fichier nous permet d’accéder aux fichiers en tant que périphériques IO. Les fichiers sont ouverts en mode binaire par défaut; cependant, vous pourriez passer : utf8 Comme une option. De même, lorsqu'un nom de fichier est spécifié en tant que liste de caractères ('nom_comme.txt'), il est toujours traité comme UTF-8.

Voyons maintenant quelques exemples d'utilisation des modules mentionnés ci-dessus.

Ouverture et lecture de fichiers avec IO

Bien entendu, la tâche la plus courante consiste à ouvrir et à lire des fichiers. Pour ouvrir un fichier, une fonction appelée open / 2 peut être utilisée. Il accepte un chemin d'accès au fichier et une liste optionnelle de modes. Par exemple, essayons d'ouvrir un fichier en lecture et en écriture:

: ok, fichier = Fichier.open ("test.txt", [: lire,: écrire]) fichier |> IO.inspect # => #PID<0.72.0>

Vous pouvez ensuite lire ce fichier en utilisant la fonction read / 2 du répertoire IO module également:

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

Ici, nous lisons le fichier ligne par ligne. Noter la : eof atome qui signifie "fin de fichier".

Vous pouvez aussi passer :tout au lieu de :ligne lire tout le fichier en une fois:

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

Dans ce cas, : eof ne sera pas retourné à la place, nous obtenons une chaîne vide. Pourquoi? Eh bien, parce que, comme nous l’avons dit précédemment, les appareils conservent leur position et nous commençons à lire à partir de.

Il existe également une fonction open / 3, qui accepte une fonction comme troisième argument. Une fois que la fonction passée a terminé son travail, le fichier est fermé automatiquement:

Fichier.open "test.txt", [: lire], fn (fichier) -> IO.read (fichier,: tout) |> IO.inspect end

Lecture de fichiers avec module de fichiers

Dans la section précédente, j'ai montré comment utiliser IO.lire pour pouvoir lire des fichiers, mais il semble que le Fichier Le module a en fait une fonction du même nom:

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

Cette fonction renvoie un tuple contenant le résultat de l'opération et un objet de données binaire. Dans cet exemple, il contient "test", qui correspond au contenu du fichier..

Si l'opération a échoué, le tuple contiendra un :Erreur atome et la raison de l'erreur:

Fichier.read ("non_existent.txt") # => : erreur,: enoent

Ici, : enoent signifie que le fichier n'existe pas. Il y a d'autres raisons comme : eacces (n'a pas de permission).

Le tuple renvoyé peut être utilisé dans la correspondance de modèle pour gérer différents résultats:

case File.read ("test.txt") do : ok, body -> IO.puts (body) : erreur, raison -> IO.puts ("Une erreur s'est produite: # raison") fin

Dans cet exemple, nous imprimons le contenu du fichier ou affichons un motif d'erreur..

Une autre fonction pour lire les fichiers s'appelle read! / 1. Si vous venez du monde Ruby, vous avez probablement deviné ce qu'il fait. Fondamentalement, cette fonction ouvre un fichier et renvoie son contenu sous la forme d'une chaîne (pas de tuple!):

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

Cependant, si quelque chose ne va pas et que le fichier ne peut pas être lu, une erreur est générée:

File.read! ("Non_existent.txt") # => (File.Error) n'a pas pu lire le fichier "non_existent.txt": aucun fichier ni répertoire de ce type

Ainsi, pour être sûr, vous pouvez, par exemple, utiliser la fonction existe? / 1 pour vérifier si un fichier existe réellement: 

defmodule Exemple do def read_file (fichier) do si File.exists? (fichier) File.read! (fichier) |> IO.inspect end end end Example.read_file ("non_existent.txt")

Génial, maintenant nous savons lire les fichiers. Cependant, nous pouvons faire beaucoup plus, alors passons à la section suivante!

Écrire dans des fichiers

Pour écrire quelque chose dans un fichier, utilisez la fonction write / 3. Il accepte un chemin d'accès à un fichier, son contenu et une liste facultative de modes. Si le fichier n'existe pas, il sera créé automatiquement. Si, toutefois, il existe, tout son contenu sera écrasé par défaut. Pour éviter cela, définissez le paramètre :ajouter mode:

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

Dans ce cas, le contenu sera ajouté au fichier et :D'accord sera retourné à la suite. Si quelque chose ne va pas, vous aurez un tuple : erreur, raison, juste comme avec le lis une fonction.

En outre, il y a une écriture! fonction qui fait à peu près la même chose, mais déclenche une exception si le contenu ne peut pas être écrit. Par exemple, nous pouvons écrire un programme Elixir qui crée un programme Ruby qui, à son tour, affiche "hello!":

File.write! ("Test.rb", "met \" bonjour! \ "")

Fichiers en streaming

Les fichiers peuvent en effet être assez volumineux, et lorsqu’on utilise le lis fonction vous chargez tout le contenu dans la mémoire. La bonne nouvelle est que les fichiers peuvent être transférés assez facilement:

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

Dans cet exemple, nous ouvrons un fichier, le diffusons ligne par ligne et inspectons chaque ligne. Le résultat ressemblera à ceci:

"test \ n" "ligne 2 \ n" "ligne 3 \ n" "une autre ligne… \ n"

Notez que les nouveaux symboles de ligne ne sont pas supprimés automatiquement. Vous pouvez donc vous en débarrasser à l'aide de la fonction String.replace / 4.

Il est un peu fastidieux de diffuser un fichier ligne par ligne comme indiqué dans l'exemple précédent. Au lieu de cela, vous pouvez vous fier à la fonction stream! / 3, qui accepte un chemin d'accès au fichier et deux arguments facultatifs: une liste de modes et une valeur expliquant comment un fichier doit être lu (la valeur par défaut est :ligne):

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

Dans ce morceau de code, nous diffusons un fichier en continu tout en supprimant les caractères de nouvelle ligne, puis en imprimant chaque ligne.. File.stream! est plus lent que Fichier.read, mais nous n'avons pas besoin d'attendre que toutes les lignes soient disponibles, nous pouvons commencer à traiter le contenu immédiatement. Ceci est particulièrement utile lorsque vous devez lire un fichier depuis un emplacement distant..

Jetons un coup d'oeil à un exemple légèrement plus complexe. Je souhaite diffuser un fichier avec mon script Elixir, supprimer les caractères de nouvelle ligne et afficher chaque ligne avec un numéro de ligne à côté:

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

Stream.with_index / 2 accepte un enumerable et retourne une collection de tuples, où chaque tuple contient une valeur et son index. Ensuite, nous parcourons simplement cette collection et imprimons le numéro de la ligne et la ligne elle-même. En conséquence, vous verrez le même code avec les numéros de ligne:

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

Déplacement et suppression de fichiers

Voyons maintenant brièvement comment manipuler des fichiers, les déplacer et les supprimer. Les fonctions qui nous intéressent sont renommer / 2 et rm / 1. Je ne vous ennuierai pas en décrivant tous les arguments qu’ils acceptent, car vous pouvez lire la documentation vous-même, et ils n’ont absolument rien de complexe. Au lieu de cela, jetons un oeil à quelques exemples.

Tout d'abord, j'aimerais coder une fonction qui prend tous les fichiers du répertoire actuel en fonction d'une condition, puis les déplace dans un autre répertoire. La fonction devrait s'appeler comme ceci:

Copycat.transfer_to "textes", fn (fichier) -> chemin d'accès.nom (fichier) == ".txt" end

Donc, ici je veux attraper tout .SMS fichiers et déplacez-les vers le des textes annuaire. Comment pouvons-nous résoudre cette tâche? Tout d’abord, définissons un module et une fonction privée pour préparer un répertoire de destination:

defmodule Copycat do def transfer_to (dir, fun) do prepare_dir! dir end defp prepare_dir! (dir) faire sauf si File.exists? (dir) faire File.mkdir! (dir) end end end

Comme vous l'avez déjà deviné, mkdir !, tente de créer un répertoire et renvoie une erreur en cas d'échec de cette opération..

Ensuite, nous devons récupérer tous les fichiers du répertoire actuel. Cela peut être fait en utilisant le ls! fonction, qui retourne une liste de noms de fichiers:

Fichier.ls!

Enfin, nous devons filtrer la liste résultante en fonction de la fonction fournie et renommer chaque fichier, ce qui signifie effectivement le déplacer dans un autre répertoire. Voici la version finale du programme:

defmodule Copycat fait def transfer_to (dir, fun) fait prepare_dir! (dir) File.ls! |> Stream.filter (& (fun. (& 1))) |> Enum.each (& (File.rename (& 1, "# dir / # & 1"))) fin defp prepare_dir! (Dir) faire sauf si File.exists? (dir) faire File.mkdir! (dir) end end end

Voyons maintenant le rm en action en codant une fonction similaire qui va supprimer tous les fichiers en fonction d'une condition. La fonction sera appelée de la manière suivante:

Copycat.remove_if fn (fichier) -> Path.extname (fichier) == ".csv" fin

Voici la solution correspondante:

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

rm! / 1 générera une erreur si le fichier ne peut pas être supprimé. Comme toujours, il a une contrepartie rm / 1 qui retournera un tuple avec la raison de l'erreur en cas de problème..

Vous pouvez noter que le remove_if et Transférer à les fonctions sont très similaires. Alors, pourquoi ne supprimons-nous pas la duplication de code en tant qu'exercice? Je vais ajouter encore une autre fonction privée qui prend tous les fichiers, les filtre en fonction de la condition fournie, puis leur applique une opération:

defp filter_and_process_files (condition, opération) faire File.ls! |> Stream.filter (& (condition. (& 1))) |> Enum.each (& (operation. (& 1))) end

Maintenant, utilisez simplement cette fonction:

defmodule Copycat fait def transfer_to (dir, fun) faire prepare_dir! (dir) filter_and_process_files (fun, fn (fichier) -> File.rename (fichier, "# dir / # fichier") end def end_if ( fun) fait filter_and_process_files (fun, fn (fichier) -> fichier.rm! (fichier) fin) fin #… fin

Solutions tierces

La communauté d’Elixir s’agrandit et de nouvelles bibliothèques tentent de résoudre diverses tâches. Le référentiel Awesome Elixir GitHub répertorie certaines des solutions les plus courantes. Il existe bien entendu une section avec des bibliothèques permettant de travailler avec des fichiers et des répertoires. Il existe des implémentations pour le téléchargement de fichiers, la surveillance, la désinfection de fichiers, etc..

Par exemple, il existe une solution intéressante appelée Librex pour convertir vos documents à l’aide de LibreOffice. Pour le voir en action, vous pouvez créer un nouveau projet:

$ mix new converter

Ajoutez ensuite une nouvelle dépendance au fichier mix.exs:

 defp deps [: librex, "~> 1.0"] end

Après cela, lancez:

$ mix do deps.get, deps.compile

Ensuite, vous pouvez inclure la bibliothèque et effectuer des conversions:

defmodule Converter importe Librex def convert_and_remove (dir) convertit "chemin_un / chemin.fichier", "chemin_autre / 1.pdf" end end

Pour que cela fonctionne, l'exécutable LibreOffice (soffice.exe) doit être présent dans le CHEMIN. Sinon, vous devrez fournir un chemin d'accès à ce fichier en tant que troisième argument:

defmodule Converter importe Librex def convert_and_remove (dir) convertit "chemin_un / chemin.fichier", "chemin_autre / 1.pdf", "chemin / soffice" end end

Conclusion

C'est tout pour aujourd'hui! Dans cet article, nous avons vu le IO, Fichier et Chemin modules en action et discuté de certaines fonctions utiles comme ouvrir, lis, écrire, et d'autres. 

Il y a beaucoup d'autres fonctions disponibles, alors assurez-vous de parcourir la documentation d'Elixir. En outre, il existe un didacticiel d’introduction sur le site Web officiel de la langue qui peut également être utile..

J'espère que vous avez apprécié cet article et que vous êtes maintenant un peu plus confiant quant à l'utilisation du système de fichiers dans Elixir. Merci de rester avec moi et à la prochaine fois!