How to copy os.Stdout output to string variable - go

I have a function like this:
package main
import (
"fmt"
)
// PrintSomething prints some thing
func PrintSomething() {
fmt.Println("print something")
}
func main() {
PrintSomething()
}
How do I wrap PrintSomething to another function call CaptureSomething to save the string "print something" to a variable and return it?

Create pipe and set stdout to the pipe writer. Start a goroutine to copy the pipe reader to a buffer. When done, close the pipe writer and wait for goroutine to complete reading. Return the buffer as a string.
// capture replaces os.Stdout with a writer that buffers any data written
// to os.Stdout. Call the returned function to cleanup and get the data
// as a string.
func capture() func() (string, error) {
r, w, err := os.Pipe()
if err != nil {
panic(err)
}
done := make(chan error, 1)
save := os.Stdout
os.Stdout = w
var buf strings.Builder
go func() {
_, err := io.Copy(&buf, r)
r.Close()
done <- err
}()
return func() (string, error) {
os.Stdout = save
w.Close()
err := <-done
return buf.String(), err
}
}
Use it like this:
done := capture()
fmt.Println("Hello, playground")
capturedOutput, err := done()
if err != nil {
// handle error
}
playground example

For example,
package main
import (
"fmt"
"io/ioutil"
"os"
)
// PrintSomething prints some thing
func PrintSomething() {
fmt.Println("print something")
}
func CaptureSomething() (string, error) {
defer func(stdout *os.File) {
os.Stdout = stdout
}(os.Stdout)
out, err := ioutil.TempFile("", "stdout")
if err != nil {
return "", err
}
defer out.Close()
outname := out.Name()
os.Stdout = out
PrintSomething()
err = out.Close()
if err != nil {
return "", err
}
data, err := ioutil.ReadFile(outname)
if err != nil {
return "", err
}
os.Remove(outname)
return string(data), nil
}
func main() {
s, err := CaptureSomething()
if err != nil {
fmt.Println(err)
} else {
fmt.Print(s)
}
}
Playground: https://play.golang.org/p/O2kSegxYeGy
Output:
print something

Use one of these, whichever works for you:
package main
import (
"bytes"
"fmt"
"io"
"os"
"strings"
)
func PrintSomething() {
fmt.Println("print something")
}
func PrintSomethingBig() {
for i := 0; i < 100000; i++ {
fmt.Println("print something")
}
}
func PrintSomethingOut(out io.Writer) {
fmt.Fprintln(out, "print something to io.Writer")
}
func PrintSomethingString() string {
return fmt.Sprintln("print something into a string")
}
// not thread safe
// modified by zlynx#acm.org from original at http://craigwickesser.com/2015/01/capture-stdout-in-go/
func captureStdout(f func()) string {
old := os.Stdout
r, w, _ := os.Pipe()
os.Stdout = w
go func() {
f()
w.Close()
}()
buf := &bytes.Buffer{}
// Will complete when the goroutine calls w.Close()
io.Copy(buf, r)
// Clean up.
os.Stdout = old
r.Close()
return buf.String()
}
func main() {
str1 := &strings.Builder{}
str2 := PrintSomethingString()
PrintSomethingOut(str1)
PrintSomethingOut(os.Stdout)
str3 := captureStdout(PrintSomething)
str4 := captureStdout(PrintSomethingBig)
fmt.Println("string 1 is", str1)
fmt.Println("string 2 is", str2)
fmt.Println("string 3 is", str3)
fmt.Println("string 4 len", len(str4))
}

Related

Unable to loop through golang dynamic channels

