ripat
ripat

Reputation: 3236

Prevent ReadFile or ReadAll from reading EOF

I start learning Go and I am a bit puzzled by the fact it includes the EOF when using the ioutil.ReadFile function. I want, for example, to read a file and parse all its lines on a field separator.

Sample input File:

CZG;KCZG;some text
EKY;KEKY;some text
A50;KA50;some text
UKY;UCFL;some text
MIC;KMIC;some text
K2M;K23M;some text

This is what I do to read and parse that file:

import(
    "fmt"
    "log"
    "io/ioutil"
    "strings"
    )

func main() {  
    /* Read file */
    airportsFile := "/path/to/file/ad_iata"
    content, err := ioutil.ReadFile(airportsFile)
    if err != nil {
        log.Fatal(err)
    }

    /* split content on EOL */
    lines := strings.Split(string(content), "\n")

    /* split line on field separator ; */
    for _, line := range lines {
        lineSplit := strings.Split(line, ";")
        fmt.Println(lineSplit)
    }
}

The string.Split function adds a empty element at the end of the lineSplit slice when it sees the EOF (nothing to parse). Therefore, if I want to access the second index of that slice (lineSplit[1]) I run into a panic: runtime error: index out of range. I have to restrict the range by doing this

/* split line on field separator ; */
lenLines := len(lines) -1
for _, line := range lines[:lenLines] {
    lineSplit := strings.Split(line, ";")
    fmt.Println(lineSplit[1])
}

Is there a better way if I want to keep using ReadFile for its terseness ?

The same problem occurs when using ioutil.ReadAll

Upvotes: 1

Views: 2470

Answers (3)

Uvelichitel
Uvelichitel

Reputation: 8490

Your input File seeems to be CSV file, so you can use encoding/csv

airportsFile := "/path/to/file/ad_iata"
content, err := os.Open(airportsFile)
    if err != nil {
        log.Fatal(err)
    }
r := csv.NewReader(content)
r.Comma = ';'
records, err := r.ReadAll()  /* split line on field separator ; */
if err != nil {
    log.Fatal(err)
}
fmt.Println(records)

which looks terse enough for me and provide correct output

[[CZG KCZG some text] [EKY KEKY some text] [A50 KA50 some text] [UKY UCFL some text] [MIC KMIC some text] [K2M K23M some text]]

Upvotes: 2

Endre Simo
Endre Simo

Reputation: 11541

You may use scanner.Err() to check for errors on file read.

// Err returns the first non-EOF error that was encountered by the Scanner.
func (s *Scanner) Err() error {
    if s.err == io.EOF {
        return nil
    }
    return s.err
}

In general in go the idiomatic way to read and parse a file is to use bufio.NewScanner which accept as an input parameter the file to read and returns a new Scanner.

Considering the above remarks here is a way you can read and parse a file:

package main

import (
    "bufio"
    "fmt"
    "os"
)

func main() {
    input, err := os.Open("example.txt")

    if err != nil {
        panic("Error happend during opening the file. Please check if file exists!")
        os.Exit(1)
    }

    defer input.Close()

    scanner := bufio.NewScanner(input)
    for scanner.Scan() {
        line := scanner.Text()
        fmt.Printf("%v\n", line)
    }
    if err := scanner.Err(); err != nil {
        fmt.Fprintln(os.Stderr, "reading input:", err)
    }    
}

Upvotes: 1

Thomas
Thomas

Reputation: 182000

There is no such thing as an "EOF byte" or "EOF character". What you are seeing is probably caused by a line break character ('\n') at the very end of the file.

To read a file line by line, it's more idiomatic to use bufio.Scanner instead:

file, err := os.Open(airportsFile)
if err != nil {
    log.Fatal(err)
}
defer file.Close()

scanner := bufio.NewScanner(file)
for scanner.Scan() {
    line := scanner.Text()
    // ... use line as you please ...
}

if err := scanner.Err(); err != nil {
    log.Fatal(err)
}

And this actually addresses your problem, because Scanner will read the final newline without starting a new line, as evidenced by this playground example.

Upvotes: 4

Related Questions