
fs
Fichiers et dossiers du support de stockage : sauvegarder des données, tenir un journal, ranger ses programmes.
La bibliothèque fs lit et écrit les fichiers du support de stockage de l'ordinateur : les mêmes fichiers que le shell affiche avec ls, ouvre avec edit et lance par leur nom. Un programme s'en sert pour garder ce qui doit survivre à un redémarrage ou à un rechargement du monde (un compteur, un record, des réglages), pour tenir un journal, ou pour ranger ses données dans des dossiers.
fs.write("notes", "Vérifier la Presse mécanique de la ligne 2")
print(fs.read("notes"))
print(fs.size("notes") .. " octets")Vérifier la Presse mécanique de la ligne 2 44 octets
(42 caractères, mais 44 octets : chaque é en prend deux.)
Tous les ordinateurs ont la bibliothèque fs. Ce qu'elle peut contenir dépend du support :
| support | capacité | lu par |
|---|---|---|
| Paquet de cartes perforées | 4 Ko (4 096 octets) | tous les ordinateurs qui ont un lecteur |
| Bobine de bande magnétique | 16 Ko | Ordinateur à transistors, Mini-ordinateur, Micro-ordinateur, Ordinateur moderne |
| Disquette | 64 Ko | Mini-ordinateur, Micro-ordinateur, Ordinateur moderne |
| SSD | 1 Mo | Ordinateur moderne |
| mémoire soudée | 16 Ko | le Microcontrôleur, qui n'a pas de lecteur |
Les fichiers vivent sur le support. Insérez un support d'un clic droit, éjectez-le accroupi d'un clic droit (main vide), mettez-le dans un autre ordinateur : les fichiers le suivent. Le contenu est gardé sur le serveur, dans la sauvegarde du monde ; l'objet ne porte qu'un numéro. Le Microcontrôleur n'a pas de lecteur : sa mémoire est soudée, et reste avec lui même quand vous le ramassez.
Sans support, toutes les fonctions de fs sauf fs.cwd arrêtent le programme avec no storage medium. La section Motifs courants montre comment en attendre un.
fs.read(path) | Lit un fichier entier et renvoie son texte. |
fs.write(path, text) | Remplace le contenu d'un fichier par text, ou le crée. |
fs.append(path, text) | Ajoute du texte à la fin d'un fichier, après ce qu'il contient déjà. Le fichier (et ses dossiers) est créé au besoin. |
fs.exists(path) | Dit si un fichier ou un dossier a ce chemin. |
fs.is_dir(path) | Dit si le chemin est un dossier. |
fs.list([folder]) | Les noms des fichiers et dossiers qui se trouvent directement dans un dossier. |
fs.make_dir(path) | Crée un dossier, et les dossiers qui y mènent. |
fs.delete(path) | Supprime un fichier, ou un dossier avec tout ce qu'il contient. |
fs.move(from, to) | Déplace ou renomme un fichier ou un dossier (avec tout son contenu). |
fs.copy(from, to) | Copie un fichier, ou un dossier avec tout ce qu'il contient. |
fs.cwd() | Le dossier courant : celui d'où partent les chemins relatifs. |
fs.size(path) | La taille d'un fichier en octets, ou de tout ce que contient un dossier. |
fs.free() | Les octets encore libres sur le support. |
fs.capacity() | La taille totale du support en octets : 4096 pour un Paquet de cartes perforées, 16384 pour une Bobine de bande magnétique ou la mémoire du Microcontrôleur, 65536 pour une Disquette, 1048576 pour un SSD. |
Chemins
Un chemin désigne un fichier ou un dossier : startup, journaux/porte, /jeux/snake/record.
- Un nom est fait de lettres sans accents, de chiffres, de
_,.et-, de 1 à 32 caractères. Pas d'espaces :fs.write("mon fichier", "...")s'arrête avecbad argument #1 to 'write' (invalid file name 'mon fichier'). Majuscules et minuscules sont différentes :Journaletjournalsont deux fichiers. /sépare les dossiers :journaux/2024/porte.- Un chemin est relatif au dossier courant (le dossier où était le shell quand le programme a été lancé, voir
fs.cwd), ou part de la racine quand il commence par/. .est le dossier lui-même et..le dossier au-dessus. Remonter au-dessus de la racine s'arrête avecbad argument #1 to 'read' (no folder above the root).- Les barres obliques répétées ou finales ne comptent pas :
journaux//porte/estjournaux/porte. - Un chemin complet fait 128 caractères au plus.
Un support contient au plus 1024 fichiers et dossiers, quelle que soit leur taille. Les dossiers ne prennent pas de place ; un fichier prend les octets de son texte (une lettre sans accent compte 1, une lettre accentuée 2).
Quand quelque chose ne va pas, le programme s'arrête avec l'un de ces messages (attrapez-les avec pcall quand cela peut arriver, comme un support plein) :
| message | quand |
|---|---|
no storage medium | le lecteur est vide |
not enough space | le texte ne tient pas sur le support |
too many files (1024 at most) | le support contient déjà 1024 fichiers et dossiers |
'journaux' is a folder | écrire, déplacer ou copier sur un dossier |
'sauve' is a file, not a folder | un chemin traverse un fichier comme si c'était un dossier (sauve/score quand sauve est un fichier) |
no folder 'journaux', 'journaux' is a file | fs.list sur autre chose qu'un dossier |
no file 'rapport' | déplacer ou copier quelque chose qui n'existe pas |
bad argument #1 to 'write' (invalid file name 'é') | un nom avec un caractère interdit |
bad argument #1 to 'write' (path too long (128 characters at most)) | un chemin de plus de 128 caractères |
Les mêmes noms et chemins servent dans le shell, avec ls, cd, mkdir, rm, cp et mv (voir Le shell et l'éditeur).
Lire et écrire
Les fichiers contiennent du texte. Pour sauvegarder un nombre, changez-le en texte avec tostring (ou laissez fs.write le faire), et relisez-le avec tonumber. Pour sauvegarder une liste, mettez une entrée par ligne.
Lit un fichier entier et renvoie son texte.
pathstring- le fichier à lire
- string|nil
- tout le texte du fichier, ou
nilsi ce fichier n'existe pas
Quand le fichier n'existe pas, ou quand le chemin est un dossier, le résultat est nil : pas d'erreur. « Lire, ou prendre une valeur par défaut » tient donc en une ligne :
local texte = fs.read("fichier_absent")
print(texte)
local record = tonumber(fs.read("meilleur_score") or "0") or 0
print("meilleur score : " .. record)nil meilleur score : 0
Le texte revient exactement tel qu'il a été écrit, retours à la ligne compris ; string.split(texte, "\n") le découpe en lignes. Lire coûte une instruction de plus par tranche de 16 caractères.
Voir aussi fs.write() fs.exists()
fs.write(path, text)
Remplace le contenu d'un fichier par text, ou le crée.
pathstring- le fichier à écrire
textstring- le nouveau contenu (un nombre est changé en texte)
L'ancien contenu est perdu. Les dossiers du chemin sont créés au besoin : fs.write("sauve/snake/record", "120") marche sur un support vide.
fs.write("sauve/snake/record", 120)
print(fs.read("sauve/snake/record"))
print(fs.is_dir("sauve/snake"))120 true
Les nombres sont acceptés et changés en texte ; toute autre valeur arrête le programme avec bad argument #2 to 'write' (string expected, got boolean) : utilisez tostring(valeur). L'écriture échoue quand un dossier porte le nom du fichier ('sauve' is a folder), quand le support est plein (not enough space) ou contient déjà 1024 entrées.
Le fichier est écrit tout de suite : un autre programme de l'ordinateur, ou le shell, le voit aussitôt. Le monde l'enregistre sur le disque avec tout le reste. Écrire coûte une instruction de plus par tranche de 16 caractères.
Voir aussi fs.append() fs.read()
fs.append(path, text)
Ajoute du texte à la fin d'un fichier, après ce qu'il contient déjà. Le fichier (et ses dossiers) est créé au besoin.
pathstring- le fichier à compléter
textstring- le texte à ajouter à la fin (un nombre est changé en texte)
C'est l'outil des journaux : chaque évènement ajoute une ligne, les anciennes restent. Rien n'est ajouté entre deux appels : terminez chaque ligne par "\n" vous-même.
fs.append("journaux/gare", "train venant du nord\n")
fs.append("journaux/gare", "train venant de l'est\n")
write(fs.read("journaux/gare"))train venant du nord train venant de l'est
Un journal qui ne fait que grandir finira par remplir le support : voyez le journal tournant dans les Motifs courants. Ajouter coûte une instruction de plus par tranche de 16 caractères ajoutés.
Voir aussi fs.write()
Fichiers et dossiers
Dit si un fichier ou un dossier a ce chemin.
pathstring- un fichier ou un dossier
- boolean
truesi un fichier ou un dossier a ce chemin
fs.write("reglages", "vitesse=64")
print(fs.exists("reglages"))
print(fs.exists("records"))
print(fs.exists("/"))true false true
La racine (/) existe toujours. Pour distinguer un fichier d'un dossier, utilisez fs.is_dir. Pour lire un fichier qui peut manquer, fs.read suffit : il renvoie nil.
Voir aussi fs.is_dir() fs.read()
Dit si le chemin est un dossier.
pathstring- un fichier ou un dossier
- boolean
truepour un dossier,falsepour un fichier ou pour rien
fs.write("jeux/snake/main", "-- le jeu")
print(fs.is_dir("jeux"))
print(fs.is_dir("jeux/snake/main"))
print(fs.is_dir("rien_ici"))true false false
Voir aussi fs.exists() fs.list()
Les noms des fichiers et dossiers qui se trouvent directement dans un dossier.
folderstring facultatif- le dossier à lister ; le dossier courant s'il est omis
- table
- les noms des fichiers et dossiers qu'il contient, dans l'ordre alphabétique
Les noms viennent sans leur dossier (porte, pas journaux/porte), triés : les chiffres d'abord, puis les majuscules, puis les minuscules. Dossiers et fichiers sont mélangés : demandez à fs.is_dir de les distinguer.
fs.write("journaux/porte", "")
fs.write("journaux/Presse", "")
fs.write("journaux/2024/mars", "")
for _, nom in ipairs(fs.list("journaux")) do
if fs.is_dir("journaux/" .. nom) then
print(nom .. "/")
else
print(nom)
end
end2024/ Presse porte
Un dossier vide donne une table vide. Un chemin qui n'est pas un dossier arrête le programme avec no folder 'journaux' (ou 'journaux' is a file). Pour parcourir aussi les dossiers des dossiers, voyez l'arborescence dans les Motifs courants.
Voir aussi fs.is_dir() fs.cwd()
fs.make_dir(path)
Crée un dossier, et les dossiers qui y mènent.
pathstring- le dossier à créer
Rien ne se passe quand le dossier existe déjà. Quand un fichier porte ce nom, le programme s'arrête avec 'journaux' is a file.
fs.make_dir("archives/2024")
print(fs.is_dir("archives"), fs.is_dir("archives/2024"))true true
Vous en aurez rarement besoin avant d'écrire, puisque fs.write crée lui-même les dossiers du chemin. Elle sert à préparer un dossier vide qu'un programme ou un joueur remplira plus tard. Un dossier vide ne prend pas de place, mais compte dans les 1024 entrées du support.
Voir aussi fs.write() fs.list()
Supprime un fichier, ou un dossier avec tout ce qu'il contient.
pathstring- le fichier ou le dossier à supprimer
- boolean
truesi quelque chose a été supprimé,falsesi rien ne portait ce nom
Pas de confirmation, pas de corbeille : ce qui est supprimé est perdu. Un chemin absent n'est pas une erreur, le résultat est simplement false.
fs.write("tmp/rapport", "brouillon")
print(fs.delete("tmp"))
print(fs.delete("tmp"))
print(fs.exists("tmp/rapport"))true false false
La racine elle-même ne peut pas être supprimée (the root cannot be deleted), et un dossier vidé reste en place (supprimez-le aussi si vous voulez qu'il disparaisse).
Voir aussi fs.exists()
fs.move(from, to)
Déplace ou renomme un fichier ou un dossier (avec tout son contenu).
fromstring- le fichier ou le dossier à déplacer
tostring- son nouveau chemin, nom compris
to est le nouveau chemin complet, pas un dossier où ranger : pour mettre rapport dans le dossier archives, écrivez fs.move("rapport", "archives/rapport"). (La commande mv du shell est plus souple et range dans un dossier ; fs.move est stricte.)
fs.write("rapport", "64 lingots de fer")
fs.move("rapport", "archives/rapport")
print(fs.exists("rapport"), fs.read("archives/rapport"))false 64 lingots de fer
- Les dossiers du chemin
tosont créés. - Quand un fichier a déjà le chemin
to, il est remplacé. - Quand
toest un dossier existant, le programme s'arrête avec'archives' is a folder. - Déplacer quelque chose qui n'existe pas s'arrête avec
no file 'rapport'; déplacer un dossier dans lui-même, aveca folder cannot go inside itself.
Voir aussi fs.copy() fs.delete()
fs.copy(from, to)
Copie un fichier, ou un dossier avec tout ce qu'il contient.
fromstring- le fichier ou le dossier à copier
tostring- le chemin de la copie, nom compris
Les règles pour to sont celles de fs.move : un chemin complet, des dossiers créés en route, un fichier remplacé, un dossier refusé. La copie demande de la place sur le support (not enough space), et compte dans les 1024 entrées.
fs.write("startup", 'print("Fonderie prête")')
fs.copy("startup", "secours/startup")
print(fs.read("secours/startup"))print("Fonderie prête")Copier un fichier sur lui-même s'arrête avec a file cannot be copied onto itself. Une copie coûte une instruction de plus par tranche de 16 octets copiés.
Voir aussi fs.move()
Le dossier courant : celui d'où partent les chemins relatifs.
- string
- le dossier courant, depuis la racine :
"/"ou"/jeux/snake"
C'est le dossier où se trouvait le shell (cd jeux) quand le programme a été lancé, écrit depuis la racine avec un / au début. Un programme ne peut pas le changer. Elle marche même sans support.
print(fs.cwd())/
Les chemins relatifs partent du dossier courant, pas du dossier du programme. Lancé depuis la racine avec jeux/snake/main, un programme qui appelle fs.read("record") lit /record, pas /jeux/snake/record. (import est différent : il part du dossier du fichier.) Pour garder des données à côté du programme, construisez le chemin à partir de arg[0], le chemin du fichier en cours :
local morceaux = string.split(arg[0], "/") -- "jeux/snake/main" donne jeux, snake, main
table.remove(morceaux) -- retire le nom du fichier
local ici = "/" .. table.concat(morceaux, "/")
fs.write(ici .. "/record", "120") -- toujours /jeux/snake/recordVoir aussi fs.list()
Place
La taille d'un fichier en octets, ou de tout ce que contient un dossier.
pathstring- un fichier ou un dossier
- number|nil
- la taille en octets, ou
nils'il n'y a rien à ce chemin
fs.write("journaux/porte", "ouverte\nfermée\n")
fs.write("journaux/presse", "bloquée\n")
print(fs.size("journaux/porte"))
print(fs.size("journaux"))
print(fs.size("rien"))16 25 nil
Un octet, c'est une lettre sans accent, un chiffre, une espace ou un retour à la ligne. Une lettre accentuée en prend 2 : fs.size peut donc dépasser #texte.
Voir aussi fs.free()
Les octets encore libres sur le support.
- number
- la place libre sur le support, en octets
Vérifiez-la avant d'écrire quelque chose de gros, ou pour prévenir avant que le support soit plein :
if fs.free() < 1000 then
print("Disquette presque pleine : archivez les vieux journaux")
endVoir aussi fs.capacity() fs.size()
La taille totale du support en octets : 4096 pour un Paquet de cartes perforées, 16384 pour une Bobine de bande magnétique ou la mémoire du Microcontrôleur, 65536 pour une Disquette, 1048576 pour un SSD.
- number
- la taille totale du support, en octets
fs.write("recettes", string.rep("plaque de fer = 1 lingot de fer\n", 40))
local pris = fs.capacity() - fs.free()
print(pris .. " octets pris sur " .. fs.capacity())
print(math.floor(pris * 100 / fs.capacity()) .. " % plein")1280 octets pris sur 65536 1 % plein
(Sur une Disquette.)
Voir aussi fs.free()
Motifs courants
Un compteur qui survit aux redémarrages. Lisez la valeur au départ (0 quand le fichier n'existe pas encore), sauvegardez-la à chaque changement. Ici, les trains qui passent sur un rail détecteur à gauche :
local compte = tonumber(fs.read("trains") or "0") or 0
while true do
os.pull_event("redstone")
if rs.get("left") > 0 then
compte = compte + 1
fs.write("trains", tostring(compte))
print("trains passés : " .. compte)
end
endLa même idée dans une petite fonction, lancée trois fois comme si l'ordinateur avait redémarré deux fois :
local function incrementer(nom)
local n = tonumber(fs.read(nom) or "0") or 0
n = n + 1
fs.write(nom, tostring(n))
return n
end
print(incrementer("demarrages"))
print(incrementer("demarrages"))
print(incrementer("demarrages"))1 2 3
Un record. Ne sauvegardez que si le nouveau score bat l'ancien :
local function sauver_record(score)
local record = tonumber(fs.read("snake/record") or "0") or 0
if score > record then
fs.write("snake/record", tostring(score))
return true
end
return false
end
print(sauver_record(120))
print(sauver_record(80))
print(fs.read("snake/record"))true false 120
Un journal tournant. Un journal ne doit pas remplir le support. Quand le fichier dépasserait une taille limite, il devient porte.old (à la place du précédent), et un nouveau journal commence. Le support ne contient jamais plus de deux fois la limite :
local JOURNAL = "journaux/porte"
local MAX = 60 -- octets ; prenez 2000 ou plus pour un vrai journal
local function noter(texte)
local ligne = texte .. "\n"
if (fs.size(JOURNAL) or 0) + #ligne > MAX then
fs.move(JOURNAL, JOURNAL .. ".old")
end
fs.append(JOURNAL, ligne)
end
noter("ouverte par Steve")
noter("fermée")
noter("ouverte par Alex")
noter("fermée")
noter("ouverte par Steve")
for _, nom in ipairs(fs.list("journaux")) do
print(nom, fs.size("journaux/" .. nom))
endporte 18 porte.old 51
Des réglages dans un fichier. Un fichier de lignes clé=valeur que les joueurs modifient avec edit, sans toucher au programme. Les lignes qui commencent par # sont des commentaires ; les nombres reviennent en nombres :
fs.write("coffre.cfg", "# réglages du Coffre à objet renforcé de fer\nseuil = 256\nface=back\nnom = Coffre de fer\n")
local function lire_reglages(chemin)
local reglages = {}
local texte = fs.read(chemin)
if not texte then
return reglages
end
for _, ligne in ipairs(string.split(texte, "\n")) do
ligne = string.trim(ligne)
local egal = string.find(ligne, "=")
if egal and not string.starts(ligne, "#") then
local cle = string.trim(string.sub(ligne, 1, egal - 1))
local valeur = string.trim(string.sub(ligne, egal + 1))
reglages[cle] = tonumber(valeur) or valeur
end
end
return reglages
end
local reglages = lire_reglages("coffre.cfg")
print(reglages.nom, reglages.seuil + 1, reglages.face)Coffre de fer 257 back
L'arborescence d'un support. Une fonction qui liste un dossier, et s'appelle elle-même pour chaque dossier qu'il contient :
fs.write("startup", "print('bonjour')")
fs.write("jeux/snake/main", "-- le jeu")
fs.write("jeux/snake/record", "120")
fs.write("journaux/porte", "ouverte\nfermée\n")
fs.make_dir("secours")
local function joindre(dossier, nom)
if dossier == "/" then
return "/" .. nom
end
return dossier .. "/" .. nom
end
local function arbre(dossier, retrait)
for _, nom in ipairs(fs.list(dossier)) do
local chemin = joindre(dossier, nom)
if fs.is_dir(chemin) then
print(retrait .. nom .. "/")
arbre(chemin, retrait .. " ")
else
print(retrait .. nom .. " " .. fs.size(chemin) .. " o")
end
end
end
arbre("/", "")jeux/
snake/
main 9 o
record 3 o
journaux/
porte 16 o
secours/
startup 16 oAttendre un support. Un programme lancé continue quand on éjecte son support, mais ses appels à fs échouent. Un programme qui demande au joueur de changer de support (pour copier ses journaux sur une autre disquette, par exemple) attend l'évènement disk, et essaie fs avec pcall, qui attrape l'erreur au lieu d'arrêter le programme :
while not pcall(fs.free).ok do
print("Insérez une disquette")
os.pull_event("disk")
end
print("Support prêt : " .. fs.free() .. " octets libres")Pour organiser un programme plus gros en plusieurs fichiers, voyez Un projet en plusieurs fichiers et import ; pour un enregistreur complet, Enregistreur de données et graphique.