This is a read-only snapshot of the ComputerCraft forums, taken in April 2020.
ben657's profile picture

Basic file IO - Learn how to save data!

Started by ben657, 06 September 2012 - 07:24 PM
ben657 #1
Posted 06 September 2012 - 09:24 PM
Basic file IO - Learn how to save data!

So, you want to be able to save your data and read it back? Well you've come to the right place!
The wiki is a very useful thing, but putting all the separate functions together in a working program
can be a daunting task, so this tutorial should hopefully teach you to use the file IO (fs) API via
a fully working program.

Where to start?

First, we need a program to make. What i'm going to do is a program which asks a user for their
display name, and their real name, then when they input their display name, the real name is used
to talk back to them. This is redundant on its own, but used in a multi-user system, it could come in
handy. But anyway, it should illustrate the point.

Getting input
So now we start writing! First we need to ask the user for their display name, and real name.


term.clear() -- Clears any text from the screen.
term.setCursorPos(1,1) -- Set the cursor to the top left of the screen.
print("What is your display name?") -- Just prints the string to the screen.
local dispName = read() -- Declares a variable for the display name, and saves what they type into it.
print("Okay, "..dispName.." what is your real name?") -- Asks for the real name, putting the display name in the middle via the two ".." marks.
local realName = read() -- Same as getting the display name, but with a new variable.

Okay, so now we have the display name, and real name stored, time to save them to a file!

Opening and writing to a file

A quick word here on how this works, you open the file in memory in either "write" or "append" mode
when you want to add content to it. The difference is that write will wipe the file first, whereas append
will just add content to the end of the file. Since we will be adding names, we want it in append mode.



fs.makeDir("saves") -- Here we make the folder the saves will go in.
local file = fs.open("saves/"..dispName,"a") -- This opens the file with the users name in the folder "saves" for appending.
file.writeLine(realName) -- Put the real name in the file.
file.close() -- Allows the file to be opened again by something else, and stops any corruption.

Simple! So we just open a file with the players user name, and append his real name to it. This allows us
to just open the file and read the first line… but that's the next section!

NOTE: at this point, if you run the program, then do "cd saves" then "edit yourdisplayname" you should see whatever you
put for your real name, ready to be read!

Reading the data


To read data, we need to open the file again, but this time we open it in read mode ("r") and use
a different function on the file we receive.


file = fs.open("saves/"..dispName,"r") -- Open the file we used before, ready for reading.
local fileData = {} -- Declare a table to use to hold data.
local line = file.readLine() -- This function reads the next line in the file, until the end.
repeat -- Start a loop which will read lines into a table until no lines are left.
table.insert(fileData,line) -- Puts the value of the current line into the table we have.
line = file.readLine() -- read the next line
until line == nil -- readLine() returns nil when the end of the file is reached.
file.close() -- Close up the file ready for use again.
local realNameFromFile = fileData[1] -- If all has gone well, the real name should be first as that is all that was in the file!
print("I have saved and read your name, "..realNameFromFile)

That looks like a lot of code, but the comments should explain it pretty well.
We could have just read the first line and used that as the real name, but the loop can be reused in
other programs with more than one line of data in their files so that you can get all of it into a nice table.

Done!

Here's the whole program on pastie because it's easier to read!

So I hope this helped a few people, if you have any problems, feel free to post in this thread, or even give
me a PM on the forums and i'll do what I can to help. If there are any other tutorials you would like, say that
too, quite enjoyed writing this so i'll do some more if people want them!
ETHANATOR360 #2
Posted 06 September 2012 - 10:40 PM
nice tutorial fixed up some code for you :D/>/>
before:

