How to continue program execution when using bufio.Scanner in golang - go

Forgive me I am starting out in Go and I am learning about the bufio package but every time I use the Scanner type the command line is stuck on the input and does not continue with normal program flow. I have tried pressing Enter but it just keeps going to a new line.
Here is my code.
/*
Dup 1 prints the text of each line that appears more than
once in the standard input, proceeded by its count.
*/
package main
import(
"bufio"
"fmt"
"os"
)
func main(){
counts := make(map[string]int)
fmt.Println("Type Some Text")
input := bufio.NewScanner(os.Stdin)
for input.Scan(){
counts[input.Text()]++
}
//NOTE: Ignoring potential Errors from input.Err()
for line,n := range counts{
if n > 1{
fmt.Printf("%d \t %s \n",n,line)
}
}
}

You have a for loop which reads lines from the standard input. This loop will run as long as os.Stdin doesn't report io.EOF (that's one case when Scanner.Scan() would return false). Normally this won't happen.
If you want to "simulate" the end of input, press Ctrl+Z on Windows, or Ctrl+D on Linux / unix systems.
So enter some lines (each "closed" by Enter), and when you're finished, press the above mentioned key.
Example output:
Type Some Text
a
a
bb
bb
bbb <-- CTRL+D pressed here
2 a
2 bb
Another option would be to use a "special" word for termination, such as "exit". It could look like this:
for input.Scan() {
line := input.Text()
if line == "exit" {
break
}
counts[line]++
}
Testing it:
Type Some Text
a
a
bb
bb
bbb
exit
2 a
2 bb

Related

Golang: Command Prompt

