Basics of Go

Reading Files

Maximiliano Firtman

Maximiliano Firtman

Independent Consultant
Basics of Go

Check out a free preview of the full Basics of Go course

The "Reading Files" Lesson is part of the full, Basics of Go course featured in this preview video. Here's what you'd learn in this lesson:

Maximiliano showcases the usage of os.ReadFile to read the contents of a text file, and provides guidance on handling errors that may occur during a failed read attempt.


Transcript from the "Reading Files" Lesson

>> So so far we've been working with format, with pointers, with a little bit about back caches. Let's add one more topic to it and let's talk about file management. How can we, for example, read and write files that we can use for text based files, for JSON, for XML?

And of course you can then extend that to binary files as well. And for that, we are going to create another project. Can we reuse the same one? I mean we can, the problem is that you cannot have more than one main function. So to actually play with a lot of project in one project, you need to start doing weird things like this, so then because it's not a dot go anymore, the compiler will not find it.

The problem is that if the compiler finds another Go file that has a main, so I'm talking about just changing or using any weird extension, so then the compiler will say, no, no, that's not a Go file, I'm not going to look into that one. Okay, so what I mean is that most of the time it's better to just create a new project.

We know it's not a big deal, we can do it faster every time. Okay, so I open a new folder, we are going to work with files. We can call this files. And we know that the next step is to open the terminal. And say go, mode init and a name such as and we can call this files.

And when it's done we create a new file main.go with package main, func main. It always the same, and now you're ready to create a new package. So in this case, we are going to create a package. So let's remember how to create a package. I will wait also a couple of seconds so you can get there.

But remember, a package is just a folder with one name. It doesn't matter the name of the folder, and then a name inside it. If we create the folder here, let's say for example, we call the folder filemanagement, something like that. Is that the name of the package?

Actually no, the name of the package is when we have let's create the file reader and your file writer. The name of the package will be whatever you put here. So you can put x, this is package x. Well now that's the name of the package, and all the files in the folder must have the same name,okay.

But the file system name doesn't need to match the package name. Typically we try to match them, okay, but actually there is no need, right? And also that means that I can change the name of the folder at any time and it's not going to change anything in our code, okay?

I'm not going to call it package x, we can call it file management, file utils, however you wanna call this, it's fine. This is a reader, so we're going to read the file. So we will have a function read file. And we will get the file name as a string, and we are going to return the context of that string so we can call that read text file because it's going to be a text file.

Remember, these are input arguments and these are output arguments. That sometimes is one, but we know it can be more than one, okay, and we will see if maybe we wannahave another argument in a second. So how to work with files. First, to work with files, we need to use a package from the standard library.

So if we go to Go Docs, Standard Library, we can go and find which is the package that we need to use. We can search for file here and see if we can find something that explained to us that is reading file premise if file is to global the general, actually is IO util.

That's how you get there. Also we have FS for file system. So FS is to actually, for example, read the contents of a folder, create a directory. But if you wanna work with the contents of a file, it's an IO util. So you get into IO utils, and here you have, for example, how to read the file, how to write the file, and if you get there, you see the signature.

And before writing the code, how many arguments do we have here? Question for you, how many arguments do we have in read file? One. How many return values do we have? Two. Remember, this is return, and it's returning two values. The first one is a slice of byte, and then we have error.

That's the design pattern for error management. So then to use that, by the way, it says deprecated because there is a newer version cuz we can call OS read file, we can use that one if you want. So instead of doing that we say okay by the way, typically we have examples so we can use the IO util version of the OS, it's just the same, it's not going to make any difference.

We can use the OS one if you want. Read file, it's just the same, you can see the signature is the same signature, they change where they store that file. Well, the name of the file, we receive that as an argument, and then we just start this into two variables, remember, and we can create them with bar or we can use a shortcut.

And say, well, we have the content and a possible error that typically would call that error, err, but it's just a variable name, so any name will work, that's kind of the way to work. And then we check if it's nil or if it's not nil. So if we do have an error, then we need to do something.

If not, we do something else. So in this case if we are here is because we couldn't read the file. This is read operation successful. Okay, makes sense? So the question is let's talk about what happens if we have the value, what if we have the value we need to return and we're going to return what, content, right?

That's the variable with information. But what do we want to do if the operating system says, you know what, I couldn't read the file, or you don't have permission, or the path doesn't exist or something happened. You were reading a USB drive and someone disconnected the drive, something happened so it didn't work.

So from a Go point of view, it's up to us. So Go says, it's your problem, we have three ways to solve the problem. You pick the one you prefer, let's say the lazy version is to return empty string. The premise is that the color will never know why it's empty.

Is it because the file is empty? Actually we don't know, right? So that's kind of a problem. Also, we have an error here, it's saying, hey, you cannot use the variable of type, slice of byte as a string, which maybe is true. It's not the same, right, type.

Well, we can actually create a string from an array of bytes using UTF8 by using this converter. Does it make sense? It's pretty simple, that part was simple. Again, this is the lazy version, I'm not sure I like it. One of the things you can do, well, you can also panic, remember panic?

You like that one, you like to panic. So you can panic like that.
>> [LAUGH]
>> [LAUGH] Yeah, I cannot do anything, so I'm panicking, okay? Or we can continue the same design pattern up in the code stack. So instead of returning the string, we can return the string or an error.