[color="#000000"]term[/color][color="#666600"].[/color][color="#000000"]clear[/color][color="#666600"]()[/color] [color="#666600"]--[/color][color="#000000"] [[[/color][color="#660066"]Clears[/color][color="#000000"] any text [/color][color="#000088"]from[/color][color="#000000"] the screen[/color][color="#666600"].]][/color]
[color="#000088"]print[/color][color="#666600"]([/color][color="#008800"]"What is your display name?"[/color][color="#666600"])[/color] [color="#666600"]--[[[/color] [color="#660066"]Just[/color][color="#000000"] prints the [/color][color="#000088"]string[/color][color="#000000"] to the screen[/color][color="#666600"].[/color] [color="#666600"]]][/color]
[color="#000000"]dispName [/color][color="#666600"]=[/color][color="#000000"] read[/color][color="#666600"]()[/color] [color="#666600"]--[[[/color] [color="#660066"]Declares[/color][color="#000000"] a variable [/color][color="#000088"]for[/color][color="#000000"] the display name[/color][color="#666600"],[/color] [color="#000088"]and[/color][color="#000000"] saves what they type [/color][color="#000088"]into[/color][color="#000000"] it[/color][color="#666600"].[/color] [color="#666600"]]][/color]
[color="#000088"]print[/color][color="#666600"]([/color][color="#008800"]"Okay, "[/color][color="#666600"]..[/color][color="#000000"]dispName[/color][color="#666600"]..[/color][color="#008800"]" what is your real name?"[/color][color="#666600"])[/color] [color="#666600"]--[[[/color] [color="#660066"]Asks[/color] [color="#000088"]for[/color][color="#000000"] the real name[/color][color="#666600"],[/color][color="#000000"] putting the display name [/color][color="#000088"]in[/color][color="#000000"] the middle via the two [/color][color="#008800"]".."[/color][color="#000000"] marks[/color][color="#666600"].]][/color]
[color="#000000"]realName [/color][color="#666600"]=[/color][color="#000000"] read[/color][color="#666600"]()[/color] [color="#666600"]--[[[/color] [color="#660066"]Same[/color] [color="#000088"]as[/color][color="#000000"] getting the display name[/color][color="#666600"],[/color][color="#000000"] but [/color][color="#000088"]with[/color][color="#000000"] a [/color][color="#000088"]new[/color][color="#000000"] variable[/color][color="#666600"].[/color] [color="#666600"]]][/color]
[color="#666600"]

after:

term.clear() Clears any text from the screen.
term.setCursorPos (1,1)

print("What is your display name?") Just prints the string to the screen.
local dispName = read() Declares a variable for the display name, and saves what they type into it.
print("Okay, "..dispName.." what is your real name?") Asks for the real name, putting the display name in the middle via the two ".." marks.
local realName = read() Same as getting the display name, but with a new variable.


double brackets are use for multi line stuff such as
– [[this is a multi
line comment]]
always put local before variables because if its not local it can be accsesed from other programs and that can cause aalot of incompatibilities
EDIT:not sure why all the html stuff is showing up in the quotes
ben657 #3
Posted 06 September 2012 - 11:11 PM
Thanks for those corrections, I'll put them in once I'm back on my computer, far too fiddly with this phone!

As for the comments, I generally just do them like that on this forum for the look of it… Although I suppose it is a bit redundant… And normal ones might be easier to read, will change that too… And get put the next section in!
ETHANATOR360 #4
Posted 06 September 2012 - 11:25 PM
will the next section be reading the files? because if it is im very exicted
Noodle #5
Posted 07 September 2012 - 01:55 AM
Thankyou for not actually using io.
Turns out that io is a wrapper around fs (as quoted from wiki).
ben657 #6
Posted 07 September 2012 - 08:32 AM
Added the final section, and fixed up a couple of things, mostly the corrections given by ethan, and making the directory before saving to avoid errors.
Luanub #7
Posted 07 September 2012 - 08:50 AM
Thankyou for not actually using io.
Turns out that io is a wrapper around fs (as quoted from wiki).

Where did you read that? I don't see it anywhere on the wiki and it is backwards.

IO is the default Lua input / output API, and FS is an API for CC.

That's why you can find info for IO in the Official 5.1 Lua Reference manual, but FS is not mentioned.

Edit: Okay I found where it states that on the wiki. Still find it rather odd that a default API is actually a wrapper around a CC API. I guess if they severely modified the IO API that could be the case.
Edited on 07 September 2012 - 06:58 AM
ben657 #8
Posted 07 September 2012 - 08:58 AM
Thankyou for not actually using io.
Turns out that io is a wrapper around fs (as quoted from wiki).

Where did you read that? I don't see it anywhere on the wiki and it is backwards.

