25

I am using the following code to read a file in Go:

spoon , err := ioutil.ReadFile(os.Args[1])
if err!=nil {
        panic ("File reading error")
}

Now I check for every byte I pick for what character it is. For example:

spoon[i]==' ' //for checking space

Likewise I read the whole file (I know there maybe other ways of reading it) but keeping this way intact, how can I know that I have reached EOF of the file and I should stop reading it further?

Please don't suggest to find the length of spoon and start a loop. I want a sure shot way of finding EOF.

the system
  • 9,244
  • 40
  • 46
Worlock
  • 406
  • 1
  • 4
  • 15
  • easy solution: monocles! – thang Jan 22 '13 at 23:05
  • 1
    @thang what is monocles ? – Worlock Jan 22 '13 at 23:10
  • @Worlock: Once again, the problem as shown above doesn't directly involve any EOF. It could be _only_ inferred from the number of bytes in the returned `spoon` slice, where an `io.EOF` would have occurred if the file was read sequentially. Even `io.ReadFile` probably may have never seen any EOF in this setup. It's cheaper to `stat` the file, size the buffer (the returned-to-be slice) and ask the OS to fill it completely. – zzzz Jan 22 '13 at 23:15

5 Answers5

31

Use io.EOF to test for end-of-file. For example, to count spaces in a file:

package main

import (
    "fmt"
    "io"
    "os"
)

func main() {
    if len(os.Args) <= 1 {
        fmt.Println("Missing file name argument")
        return
    }
    f, err := os.Open(os.Args[1])
    if err != nil {
        fmt.Println(err)
        return
    }
    defer f.Close()
    data := make([]byte, 100)
    spaces := 0
    for {
        data = data[:cap(data)]
        n, err := f.Read(data)
        if err != nil {
            if err == io.EOF {
                break
            }
            fmt.Println(err)
            return
        }
        data = data[:n]
        for _, b := range data {
            if b == ' ' {
                spaces++
            }
        }
    }
    fmt.Println(spaces)
}
peterSO
  • 158,998
  • 31
  • 281
  • 276
  • 2
    Read can return data alongside an EOF: "Callers should always process the n > 0 bytes returned before considering the error err. Doing so correctly handles I/O errors that happen after reading some bytes and also both of the allowed EOF behaviors." – Thomas Hurst Dec 13 '20 at 16:49
  • This code skips counting spaces in the last chunk of `data` right? if there are 99 spaces left in a file, the reader will return an EOF error and break the for loop without counting those last 99. – andrw Jun 13 '22 at 18:05
10

This is what you need to look for to find out about End Of File(EOF)

if err != nil {
        if errors.Is(err, io.EOF) { // prefered way by GoLang doc
            fmt.Println("Reading file finished...")
        }
        break
    }
MoHo
  • 2,402
  • 1
  • 21
  • 22
8

ioutil.ReadFile() reads the entire contents of the file into a byte slice. You don't need to be concerned with EOF. EOF is a construct that is needed when you read a file one chunk at a time. You need to know which chunk has reached the end of the file when you're reading one chunk at a time.

The length of the byte slice returned by ioutil.ReadFile() is all you need.

data := ioutil.ReadFile(os.Args[1])

// Do we need to know the data size?
slice_size := len(data)

// Do we need to look at each byte?
for _,byte := range data {
    // do something with each byte
}
Daniel
  • 38,041
  • 11
  • 92
  • 73
  • you wrote "You need to know which chunk has reached the end of the file" . How would i know that ? By the way you wrote ? or sumthing different ? – Worlock Jan 22 '13 at 23:07
3

When you use ioutil.ReadFile(), you don't ever see io.EOF, by design, because ReadFile will read the whole file until EOF is reached. So the slice it returns is the whole file. From the doc:

ReadFile reads the file named by filename and returns the contents. A successful call returns err == nil, not err == EOF. Because ReadFile reads the whole file, it does not treat an EOF from Read as an error to be reported.

From your question, you explicitly mention that you are aware there are other ways to read the file, and some of those ways require you to test the error for io.EOF, but not ReadFile.

Then, with the slice you have, you can read the file using the for...range construct, as others have mentioned. This is a sure way to read the whole file and nothing more (again, ReadFile takes care of that). Or iterating from 0 to len(spoon) - 1 would work too, but range is more idiomatic and basically does the same.

In other words: when you reach the end of the slice, you reach the end of the file (provided ReadFile did not return an error).

mna
  • 22,989
  • 6
  • 46
  • 49
2

A slice has no concept of end of file. The slice returned by ioutil.ReadFile has a specific length, which reflects the size of the file it was read from. A common idiom, but only one of the possible used in this case, is to range the slice, effectively "consuming" all of the bytes, originally sitting in the file:

for i, b := range spoon {
        // At index 'i' is byte 'b'
        // At file's offset 'i', 'b' was read
        ... do something useful here
}
zzzz
  • 87,403
  • 16
  • 175
  • 139