I am writing a personal tool which gives me a prompt and lets me execute useful commands. Like bash but its not a shell program
I want an input prompt like Bash
If you enter nothing, just print a new line and scan for input again
If you enter the hotkey Ctrl + D, terminate the program
It should be like this
[user#computer ~]$ go build
[user#computer ~]$ ./myapp
$
$ command
.. do command
$ (Ctrl + D hotkey)
[user#computer ~]$
I tried using Scanln but it didn't accept spaces which was a huge problem
EDIT: here is what I implemented
func main() {
var input string
fmt.Println(banners.Gecko1)
fmt.Print("$ ")
fmt.Scanln(&input)
}
Here is the console:
[user#pc MyTool]$ go build
[user#pc MyTool]$ ./MyTool
-----ENTER COMMANDS
$ term1 term2 term3 term4 term5
[user#pc MyTool]$ erm2 term3 term4 term5
bash: erm2: command not found
[user#pc MyTool]$
as you can see, everything after term1 is ignored and somehow passed to bash...
I think that's how scanln is supposed to work. The docs state that it "scans text from standard input, storing successive space-separated values into successive arguments." Alternatively, use the bufio package if you want to read one line at a time. You can refer to the sample code below:
package main
import (
"bufio"
"fmt"
"os"
"strings"
)
func main() {
input_reader := bufio.NewReader(os.Stdin)
for {
fmt.Print("$ ")
line, _ := input_reader.ReadString('\n')
line = strings.Replace(line, "\n", "", -1)
fmt.Println(line)
}
}

Issue with ANSI cursor movement in goroutine

Background
I'm trying to write a Go library for creating terminal task-lists, inspired by the Node library listr.
My library, golist, prints the task list out in a background goroutine and updates the text and status characters using ANSI escape sequences.
The Problem
There's an issue where the final print of the list will occasionally have extra spaces included, leading to some spaces or repeated lines. Here are two examples – one correct, one not – both from runs of the same exact code (here's a link to the code).
Example
Here's an example of what it should look like:
(Here's a gist of the raw text output for the correct output)
And here's an example of what it sometimes looks like:
(Here's a gist of the raw text output for the incorrect output)
If you look at lines 184 and 185 in the gist of the incorrect version, there are two blank lines that aren't in the correct version.
Why is this happening and why is it only happening sometimes?
Code
I'm printing the list to the terminal in the following loop:
go func() {
defer donePrinting() // Tell the Stop function that we're done printing
ts := l.getTaskStates()
l.print(ts)
for {
select {
case <-ctx.Done(): // Check if the print loop should stop
// Perform a final clear and an optional print depending on `ClearOnComplete`
ts := l.getTaskStates()
if l.ClearOnComplete {
l.clear(ts)
return
}
l.clearThenPrint(ts)
return
case s := <-l.printQ: // Check if there's a message to print
fmt.Fprintln(l.Writer, s)
default: // Otherwise, print the list
ts := l.getTaskStates()
l.clearThenPrint(ts)
l.StatusIndicator.Next()
time.Sleep(l.Delay)
}
}
}()
The list is formatted as a string and then printed. The following function formats the string:
// fmtPrint returns the formatted list of messages
// and statuses, using the supplied TaskStates
func (l *List) fmtPrint(ts []*TaskState) string {
s := make([]string, 0)
for _, t := range ts {
s = append(s, l.formatMessage(t))
}
return strings.Join(s, "\n")
}
and the following function builds the ANSI escape string to clear the lines:
// fmtClear returns a string of ANSI escape characters
// to clear the `n` lines previously printed.
func (l *List) fmtClear(n int) string {
s := "\033[1A" // Move up a line
s += "\033[K" // Clear the line
s += "\r" // Move back to the beginning of the line
return strings.Repeat(s, n)
}
I'm using this site as a reference for the ANSI codes.
Thanks in advance for any suggestions you might have about why this is happening!
Let me know if there's any other information I can add that can help.
I think the ANSI codes are just a red herring. I pulled down the library and tried running it locally, and found that the following section is what is creating this issue:
case s := <-l.printQ: // Check if there's a message to print
fmt.Fprintln(l.Writer, s)
When the printQ channel is getting closed, this case is sometimes running, which seems to be moving the cursor down even though nothing is getting printed. This behaviour went away when I moved the call to close the channel after l.printDone is called.
...
// Wait for the print loop to finish
<-l.printDone
if l.printQ != nil {
close(l.printQ)
}
...
This ensures that the loop is no longer running when the channel is closed, and thus the s := <-l.printQ case cannot run.

How can I print the counts of the lines that are introduced via stdin?

I have the following program that I want to show how many times a specific introduced line appears from stdin:
package main
import (
"bufio"
"fmt"
"os"
)
func main() {
counts := make(map[string]int)
input := bufio.NewScanner(os.Stdin)
for input.Scan() {
counts[input.Text()]++
}
// NOTE: ignoring potential errors from input.Err()
for line, n := range counts {
if n > 1 {
fmt.Printf("%d\t%s\n", n, line)
}
}
}
When I run the program it allows me to enter strings, but even when I press enter I don't get any feedback.
What am I missing here? I believe it gets stuked in the first for.
Try this - save the following text in /tmp/input.txt or other local file:
Line A
Line B
Line B
Line A
Line C
Line B
Now pipe the contents of that file as standard input to your program - for example in /tmp/q.go:
cat /tmp/input.txt | go run /tmp/q.go
The output should be:
$ cat /tmp/input.txt | go run /tmp/q.go
2 Line A
3 Line B
your code first gets all lines, then prints the result at the end (EOF).
1 - if you need feedback for each line when you press Enter: edit your first loop:
for input.Scan() {
txt := input.Text()
counts[txt]++
fmt.Println("counts[", txt, "] =", counts[txt])
}
like this working sample code:
package main
import (
"bufio"
"fmt"
"os"
)
func main() {
counts := make(map[string]int)
input := bufio.NewScanner(os.Stdin)
for input.Scan() {
txt := input.Text()
counts[txt]++
fmt.Println("counts[", txt, "] =", counts[txt])
}
// NOTE: ignoring potential errors from input.Err()
for line, n := range counts {
if n > 1 {
fmt.Printf("%d\t%s\n", n, line)
}
}
}
2- if you want the result at the end of data entry, you have two options:
Option A: press EOF at the end of lines in terminal (command prompt): Ctrl+Z then Enter in Windows, or Ctrl+D in Linux.
Option B: save all text data in one file like "lines.txt" and then run your go binary file with this file as input (Redirecting input) like this:
Windows:
main.exe < lines.txt
Linux:
./main < lines.txt
The place where you are going wrong is:
You are not giving any condition for the first for loop to terminate. This leaves you with the two options suggested by Volker. Either way, you are essentially providing the program with an EOF, for it to know when to stop scanning for input.
Now, the chances are this might not look very clean to you. In that case (and only in that case), you can proceed by introducing a termination condition inside the input for loop:
if input.Text() == "" {
break;
}
This leads to the execution to reach the second for loop as soon as you enter two new-line characters. Quite handy if you would like to prompt the user on the beginning of the program to Hit enter twice to show results, or something of that sorts.
Cheers!

Read multi word string from console

I realized that the following only reads a single word string -
fmt.Scan(&sentence)
How do I read multi word string - as in, the string sentence should store a string that contains multiple words.
One can use the InputReader also to scan and print multiple words from the console.
The solution code is as follows:
package main
import (
"bufio"
"fmt"
"os"
)
func main() {
inputReader := bufio.NewReader(os.Stdin)
input, _ := inputReader.ReadString('\n')
fmt.Println(input)
}
Console Input:
Let's Go!!!
Console Output:
Let's Go!!!
Note:
To run a GOLANG program, open the command prompt or powershell, navigate to the directory where the program file is present and type in the following command:
go run file_name.go
Your question refers to scanning space separated input. The definition of fmt.Scan https://golang.org/pkg/fmt/#Scan states:
Scan scans text read from standard input, storing successive space-
separated values into successive arguments. Newlines count as space.
It returns the number of items successfully scanned. If that is less
than the number of arguments, err will report why.
So, by definition, input is scanned up until the first space is found. To scan, let's say until you hit a \n on the command line you can use the code from the comment scanning spaces from stdin in Go:
scanner := bufio.NewScanner(os.Stdin)
for scanner.Scan() {
return scanner.Text()
}
if err := scanner.Err(); err != nil {
fmt.Fprintln(os.Stderr, "reading standard input:", err)
}
Also this thread might be useful: https://groups.google.com/forum/#!topic/golang-nuts/r6Jl4D9Juw0

How to have an in-place string that updates on stdout

I want to output to stdout and have the output "overwrite" the previous output.
For example; if I output On 1/10, I want the next output On 2/10 to overwrite On 1/10. How can I do this?
stdout is a stream (io.Writer). You cannot modify what was already written to it. What can be changed is how that stream's represented in case it is printed to a terminal. Note that there's no good reason to assume this scenario. For example, a user could redirect stdout to a pipe or to a file at will.
So the proper approach is to first check:
if the stdout is going to a terminal
what is that terminal's procedure to overwrite a line/screen
Both of the above are out of this question's scope, but let's assume that a terminal is our device. Then usually, printing:
fmt.Printf("\rOn %d/10", i)
will overwrite the previous line in the terminal. \r stands for carriage return, implemented by many terminals as moving the cursor to the beginning of the current line, hence providing the "overwrite line" facility.
As an example of "other" terminal with a differently supported 'overwriting', here is an example at the playground.
Use this solution if you want to rewrite multiple lines to the output. For instance, I made a decent Conway's "Game of Life" output using this method.
DISCLAIMER: this only works on ANSI Terminals, and besides using fmt this isn't a Go-specific answer either.
fmt.Printf("\033[0;0H")
// put your other fmt.Printf(...) here
Brief Explanation: this is an escape sequence which tells the ANSI terminal to move the cursor to a particular spot on the screen. The \033[ is the so-called escape sequence, and the 0;0H is the type of code telling the terminal move the cursor to row 0, column 0 of the terminal.
Source: https://en.wikipedia.org/wiki/ANSI_escape_code#Sequence_elements
The solution for one string which will replace whole string
fmt.Printf("\033[2K\r%d", i)
For example, it correctly prints from 10 to 0:
for i:= 10; i>=0; i-- {
fmt.Printf("\033[2K\r%d", i)
time.Sleep(1 * time.Second)
}
fmt.Println()
which previous answers don't solve.
Found something worth sharing for problems like this.
Sharing for people who might be facing same problem in future
Check if output is being written to terminal. If so, use \r (carriage return) defined by terminal to move cursor to the beginning of line
package main
import (
"flag"
"fmt"
"os"
"time"
)
var spinChars = `|/-\`
type Spinner struct {
message string
i int
}
func NewSpinner(message string) *Spinner {
return &Spinner{message: message}
}
func (s *Spinner) Tick() {
fmt.Printf("%s %c \r", s.message, spinChars[s.i])
s.i = (s.i + 1) % len(spinChars)
}
func isTTY() bool {
fi, err := os.Stdout.Stat()
if err != nil {
return false
}
return fi.Mode()&os.ModeCharDevice != 0
}
func main() {
flag.Parse()
s := NewSpinner("working...")
isTTY := isTTY()
for i := 0; i < 100; i++ {
if isTTY {
s.Tick()
}
time.Sleep(100 * time.Millisecond)
}
}
Example code taken from

Resources