IO is the default Lua input / output API, and FS is an API for CC.

That's why you can find info for IO in the Official 5.1 Lua Reference manual, but FS is not mentioned.

I thought it was this way round too… although I do remember the wiki used to say something about io being a wrapper…
Tiin57 #9
Posted 07 September 2012 - 05:22 PM
Thankyou for not actually using io.
Turns out that io is a wrapper around fs (as quoted from wiki).
You still sore about Cloudy's rebuttal, then?
ben657 #10
Posted 07 September 2012 - 05:30 PM
Thankyou for not actually using io.
Turns out that io is a wrapper around fs (as quoted from wiki).
You still sore about Cloudy's rebuttal, then?
Have I like, started up some old argument with this thread or..? xD
Noodle #11
Posted 07 September 2012 - 10:22 PM
Thankyou for not actually using io.
Turns out that io is a wrapper around fs (as quoted from wiki).
You still sore about Cloudy's rebuttal, then?
What rebuttal?
I don't remember one 0.0
People on my server told me IO was a wrapper around fs.. I said io is actual Lua ?
Cloudy #12
Posted 07 September 2012 - 11:16 PM
IO is a wrapper round FS - however I don't understand why using IO would be a bad thing?

IO is there to provide compatibility between the sandboxed FS and the Lua IO library. Learning the IO library would be much more useful since it isn't CC specific.
ben657 #13
Posted 07 September 2012 - 11:32 PM
No offence to you guys discussing IO and fs, but could you not do it on this thread? It's more for help regarding the tutorial, or feedback.
Noodle #14
Posted 08 September 2012 - 12:57 AM
IO is a wrapper round FS - however I don't understand why using IO would be a bad thing?

IO is there to provide compatibility between the sandboxed FS and the Lua IO library. Learning the IO library would be much more useful since it isn't CC specific.
Its not a bad thing, its just "incorrect" to teach about when the actual filesystem is FS not IO in CC.
Cloudy #15
Posted 08 September 2012 - 11:36 AM
IO is a wrapper round FS - however I don't understand why using IO would be a bad thing?

IO is there to provide compatibility between the sandboxed FS and the Lua IO library. Learning the IO library would be much more useful since it isn't CC specific.
Its not a bad thing, its just "incorrect" to teach about when the actual filesystem is FS not IO in CC.

But it isn't. IO is universal between things that use Lua - so teaching IO is more correct.

To OP: nice work - but I'm sure you see my point about IO :D/>/>
ben657 #16
Posted 08 September 2012 - 01:04 PM
But it isn't. IO is universal between things that use Lua - so teaching IO is more correct.

To OP: nice work - but I'm sure you see my point about IO :D/>/>
Thanks :D/>/> and yeah, but you both have points. IO is obviously more powerful since it works outside of computercraft, on your actual system, but for the sake of saving files using CC in-game, then fs is perfectly fine, and a little simpler (although I admit I haven't looked much into the IO api).
MrBarry #17
Posted 14 September 2012 - 07:58 PM
Deleted: stupid question. Sorry.
Mr. Fang #18
Posted 18 September 2012 - 09:14 PM
This is actually something awesome! Thanx for the code…Imma make a door lock for this now!
ben657 #19
Posted 18 September 2012 - 10:28 PM
This is actually something awesome! Thanx for the code…Imma make a door lock for this now!

Because its not computer craft unless it's got a door lock :D/>/>

But thanks for the comments, good to know its okay, first tutorial I've written :)/>/>
Sariaz #20
Posted 23 September 2012 - 08:22 AM
and so based of this I'm assuming that if i wanted more data id just do more write lines with the data or would it be like the write function and add to current line and if it dosnt is there anyway to append on to a previously written line besides reading it from file apending to it then rewriting old line?
sjele #21
Posted 23 September 2012 - 08:23 AM
I don't think you can edit/add anew part on a line, if that was what you ment.
ben657 #22
Posted 23 September 2012 - 07:14 PM
and so based of this I'm assuming that if i wanted more data id just do more write lines with the data or would it be like the write function and add to current line and if it dosnt is there anyway to append on to a previously written line besides reading it from file apending to it then rewriting old line?

Yeah, like with printing to the console, writeLine puts a line break at the end, and you can use file.Write to just append onto the current position. Hope that helps :P/>/>
Sariaz #23
Posted 24 September 2012 - 04:16 PM
yup thanks thats how i thought it would work but wanted to make sure before i started to count on that working like that.
Mtdj2 #24
Posted 25 September 2012 - 03:26 PM
You could instead of the loop use;

tFile = file.readAll()
and acsess the first line like:

print(tFile[1])
Hope I helped… Well, I think I did.
ben657 #25
Posted 25 September 2012 - 04:13 PM
You could instead of the loop use;

tFile = file.readAll()
and acsess the first line like:

print(tFile[1])
Hope I helped… Well, I think I did.

Oh that's how that works? honestly I've never used it, just sorta assumed it gives you the whole file as one string :P/>/>
Will add that into the OP ;)/>/>
auser #26
Posted 30 September 2012 - 05:58 PM
How would you suggest extending this to a key=value store? (ini?)
leftcatcher #27
Posted 01 October 2012 - 11:44 AM
Just a few questions:

Does this program create more than one dispName and realName folder? Or does it just overwrite the previous one? Like, for example, I create the "leftcatcher" dispName and Blah for realName, and then I create "ben675" for dispName, and Blahblah for realName. Would there then be two folders in the "saves" section? One for each dispName? Or will the "leftcatcher" folder have been overwritten?

Could I get it to basically activate off a login? For example, on startup the computer will show a menu(Which I can set up easily) which says "Login" or "Create Account". We select the latter here and it starts up the program. It then ask for a Username, (which replaces the dispName here), and then asks for a Password (Replacing realName). It then basically just creates the file around he Username in the same way your program does around the dispName, and puts the Password into the file. Then, basically, in the "Login" section, it would ask for your Username and you would enter it, and it would access the file containing that username and make the Password = requiredPassword so that way you have to enter it to log in. Basically, what your program does but with a bit more added onto the ending. If your code here does in fact have the ability to create numerous folders, then it is entirely possible to have this and actually make use of it instead of it just being a one time thing. :<
Doyle3694 #28
Posted 01 October 2012 - 04:20 PM
It is very possible. I have one made like 50% done, it works with logins and passwords but there are some things left to do like preventing double users wiht same username and stuff.
Spoiler

local userFile
local passwordFile
local users = {}
local passwords = {}
local passwordEntered
local userEntered
local userNum = 0
local lineRead
local registerUser = ""
local registerPassword = ""
local confirmPassword = ""
local regUser
local regPassword
local curY1
local curY2

function RegisterUser()
   readUsersAndPasswords()
   textutils.slowPrint("Please enter your desired username: ", 20)
   registerUser = read()
   RegisterPassword()
end

function RegisterPassword()
   textutils.slowPrint("Please enter your desired password: ", 20)
   registerPassword = read("*")
   textutils.slowPrint("Please confirm your password: ", 20)
   confirmPassword = read("*")
   if registerPassword == confirmPassword then
	  textutils.slowPrint("Passwords match.", 20)
	  fs.makeDir("passwordFolder")
	  regUser = fs.open("passwordFolder/users","a")
	  regPassword = fs.open("passwordFolder/passwords","a")
	  
	  regUser.writeLine(registerUser)
	  regPassword.writeLine(registerPassword)
	  
	  regUser.close()
	  regPassword.close()
	  
	  readUsersAndPasswords()
	  textutils.slowPrint("User registered.", 20)
   else
	  textutils.slowPrint("Passwords missmatch. Please try again: ", 20)
	  regUser.close()
	  regPassword.close()
	  RegisterPassword()
   end
end

function readUsersAndPasswords()
   fs.makeDir("passwordFolder")
   regUser = fs.open("passwordFolder/users","a")
   regPassword = fs.open("passwordFolder/passwords","a")
   regUser.close()
   regPassword.close()
   users = {}
   passwords = {}
   userFile = fs.open("passwordFolder/users","r")
   passwordFile = fs.open("passwordFolder/passwords","r")

   lineRead = userFile.readLine()
   repeat
	  table.insert(users,lineRead)
	  lineRead = userFile.readLine()
   until lineRead == nil

   lineRead = passwordFile.readLine()
   repeat
	  table.insert(passwords,lineRead)
	  lineRead = passwordFile.readLine()
   until lineRead == nil
   passwordFile.close()
   userFile.close()