Does that make sense? So in this case, now it's giving me an error here because I need to return an error that can be nil because it was okay here. So we add the comma nil, and if we couldn't read the file, then we return nil as the first argument.

And we can bypass the same error here, like so. By the way, the type have a problem here. That's not the type that was the name of my variable, actually is error, that's a type error. Now it's giving me an error here over the nil, saying the string cannot be nil, because a nil string is actually an empty string.

Here we have two options. I will use the lazy option here, give me that for a second, because we will use the other option on the next sample. So for strings, we don't have nils, there is a way to solve that, and that is to actually return a pointer to a string.

But for now, let's say because we're not going to use that, okay? For now, believe me that that will work. So it's complaining that it's on use. This isn't a package reader, right? What if I wanna use this from main.go, what are the steps? First, will it work?

I haven't seen it, why? Why no, why this?
>> You need to import it.
>> I need to import it, okay, in main.go, right?
>> Yes, or you would've to capitalize it.
>> Exactly, I need to capitalize it because if I look at that function, I know it's private to my package, so I need to use capital R, okay?

That's the first step. Now, after I've done that you can see that it's not complaining anymore that no one is using it. Because it says okay, no one is using it right now in this package, but maybe because we are exporting that I won't complain, because maybe someone else from the outside will use that, okay?

If it's here, there is something wrong, it says, hey, you're not using it locally, and from the outside no one can actually access that, so maybe we have a problem. So that's the warning. So now from here, can I try to use it directly like read? Well, we should actually start trying to get the package is fileutils.

Can I try fileutils and press return? So I start typing the name of my folder, actually the name of my package, not [LAUGH] the folder. And it's fileutils, and you can see at the bottom that's actually what is going to import. It has the prefix of my module's name and then the package.

And then read text file, and then let's say when I read a file so I can create the folder here, maybe create another folder there called data, and I'm going to create a file which shall have some text, text txt. I'm a content of a text file, just a text file,and I will try to read that.

By the way, I will refactor this, just a warning. So I would say data/text.txt. A couple of questions that may arise at this point, will the compiler think that data is now a package or not? Can I create folders without Go files? So because we mentioned that the package is a folder, so can I make this?

Well actually, the compiler will only check for go files. The compiler will ignore everything else. The compiler doesn't know about other files, doesn't care. So if you have folders, empty folders, it will ignore them. If you have folders with content that is not go files, doesn't matter, okay?

Typically, you don't mix source code with data with assets, we have them separated on a real project, okay? But for now, we are just playing, I can try to read that. And by the way, what are we getting from here? Two values, right? Remember, always two values. So we can call this, this is the contents, and error.

And we can try if error is not nil, we can call format, fmt, and say Println c. And if not, we can use a Printf with format and say ERROR PANIC and then we can put the value, and the value can be the error object, so we can see the details of what happened.

So this is the pattern that you will typically see when you're executing functions that might not end properly. Your wizard receive two values, so you will see this pattern a lot. What if you don't care? Like you know what, if it doesn't matter, I will just don't care about the error.

I will just move on with an empty string because you want to. Remember you can also use the underscore here. Of course, in that case, I can actually now read for the error... But if you do that, then if you don't care, you can just ignore the second argument and just continue with whatever C you have, okay?

So if I try this, I already mentioned that I will refactor this. Because if I try this, it says ERROR PANIC, nil, okay? So what's going on here? That we were guessing that, this go file is being executed in the current folder and then the path is relative to the current folder.

So actually doesn't help this work, I need to parse an absolute path. So from my computer where that text file is actually right now, okay? Do you understand the problem? So what we need is to ask the OS about the current folder. So it's not a big deal, and for that we have an OS, the same OS that we were using before operating system.

You can see that all the functions are capitalized, why? Because they're exported. If I see them, [LAUGH] It's because they must be capitalized. And we do have a getWD. The name doesn't say a lot actually by itself, that it's currently the working directory, the current working directory. So this is giving me the current working directory and current working directory if I try to see it, it returns the directory and error, okay?

So actually it's the same thing. So I can get this is the, let's say the root path and the error. If I don't care about the error, I can just say this. Why? Because I feel at this point that having an error to get the current working directory is not really common.

Again, we are situations you're running your Go file from an external hard drive and then it's in memory, and then you remove the hard drive while the software is still in memory. So then when it tries to read, the working directory is not there anymore, it's kind of weird.

Okay, so it's not gonna happen, so I will just say, I don't care about there. And then you can take that root path. And we also need to add a forward slash to separate the root path from the text file. So the other problem that we have is that we need to swap conditions.

Because right now if the error is not nil, is because we do have an error, so I need to swap these two expressions or verify if it's nil is because we don't have an error, okay? And then if I try this now I can see the contents of the file here.

After this, instead of sending that to the console, you can, for example, parse the JSON, we will see how to do that. You can parse, it can be a CSV to parse a comma separated value file or do something with that. But that, for example, how to work and read a file.

Learn Straight from the Experts Who Shape the Modern Web

  • In-depth Courses
  • Industry Leading Experts
  • Learning Paths
  • Live Interactive Workshops
Get Unlimited Access Now