golang issue with reusing variable outside of if/for loops - go

I am new to Go and have been working through different issues I am having with the code I am trying to write. One problem though has me scratching my head. I have been searching net but so far did not find a way to solve this.
As you will see in the below code, I am using flag to specify whether to create log file or not. The problem I am running into is that if I put w := bufio.NewWriter(f) inside the if loop then w is inaccessible from the following for loop. If I leave it outside of the if loop then buffio cannot access f.
I know I am missing something dead simple, but I am at a loss at this moment.
Does anyone have any suggestions?
package main
import (
"bufio"
"flag"
"fmt"
"os"
"time"
"path/filepath"
"strconv"
)
var (
logFile = flag.String("file", "yes", "Save output into file")
t = time.Now()
dir, _ = filepath.Abs(filepath.Dir(os.Args[0]))
)
func main() {
flag.Parse()
name := dir + "/" + "output_" + strconv.FormatInt(t.Unix(), 10) + ".log"
if *logFile == "yes" {
f, err := os.Create(name)
if err != nil {
panic(err)
}
defer f.Close()
}
w := bufio.NewWriter(f)
for _, v := range my_slice {
switch {
case *logFile == "yes":
fmt.Fprintln(w, v)
case *logFile != "yes":
fmt.Println(v)
}
}
w.Flush()
}

os.Stdout is an io.Writer too, so you can simplify your code to
w := bufio.NewWriter(os.Stdout)
if *logFile == "yes" {
// ...
w = bufio.NewWriter(f)
}
for _, v := range mySlice {
fmt.Fprintln(w, v)
}
w.Flush()

Related

GO - Reading from Stdout in a loop