end

function password()
   textutils.slowPrint("Please enter your password "..users[userNum], 20)
   passwordEntered = read("*")
   if passwordEntered == passwords[userNum] then
	  term.clear()
	  term.setCursorPos(1,1)
	  textutils.slowPrint("Password match.", 20)
	  textutils.slowPrint("Welcome "..users[userNum], 20)
   elseif passwordEntered == "retry" then
	  username()
   else
	  term.clear()
	  term.setCursorPos(1,1)
	  textutils.slowPrint("Password missmatch. Please try again.", 20)
	  textutils.slowPrint("Type 'retry' to enter a new username.", 20)
	  password()
   end
end

function username()
   userNum = 0
   term.clear()
   term.setCursorPos(1,1)
   textutils.slowPrint("Welcome", 20)
   sleep(1)
   term.clear()
   term.setCursorPos(1,1)
   textutils.slowPrint("Please enter your username", 20)
   userEntered = read()
   for i=1, #users do
	  if userEntered == users[i] then
   	  userNum = i
		 break
	  end
   end
   if userNum == 0 then
	  textutils.slowPrint("Username could not be recognized. Please try again", 20)
	  sleep(1)
	  username()
   else
	  term.clear()
	  term.setCursorPos(1,1)
	  textutils.slowPrint("Username recognized.", 20)
	  sleep(1)
	  term.clear()
	  term.setCursorPos(1,1)
	  password()
   end

end
readUsersAndPasswords()
if users[1] == nil then
   textutils.slowPrint("Welcome to your new password program!", 20)
   textutils.slowPrint("Please register your admin user.", 20)
   textutils.slowPrint("This user will be the admin for this system.", 20)
   textutils.slowPrint("The admin is the only one with full access", 20)
   textutils.slowPrint("to this system.", 20)
   RegisterUser()
end
  
  
username()

if userNum ~= 1 then
  
end
  