I want to loop through the menu's options. However, it stops at the first option, since the select without "default:" is blocking and it does not know more options will appear dynamically.
Bellow is the broken code:
package main
import (
"bytes"
"fmt"
"io/ioutil"
"log"
"os/exec"
"strings"
"time"
"github.com/getlantern/systray"
"gopkg.in/yaml.v3"
)
var menuItensPtr []*systray.MenuItem
var config map[string]string
var commands []string
func main() {
config = readconfig()
systray.Run(onReady, onExit)
}
func onReady() {
systray.SetIcon(getIcon("assets/menu.ico"))
menuItensPtr = make([]*systray.MenuItem,0)
commands = make([]string,0)
for k, v := range config {
menuItemPtr := systray.AddMenuItem(k, k)
menuItensPtr = append(menuItensPtr, menuItemPtr)
commands = append(commands, v)
}
systray.AddSeparator()
// mQuit := systray.AddMenuItem("Quit", "Quits this app")
go func() {
for {
systray.SetTitle("My tray menu")
systray.SetTooltip("https://github.com/evandrojr/my-tray-menu")
time.Sleep(1 * time.Second)
}
}()
go func() {
for{
for i, menuItenPtr := range menuItensPtr {
select {
/// EXECUTION GETS STUCK HERE!!!!!!!
case<-menuItenPtr.ClickedCh:
execute(commands[i])
}
}
// select {
// case <-mQuit.ClickedCh:
// systray.Quit()
// return
// // default:
// }
}
}()
}
func onExit() {
// Cleaning stuff will go here.
}
func getIcon(s string) []byte {
b, err := ioutil.ReadFile(s)
if err != nil {
fmt.Print(err)
}
return b
}
func execute(commands string){
command_array:= strings.Split(commands, " ")
command:=""
command, command_array = command_array[0], command_array[1:]
cmd := exec.Command(command, command_array ...)
var out bytes.Buffer
cmd.Stdout = &out
err := cmd.Run()
if err != nil {
log.Fatal(err)
}
// fmt.Printf("Output %s\n", out.String())
}
func readconfig() map[string]string{
yfile, err := ioutil.ReadFile("my-tray-menu.yaml")
if err != nil {
log.Fatal(err)
}
data := make(map[string]string)
err2 := yaml.Unmarshal(yfile, &data)
if err2 != nil {
log.Fatal(err2)
}
for k, v := range data {
fmt.Printf("%s -> %s\n", k, v)
}
return data
}
Bellow is the ugly workaround that works:
package main
import (
"bytes"
"fmt"
"io/ioutil"
"log"
"os"
"os/exec"
"path/filepath"
"strings"
"time"
"github.com/getlantern/systray"
"gopkg.in/yaml.v3"
)
var menuItensPtr []*systray.MenuItem
var config map[string]string
var commands []string
var labels []string
var programPath string
func main() {
setProgramPath()
config = readconfig()
time.Sleep(1 * time.Second)
systray.Run(onReady, onExit)
}
func onReady() {
systray.SetIcon(getIcon(filepath.Join(programPath,"assets/menu.ico")))
menuItensPtr = make([]*systray.MenuItem, 0)
i := 0
op0 := systray.AddMenuItem(labels[i], commands[i])
i++
op1 := systray.AddMenuItem(labels[i], commands[i])
i++
op2 := systray.AddMenuItem(labels[i], commands[i])
i++
op3 := systray.AddMenuItem(labels[i], commands[i])
i++
systray.AddSeparator()
mQuit := systray.AddMenuItem("Quit", "Quits this app")
go func() {
for {
systray.SetTitle("My tray menu")
systray.SetTooltip("https://github.com/evandrojr/my-tray-menu")
time.Sleep(1 * time.Second)
}
}()
go func() {
for {
select {
// HERE DOES NOT GET STUCK!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
case <-op0.ClickedCh:
execute(commands[0])
case <-op1.ClickedCh:
execute(commands[1])
case <-op2.ClickedCh:
execute(commands[2])
case <-op3.ClickedCh:
execute(commands[3])
case <-mQuit.ClickedCh:
systray.Quit()
return
}
}
}()
}
func onExit() {
// Cleaning stuff will go here.
}
func getIcon(s string) []byte {
b, err := ioutil.ReadFile(s)
if err != nil {
fmt.Print(err)
}
return b
}
func setProgramPath(){
ex, err := os.Executable()
if err != nil {
panic(err)
}
programPath = filepath.Dir(ex)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
}
func execute(commands string) {
command_array := strings.Split(commands, " ")
command := ""
command, command_array = command_array[0], command_array[1:]
cmd := exec.Command(command, command_array...)
var out bytes.Buffer
cmd.Stdout = &out
err := cmd.Run()
if err != nil {
log.Fatal(err)
}
fmt.Printf("Output %s\n", out.String())
}
func readconfig() map[string]string {
yfile, err := ioutil.ReadFile(filepath.Join(programPath,"my-tray-menu.yaml"))
if err != nil {
log.Fatal(err)
}
data := make(map[string]string)
err2 := yaml.Unmarshal(yfile, &data)
if err2 != nil {
log.Fatal(err2)
}
labels = make([]string, 0)
commands = make([]string, 0)
for k, v := range data {
labels = append(labels, k)
commands = append(commands, v)
fmt.Printf("%s -> %s\n", k, v)
}
fmt.Print(len(labels))
return data
}
Full source code here:
https://github.com/evandrojr/my-tray-menu
select "chooses which of a set of possible send or receive operations will proceed". The spec sets out how this choice is made:
If one or more of the communications can proceed, a single one that can proceed is chosen via a uniform pseudo-random selection. Otherwise, if there is a default case, that case is chosen. If there is no default case, the "select" statement blocks until at least one of the communications can proceed.
Your working example:
select {
case <-op0.ClickedCh:
execute(commands[0])
case <-op1.ClickedCh:
execute(commands[1])
// ...
}
uses select successfully to choose between one of the offered options. However if you pass a single option e.g.
select {
case<-menuItenPtr.ClickedCh:
execute(commands[i])
}
}
The select will block until <-menuItenPtr.ClickedCh is ready to proceed (e.g. something is received). This is effectively the same as not using a select:
<-menuItenPtr.ClickedCh:
execute(commands[i])
The result you were expecting can be achieved by providing a default option:
select {
case<-menuItenPtr.ClickedCh:
execute(commands[i])
}
default:
}
As per the quote from the spec above the default option will be chosen if none of the other options can proceed. While this may work it's not a very good solution because you effectively end up with:
for {
// Check if event happened (not blocking)
}
This will tie up CPU time unnecessarily as it continually loops checking for events. A better solution would be to start a goroutine to monitor each channel:
for i, menuItenPtr := range menuItensPtr {
go func(c chan struct{}, cmd string) {
for range c { execute(cmd) }
}(menuItenPtr.ClickedCh, commands[i])
}
// Start another goroutine to handle quit
The above will probably work but does lead to the possibility that execute will be called concurrently (which might cause issues if your code is not threadsafe). One way around this is to use the "fan in" pattern (as suggested by #kostix and in the Rob Pike video suggested by #John); something like:
cmdChan := make(chan int)
for i, menuItenPtr := range menuItensPtr {
go func(c chan struct{}, cmd string) {
for range c { cmdChan <- cmd }
}(menuItenPtr.ClickedCh, commands[i])
}
go func() {
for {
select {
case cmd := <- cmdChan:
execute(cmd) // Handle command
case <-mQuit.ClickedCh:
systray.Quit()
return
}
}
}()
note: all code above entered directly into the question so please treat as pseudo code!

How to terminate a console input request when a new input is requested

I need to terminate an existing console input request when a new one is requested. The following code is an attempt to close an existing request using a channel but it does not seem to terminate the input request.
package main
import (
"bufio"
"fmt"
"log"
"os"
"strings"
"time"
)
func main() {
go Confirm("you are a programmer, aint you?")
time.Sleep(2 * time.Second)
Confirm("do you love go?")
}
var cancelChannel chan struct{}
func Confirm(s string) bool {
//check if channel type holds a value then close the channel to remove previous confirmation input
if cancelChannel != nil {
fmt.Println("channel to be closed")
close(cancelChannel)
}
cancelChannel = make(chan struct{})
reader := bufio.NewReader(os.Stdin)
for {
fmt.Printf("%s [y/n]: ", s)
response, err := reader.ReadString('\n')
if err != nil {
log.Fatal(err)
}
response = strings.ToLower(strings.TrimSpace(response))
if response == "y" || response == "yes" {
return true
} else if response == "n" || response == "no" {
return false
}
if _, ok := <-cancelChannel; !ok {
fmt.Println("channel closed")
return false
}
}
}
As #JimB mentioned in comment you can't interrupt read on stdin although there is kinda shady trick how you can achieve it. It's possible to duplicate os.Stdin file descriptor using syscall (not recommended) and open it as non blocking file.
package main
import (
"bytes"
"context"
"errors"
"fmt"
"io"
"io/fs"
"io/ioutil"
"os"
"syscall"
"time"
)
func setNonblock(f *os.File) error {
c, err := f.SyscallConn()
if err != nil {
return err
}
var err2 error
err = c.Control(func(fd uintptr) {
err2 = syscall.SetNonblock(int(fd), true)
})
if err != nil {
return err
}
return err2
}
func nonBlockingFile(f *os.File) (*os.File, error) {
if err := setNonblock(f); err != nil {
return nil, err
}
fd, err := syscall.Dup(int(f.Fd()))
if err != nil {
return nil, err
}
f2 := os.NewFile(uintptr(fd), f.Name())
return f2, nil
}
func read(ctx context.Context, f *os.File) (io.Reader, error) {
r, err := nonBlockingFile(f)
if err != nil {
return nil, err
}
go func() {
defer r.Close()
<-ctx.Done()
}()
buff := bytes.NewBuffer([]byte{})
for {
_, err := io.Copy(buff, r)
if err != nil {
if errors.Is(err, fs.ErrClosed) {
break
}
panic(err)
}
}
return buff, nil
}
func main() {
ctx1, cancel := context.WithCancel(context.Background())
go func() {
time.Sleep(time.Second * 2)
cancel()
}()
buf1, err := read(ctx1, os.Stdin)
if err != nil {
panic(err)
}
ctx2, _ := context.WithTimeout(context.Background(), time.Second*2)
buf2, err := read(ctx2, os.Stdin)
fmt.Println("buf1")
fmt.Println(ioutil.ReadAll(buf1))
fmt.Println("buf2")
fmt.Println(ioutil.ReadAll(buf2))
}
Go on and explore the simplicity offer by go
https://pkg.go.dev/context#WithCancel
You can have a context that returning CancelFunc then you use context.WithCancel.
And execute cancel func if you want to terminate.
This is the good practice way, you can also do a dirty os.Exit(0) in another case.

Go udp connection dial once, and send many

please consider this code below, it is a simplified version of a service. I launch a no. of goroutines as needed during its lifetime, and as they go about doing things, they need to send udp messages to a set destination.
package main
import (
"fmt"
"log"
"net"
"time"
)
const (
udp_dest = "192.168.1.200:514"
)
func main() {
fmt.Println("Hello")
message := "this is a test"
log_message(&message)
go worker(1)
go worker(2)
go worker(3)
go worker(4)
time.Sleep(3009 * time.Second)
}
func log_message(message *string) {
RemoteAddr, err := net.ResolveUDPAddr("udp", udp_dest)
if err != nil {
//fmt.Println("Err, net.ResolveUDPAddr", err)
return
}
conn, err := net.DialUDP("udp", nil, RemoteAddr)
if err != nil {
return
}
udp_message := fmt.Sprintf("<30> %s", *message)
Bytes, _ := conn.Write([]byte(udp_message))
log.Printf("Sent %d Bytes to %s\n", Bytes, udp_dest)
}
func worker(tag int) {
i := 0
for {
worker_message := fmt.Sprintf("Some message from worker%d, loop: %d", tag, i)
log_message(&worker_message)
// do some work..
time.Sleep(300 * time.Second)
i += 1
}
}
In my log_message, everytime it gets called we're calling net.DialUDP which I feel is wasteful. I tried experimenting with global variables &net.UDPConn et al, but could not get to work.
Please show how to achieve/optimize this? There's only one UDP destination, and I'd like the daemon to Dial once at its start, and then just Write as needed.
Thanks!
here's what I got so far:
package main
import (
"fmt"
"log"
"net"
"time"
)
const (
udp_dest = "192.168.1.200:514"
)
var (
myconn *net.UDPConn
)
func main() {
fmt.Println("Hello")
message := "this is a test"
log_message(&message)
go worker(1)
go worker(2)
go worker(3)
go worker(4)
time.Sleep(3009 * time.Second)
}
func log_message(message *string) {
if myconn == nil {
fmt.Println("Setting up myconn!")
RemoteAddr, err := net.ResolveUDPAddr("udp", udp_dest)
if err != nil {
//fmt.Println("Err, net.ResolveUDPAddr", err)
return
}
myconn, err = net.DialUDP("udp", nil, RemoteAddr)
if err != nil {
return
}
}
udp_message := fmt.Sprintf("<30> %s", *message)
Bytes, _ := myconn.Write([]byte(udp_message))
log.Printf("Sent %d Bytes to %s\n", Bytes, udp_dest)
}
func worker(tag int) {
i := 0
for {
worker_message := fmt.Sprintf("Some message from worker%d, loop: %d", tag, i)
log_message(&worker_message)
// do some work..
time.Sleep(10 * time.Second)
i += 1
}
}
You are almost there. Move the setup code to a function and call it before starting the goroutines.
func main() {
if err := setupLog(); err != nil {
log.Fatal(err)
}
fmt.Println("Hello")
... same as before
}
func setupLog() error {
fmt.Println("Setting up myconn!")
RemoteAddr, err := net.ResolveUDPAddr("udp", udp_dest)
if err != nil {
return err
}
myconn, err = net.DialUDP("udp", nil, RemoteAddr)
return err
}
func log_message(message *string) {
udp_message := fmt.Sprintf("<30> %s", *message)
Bytes, _ := myconn.Write([]byte(udp_message))
log.Printf("Sent %d Bytes to %s\n", Bytes, udp_dest)
}
The code in the question does not work because there's a data race on myconn.

go use fsnotify watch file ischanged not working

I use the library (http://github.com/fsnotify/fsnotify) to monitor the file system.
I m trying to adjust the repository example to match my requirements, but when i do so the program is not working anymore.
I commented the done channel within the ExampleNewWatcher function
done := make(chan bool)
<-done
As a result, now when i run the example, this channel does not output anything anymore.
event, ok := <-watcher.Events
Complete codeļ¼š
package main
import (
"github.com/fsnotify/fsnotify"
"log"
"os"
"strconv"
"time"
)
func ExampleNewWatcher() {
watcher, err := fsnotify.NewWatcher()
if err != nil {
log.Fatal(err)
}
defer watcher.Close()
done := make(chan bool) // if i commen this line the `panic` not norking
go func() {
for {
select {
case event, ok := <-watcher.Events:
if !ok {
return
}
log.Println("event:", event)
if event.Op&fsnotify.Write == fsnotify.Write {
log.Println("modified file:", event.Name)
}
panic("just for test") // new output this line
case err, ok := <-watcher.Errors:
if !ok {
return
}
log.Println("error:", err)
}
}
}()
err = watcher.Add("/tmp/foo")
if err != nil {
log.Fatal(err)
}
<-done // comment this
}
func check(err error) {
if err != nil {
panic(err)
}
}
func main() {
// create the test file
var err error
file, err := os.Create("/tmp/foo")
check(err)
_, err = file.Write([]byte("hello world"))
check(err)
stopchan := make(chan struct{})
// test change file
go func() {
for i := 0; i < 10; i++ {
file1, err := os.OpenFile("/tmp/foo", os.O_RDWR, 0644)
check(err)
d := strconv.Itoa(i) + "hello world"
_, err = file1.Write([]byte(d))
check(err)
err = file1.Close()
check(err)
time.Sleep(2 * time.Second) // wait the context writed to the file
}
}()
ExampleNewWatcher() // monitor file
stopchan <- struct{}{}
}

Golang - how this func is called

I'm working with this sample code but cannot understand how this function is called and which parameter is belong.
go func(r []string) {
processData(r)
ch <- r
}(record)
function closures:
Go functions may be closures. A closure is a function value that
references variables from outside its body. The function may access
and assign to the referenced variables; in this sense the function is
"bound" to the variables.
To Understand this:
go func(r []string) {
processData(r)
ch <- r
}(record)
let's first declare this function:
func routine(r []string) {
processData(r)
ch <- r
}
and this global variable:
var ch = make(chan []string)
Now you may call it:
go routine(record)
this calls function named routine with input parameter named record as a goroutine.
And see: https://gobyexample.com/goroutines
Try it on The Go Playground:
package main
import (
"encoding/csv"
"flag"
"fmt"
"io"
"os"
"strings"
"time"
)
func routine(r []string) {
processData(r)
ch <- r
}
var ch = make(chan []string)
func main() {
start := time.Now()
flag.Parse()
fmt.Print(strings.Join(flag.Args(), "\n"))
if *filename == "REQUIRED" {
return
}
csvfile, err := os.Open(*filename)
if err != nil {
fmt.Println(err)
return
}
defer csvfile.Close()
reader := csv.NewReader(csvfile)
i := 0
for {
record, err := reader.Read()
if err == io.EOF {
break
} else if err != nil {
fmt.Println(err)
return
}
i++
go routine(record)
fmt.Printf("go %d %s\n", i, record)
}
for ; i >= 0; i-- {
fmt.Printf("<- %d %s\n", i, <-ch)
}
fmt.Printf("\n%2fs", time.Since(start).Seconds())
}
func processData([]string) {
time.Sleep(10 * time.Millisecond)
}
var filename = flag.String("f", "REQUIRED", "source CSV file")
var numChannels = flag.Int("c", 4, "num of parallel channels")
//var bufferedChannels = flag.Bool("b", false, "enable buffered channels")

Resources