I'm new to GO and I'm trying to write a small utility in which I would like to execute commands in a loop and read their output. The code works but only the first iteration produces an output. I guess the assignment of stdout in the first iteration somehow blocks the subsequent use of stdout. Can someone explain me how to get around this problem?
(I simplified the code where the IP Addresses come from. I read them from a file but that's not relevant for the Problem.)
package main
import (
"os/exec"
"bufio"
"fmt"
"os"
"strings"
)
var ip_addresses []string
func main() {
ip_addresses = append(ip_addresses,"/server:192.168.100.1")
ip_addresses = append(ip_addresses,"/server:192.168.100.2")
ip_addresses = append(ip_addresses,"/server:192.168.100.3")
for _, eachline := range ip_addresses {
if strings.HasPrefix(eachline, "#") != true {
c, b := exec.Command("query", "user", eachline), new(strings.Builder)
c.Stdout = b
c.Run()
print(b.String())
}
}
}
You should catch the *exec.ExitError exception and log it to prevent the loop from breaking.
for _, eachline := range ip_addresses {
cmd := exec.Command("ping", eachline)
stdout, err := cmd.Output()
if exit, ok := err.(*exec.ExitError); ok {
if status, ok := exit.Sys().(syscall.WaitStatus); ok {
log.Printf("Exit Status: %d", status.ExitStatus())
}
} else {
log.Fatal(err)
}
fmt.Print(string(stdout))
}

Why does seeding and generating a random number prevent bufio scanner from reading lines in a file in Go? [duplicate]

This question already has an answer here:
Golang, a proper way to rewind file pointer
(1 answer)
Closed 1 year ago.
I have a program which generates a random number between 1 and the number of lines in a file - call that number n. It then reads the file until iterator i == n and then prints that line from the file.
I'm seeing very strange behaviour though which I'm struggling to explain. For some reason, when I allow my code to seed and generate a random number, the bufio scanner fails and does not print the name from the file. When I comment the number generation part out, the exact same code works and a name is printed as expected.
In the below code, commenting and un-commenting the code between the hashtags inexplicably changes the behaviour of the bufio scanner code - and by that I mean it either prints or doesn't print a name from the file.
The file I refer to is just a list of names e.g.
name1
name2
name3
NOTE:
go version == 1.16.3 darwin/amd64
Code:
package main
import (
"bufio"
"bytes"
"flag"
"fmt"
"io"
"log"
"os"
"path/filepath"
"strings"
)
func main() {
f, err := os.Open(nameFile)
if err != nil {
log.Fatalln(err)
}
defer f.Close()
// ############## RANDOM NUMBER GENERATION ##############
// min := 1
// max, err := lineCounter(f)
// if err != nil {
// log.Fatalln(err)
// }
// rand.Seed(time.Now().UnixNano())
// v := rand.Intn(max-min) + min
// fmt.Println(v)
// ############## RANDOM NUMBER GENERATION ##############
scanner := bufio.NewScanner(f)
i := 0
for scanner.Scan() {
i += 1
if i == 60 {
fmt.Println(scanner.Text())
}
}
if err := scanner.Err(); err != nil {
log.Fatalln(err)
}
}
func lineCounter(r io.Reader) (int, error) {
buf := make([]byte, 32*1024)
count := 0
lineSep := []byte{'\n'}
for {
c, err := r.Read(buf)
count += bytes.Count(buf[:c], lineSep)
switch {
case err == io.EOF:
return count, nil
case err != nil:
return count, err
}
}
}
Your problem can be simplified way, way down:
package main
import (
"bufio"
"io"
"strings"
)
func main() {
f := strings.NewReader(strings.Repeat("north\n", 9))
io.ReadAll(f)
s := bufio.NewScanner(f)
for s.Scan() {
println(s.Text())
}
}
So as you can see, it's nothing to do with the random numbers, and it's not even anything to do with a file. When you are calling that "bad function", you're reading up all the data in the reader, so nothing is left for the Scanner to use.

Using regular expressions in Go to Identify a common pattern

I'm trying to parse this string goats=1\r\nalligators=false\r\ntext=works.
contents := "goats=1\r\nalligators=false\r\ntext=works"
compile, err := regexp.Compile("([^#\\s=]+)=([a-zA-Z0-9.]+)")
if err != nil {
return
}
matchString := compile.FindAllStringSubmatch(contents, -1)
my Output looks like [[goats=1 goats 1] [alligators=false alligators false] [text=works text works]]
What I'm I doing wrong in my expression to cause goats=1 to be valid too? I only want [[goats 1]...]
For another approach, you can use the strings package instead:
package main
import (
"fmt"
"strings"
)
func parse(s string) map[string]string {
m := make(map[string]string)
for _, kv := range strings.Split(s, "\r\n") {
a := strings.Split(kv, "=")
m[a[0]] = a[1]
}
return m
}
func main() {
m := parse("goats=1\r\nalligators=false\r\ntext=works")
fmt.Println(m) // map[alligators:false goats:1 text:works]
}
https://golang.org/pkg/strings

Weird channel behavior in go

package main
import (
"encoding/json"
"fmt"
"/something/models"
"os"
"path/filepath"
"runtime"
)
func WriteDeviceToFile(d chan *models.Device, fileName string) {
_, b, _, _ := runtime.Caller(0)
basepath := filepath.Dir(b)
filePath := basepath + "/dataFile/" + fileName
var f *os.File
var err error
f, _ = os.OpenFile(filePath, os.O_APPEND|os.O_WRONLY, 0600)
defer f.Close()
for device := range d {
deviceB, err := json.Marshal(device)
fmt.Println(string(deviceB))
if err == nil {
if _, err = f.WriteString(string(deviceB)); err != nil {
panic(err)
}
} else {
panic(err)
}
}
}
func main() {
deviceChan := make(chan *models.Device)
go WriteDeviceToFile(deviceChan, "notalive.txt")
d := models.NewDevice("12346", "")
deviceChan <- d
d = models.NewDevice("abcd", "")
deviceChan <- d
close(deviceChan)
}
This only works with at least two devices sent to channel. With only one device in deviceChan, the function does not receive anything. Is the channel gone before the WriteDeviceToFile gets to it?
The program exits when main returns. Nothing prevents main from exiting before the files are written

Getting count of files in directory using Go

How might I get the count of items returned by io/ioutil.ReadDir()?
I have this code, which works, but I have to think isn't the RightWay(tm) in Go.
package main
import "io/ioutil"
import "fmt"
func main() {
files,_ := ioutil.ReadDir("/Users/dgolliher/Dropbox/INBOX")
var count int
for _, f := range files {
fmt.Println(f.Name())
count++
}
fmt.Println(count)
}
Lines 8-12 seem like way too much to go through to just count the results of ReadDir, but I can't find the correct syntax to get the count without iterating over the range. Help?
Found the answer in http://blog.golang.org/go-slices-usage-and-internals
package main
import "io/ioutil"
import "fmt"
func main() {
files,_ := ioutil.ReadDir("/Users/dgolliher/Dropbox/INBOX")
fmt.Println(len(files))
}
ReadDir returns a list of directory entries sorted by filename, so it is not just files. Here is a little function for those wanting to get a count of files only (and not dirs):
func fileCount(path string) (int, error){
i := 0
files, err := ioutil.ReadDir(path)
if err != nil {
return 0, err
}
for _, file := range files {
if !file.IsDir() {
i++
}
}
return i, nil
}
Starting with Go 1.16 (Feb 2021), a better option is os.ReadDir:
package main
import "os"
func main() {
d, e := os.ReadDir(".")
if e != nil {
panic(e)
}
println(len(d))
}
os.ReadDir returns fs.DirEntry instead of fs.FileInfo, which means that
Size and ModTime methods are omitted, making the process more efficient if
you just need an entry count.
https://golang.org/pkg/os#ReadDir
If you wanna get all files (not recursive) you can use len(files). If you need to just get the files without folders and hidden files just loop over them and increase a counter. And please don’t ignore errors
By looking at the code of ioutil.ReadDir
func ReadDir(dirname string) ([]fs.FileInfo, error) {
f, err := os.Open(dirname)
if err != nil {
return nil, err
}
list, err := f.Readdir(-1)
f.Close()
if err != nil {
return nil, err
}
sort.Slice(list, func(i, j int) bool { return list[i].Name() < list[j].Name() })
return list, nil
}
you would realize that it calls os.File.Readdir() then sorts the files.
In case of counting it, you don't need to sort, so you are better off calling os.File.Readdir() directly.
You can simply copy and paste this function then remove the sort.
But I did find out that f.Readdirnames(-1) is much faster than f.Readdir(-1).
Running time is almost half for /usr/bin/ with 2808 items (16ms vs 35ms).
So to summerize it in an example:
package main
import (
"fmt"
"os"
)
func main() {
f, err := os.Open(os.Args[1])
if err != nil {
panic(err)
}
list, err := f.Readdirnames(-1)
f.Close()
if err != nil {
panic(err)
}
fmt.Println(len(list))
}

Resources