I formated my code because I know how much of a pain in the arse it is to read unformated code. :)/>/>
leftcatcher #29
Posted 01 October 2012 - 07:36 PM
Well, I figure you could use the fs.exists (I believe that's it) to check if the directory it's trying to save to already exists, and make it not do it if it does. This would happen if someone tried to create an account with the same name as someone else.
Doyle3694 #30
Posted 02 October 2012 - 08:08 AM
not in my program. my program makes 1 file called 'users', 1 called 'passwords' and is going to in the future make 1 called 'rights'. and then it stores these in tables. But PM me if you need more information or help, don't want to spam down his thread.
ben657 #31
Posted 02 October 2012 - 07:46 PM
Just a few questions:

Does this program create more than one dispName and realName folder? Or does it just overwrite the previous one? Like, for example, I create the "leftcatcher" dispName and Blah for realName, and then I create "ben675" for dispName, and Blahblah for realName. Would there then be two folders in the "saves" section? One for each dispName? Or will the "leftcatcher" folder have been overwritten?

Could I get it to basically activate off a login? For example, on startup the computer will show a menu(Which I can set up easily) which says "Login" or "Create Account". We select the latter here and it starts up the program. It then ask for a Username, (which replaces the dispName here), and then asks for a Password (Replacing realName). It then basically just creates the file around he Username in the same way your program does around the dispName, and puts the Password into the file. Then, basically, in the "Login" section, it would ask for your Username and you would enter it, and it would access the file containing that username and make the Password = requiredPassword so that way you have to enter it to log in. Basically, what your program does but with a bit more added onto the ending. If your code here does in fact have the ability to create numerous folders, then it is entirely possible to have this and actually make use of it instead of it just being a one time thing. :<

Sounds like a good way of going about it :(/>/>
And yeah this code will work perfectly for that. Reason being, it makes a folder named saves, and in there are FILES not folders, with data in them, rather than more folders and files. But in short, yes. It'll work fine :D/>/>
ben657 #32
Posted 02 October 2012 - 08:04 PM
How would you suggest extending this to a key=value store? (ini?)

Very possible!
http://www.computercraft.info/forums2/index.php?/topic/3886-config-api-easily-make-user-editable-configs-v10/
Something I did a little while ago, all the codes there for it :(/>/>
anonimo182 #33
Posted 06 October 2012 - 03:52 AM
Thanks for this code! I need it to make my OS!
remiX #34
Posted 07 October 2012 - 05:57 PM
Hmm. Mine works on a single pc. Where do the files save when making a file because mine isnt creating one :X
pbcub1 #35
Posted 08 October 2012 - 01:02 AM
i just want to say thank you for this tutorial because it has helped me in my development of a new Operating System i am in the middle of making
ben657 #36
Posted 10 October 2012 - 12:15 PM
Hmm. Mine works on a single pc. Where do the files save when making a file because mine isnt creating one :X

Really? They should be in the folder you named in the file path during the tutorial. In game, load up the pc, do "ls" (no quotes), see of there's a new folder and do "cd thatfoldersname", then ls again to see if your files there.
Frederikam #37
Posted 29 December 2012 - 12:25 AM
This really is useful, just what I needed. I can see myself doing tons of turtles that uses this code.
AnthonyD98™ #38
Posted 31 January 2013 - 06:59 PM
Nice Tutorial :)/>
TheOddByte #39
Posted 05 February 2013 - 11:07 AM
Well Thanks For The Tutorial, It was Great! :D/>
Now I've finally added the ability to save/load/delete in my Game that's in my sig.
But it's in an unreleased version.
But anyway thx so much!
ThatNewb #40
Posted 12 March 2013 - 09:28 AM
nice tutorial
Brod8362 #41
Posted 20 July 2015 - 05:08 AM
Is it possible to read the data in individual Variables instead of in a table?
Dragon53535 #42
Posted 21 July 2015 - 08:43 PM
Yeah. But to be honest, it would take a lot longer of code than reasonable. If you want to set a variable to a specific line, just read the data into the table, and save your variable as the line you want.

local file = fs.open('file','r')
local tbl = {}
local line = file.readLine()
repeat
  tbl:insert(line)
  line = file.readLine()
until not line
local linethree = tbl[3]
Edited on 21 July 2015 - 06:43 PM
Lemmmy #43
Posted 23 July 2015 - 11:20 PM
Yeah. But to be honest, it would take a lot longer of code than reasonable. If you want to set a variable to a specific line, just read the data into the table, and save your variable as the line you want.

local file = fs.open('file','r')
local tbl = {}
local line = file.readLine()
repeat
  tbl:insert(line)
  line = file.readLine()
until not line
local linethree = tbl[3]

Why not save the serialized table?



local data = {}
function read()
	local file = fs.open("file", "r")
	data = textutils.unserialize(file.readAll())
	file.close() -- don''t forget to close your files
end
data.writingTest1 = "Hello"
data.writingTest2 = "World"
data.writingTest3 = 123
function write()
	local file = fs.open("file", "w")
	file.write(textutils.serialize(data))
	file.close()
end
Note: untested code written on the fly
Edited on 23 July 2015 - 09:22 PM
biggest yikes #44
Posted 26 July 2015 - 10:31 PM
You could instead of the loop use;

tFile = file.readAll()
and acsess the first line like:

print(tFile[1])
Hope I helped… Well, I think I did.
*reads post from 2 years ago that's incorrect*
Well, crap.
pb2007 #45
Posted 13 February 2018 - 08:36 PM
Actually, it wouldn't be
filedata[1]
, it would be
filedata[0]
because computers count from 0, not 1. At least I think. ComputerCraft might be different,

Sorry, don't know how to use inline code lol
Edited on 13 February 2018 - 07:37 PM
SquidDev #46
Posted 13 February 2018 - 08:44 PM
because computers count from 0, not 1
Allow me to introduce you to the wonderful world of Lua (and R, and maybe other languages).
Saldor010 #47
Posted 13 February 2018 - 08:46 PM
3 year necro.. And no, Lua starts at 1 for table indexes. It is generally a safe assumption that the index starts at 0 (for most languages), but for Lua, this isn't the case.

Edit: got ninja'd, darn you, Squid.
Edited on 13 February 2018 - 07:47 PM