How to read from os.Stdin repeatedly without shared bufio.Scanner - go

In Go, can a single line of input be read from stdin in a simple way, which also meets the following requirements?
can be called by disparate parts of a larger interactive application without having to create coupling between these different parts of the application (e.g. by passing a global bufio.Scanner between them)
works whether users are running an interactive terminal or using pre-scripted input
I'd like to modify an existing large Go application which currently creates a bufio.Scanner instance every time it asks users for a line of input. Multiple instances work fine when standard input is from a terminal, but when standard input is piped from another process, calls to Scan only succeed on the first instance of bufio.Scanner. Calls from all other instances fail.
Here's some toy code that demonstrates the problem:
package main
import (
"bufio"
"fmt"
"os"
)
func main() {
// read with 1st scanner -> works for both piped stdin and terminal
scanner1 := readStdinLine(1)
// read with 2nd scanner -> fails for piped stdin, works for terminal
readStdinLine(2)
// read with 1st scanner -> prints line 2 for piped stdin, line 3 for terminal
readLine(scanner1, 3)
}
func readStdinLine(lineNum int64) (scanner *bufio.Scanner) {
scanner = readLine(bufio.NewScanner(os.Stdin), lineNum)
return
}
func readLine(scannerIn *bufio.Scanner, lineNum int64) (scanner *bufio.Scanner) {
scanner = scannerIn
scanned := scanner.Scan()
fmt.Printf("%d: ", lineNum)
if scanned {
fmt.Printf("Text=%s\n", scanner.Text())
return
}
if scanErr := scanner.Err(); scanErr != nil {
fmt.Printf("Error=%s\n", scanErr)
return
}
fmt.Println("EOF")
return
}
I build this as print_stdinand run interactively from a bash shell:
~$ ./print_stdin
ab
1: Text=ab
cd
2: Text=cd
ef
3: Text=ef
But if I pipe in the text, the second bufio.Scanner fails:
~$ echo "ab
> cd
> ef" | ./print_stdin
1: Text=ab
2: EOF
3: Text=cd

Your sequence is:
create scanner
wait read terminal
print result
repeat 1 to 3 (creating new scanner about stdin)
repeat 2 to 3
exit program
When you exec echo in pipeline, only exists a stdin/stdout file being read/write, but you are trying to use two.
UPDATE: The flow of execution for echo is:
read args
process args
write args in stdout
terminal read stdout and print its
See that this occur on press ENTER key. The argument whole is sent to echo program and not by line.
The echo utility writes its arguments to standard output, followed by
a . If there are no arguments, only the is written.
More here: http://pubs.opengroup.org/onlinepubs/9699919799/utilities/echo.html.
See in source code how echo work:
while (argc > 0)
{
fputs (argv[0], stdout);//<-- send args to the same stdout
argc--;
argv++;
if (argc > 0)
putchar (' ');
}
So your code will work fine with this:
$ (n=1; while sleep 1; do echo a$n; n=$((n+1)); done) | ./print_stdin
$ 1: Text=a1
$ 2: Text=a2
$ 3: Text=a3
If you need repeat args in differents stdout, use "yes" program or alternatives.
yes program repeats the wrote args in stdout. More in:
https://git.savannah.gnu.org/cgit/coreutils.git/tree/src/yes.c
Example:
$ yes a | ./print_stdin
$ 1: Text=a
$ 2: Text=a
$ 3: Text=a

The suggestion in the comment by ThunderCat works.
The alternative to buffered read is reading a byte a time. Read single bytes until \n or some terminator is found and return the data up to that point.
Here's my implementation, heavily inspired by Scanner.Scan:
package lineio
import (
"errors"
"io"
)
const startBufSize = 4 * 1024
const maxBufSize = 64 * 1024
const maxConsecutiveEmptyReads = 100
var ErrTooLong = errors.New("lineio: line too long")
func ReadLine(r io.Reader) (string, error) {
lb := &lineBuf {r:r, buf: make([]byte, startBufSize)}
for {
lb.ReadByte()
if lb.err != nil || lb.TrimCrlf() {
return lb.GetResult()
}
}
}
type lineBuf struct {
r io.Reader
buf []byte
end int
err error
}
func (lb *lineBuf) ReadByte() {
if lb.EnsureBufSpace(); lb.err != nil {
return
}
for empties := 0; ; {
n := 0
if n, lb.err = lb.r.Read(lb.buf[lb.end:lb.end+1]); lb.err != nil {
return
}
if n > 0 {
lb.end++
return
}
empties++
if empties > maxConsecutiveEmptyReads {
lb.err = io.ErrNoProgress
return
}
}
}
func (lb *lineBuf) TrimCrlf() bool {
if !lb.EndsLf() {
return false
}
lb.end--
if lb.end > 0 && lb.buf[lb.end-1] == '\r' {
lb.end--
}
return true
}
func (lb *lineBuf) GetResult() (string, error) {
if lb.err != nil && lb.err != io.EOF {
return "", lb.err
}
return string(lb.buf[0:lb.end]), nil
}
func (lb *lineBuf) EndsLf() bool {
return lb.err == nil && lb.end > 0 && (lb.buf[lb.end-1] == '\n')
}
func (lb *lineBuf) EnsureBufSpace() {
if lb.end < len(lb.buf) {
return
}
newSize := len(lb.buf) * 2
if newSize > maxBufSize {
lb.err = ErrTooLong
return
}
newBuf := make([]byte, newSize)
copy(newBuf, lb.buf[0:lb.end])
lb.buf = newBuf
return
}
TESTING
Compiled lineio with go install and main (see below) with go build -o read_each_byte.
Tested scripted input:
$ seq 12 22 78 | ./read_each_byte
1: Text: "12"
2: Text: "34"
3: Text: "56"
Tested input from an interactive terminal:
$ ./read_each_byte
abc
1: Text: "abc"
123
2: Text: "123"
x\y"z
3: Text: "x\\y\"z"
Here's main:
package main
import (
"fmt"
"lineio"
"os"
)
func main() {
for i := 1; i <= 3; i++ {
text, _ := lineio.ReadLine(os.Stdin)
fmt.Printf("%d: Text: %q\n", i, text)
}
}

Related

Why i get a duplicate output when the loop if there is a scanf?

When the user enters a wrong number, the code expects to show once the output message but this it's duplicated. why?
note: seems to be something with the scanf inside the loop because if I use scan lonely it works as expected.
Anyway, I can't understand why this behavior
package main
import (
"fmt"
"math/rand"
"time"
)
func main(){
rand.Seed(time.Now().UnixNano())
var number int = rand.Intn(99)
var input int = 0
fmt.Println("random: ", number)
fmt.Println("enter a number: ")
fmt.Scanf("%d",&input)
for {
if number != input {
fmt.Println("wrong! try again:")
fmt.Scanf("%d",&input)
continue
} else {
fmt.Println("that's correct!")
break
}
}
}
To accomodate Windows, write fmt.Scanf("%d\n", &input):
package main
import (
"fmt"
"math/rand"
"time"
)
func main() {
rand.Seed(time.Now().UnixNano())
var number int = rand.Intn(99)
var input int = 0
fmt.Println("random: ", number)
fmt.Println("enter a number: ")
fmt.Scanf("%d\n", &input)
for {
if number != input {
fmt.Println("wrong! try again:")
fmt.Scanf("%d\n", &input)
continue
} else {
fmt.Println("that's correct!")
break
}
}
}
Output:
random: 84
enter a number:
42
wrong! try again:
42
wrong! try again:
84
that's correct!
Windows uses "\r\n" for end-of-line. Linux and others use "\n" for end-of-line.
You did not check for Scanf errors.
package main
import (
"fmt"
"math/rand"
"time"
)
func main() {
rand.Seed(time.Now().UnixNano())
var number int = rand.Intn(99)
var input int = 0
fmt.Println("random: ", number)
fmt.Println("enter a number: ")
n, err := fmt.Scanf("%d", &input)
if err != nil {
fmt.Println(n, err)
}
for {
if number != input {
fmt.Println("wrong! try again:")
n, err := fmt.Scanf("%d", &input)
if err != nil {
fmt.Println(n, err)
}
continue
} else {
fmt.Println("that's correct!")
break
}
}
}
Output (Windows):
random: 84
enter a number:
42
wrong! try again:
0 unexpected newline
wrong! try again:
42
wrong! try again:
0 unexpected newline
wrong! try again:
84
that's correct!
Windows Scans "42\r\n" as "42\r" and "\n".
Output (Linux):
random: 84
enter a number:
42
wrong! try again:
42
wrong! try again:
84
that's correct!
Linux Scans "42\n".
fmt.Scanf will read input alongside the format, if the format doesn't match then the next rune (char) will pass to next input as parameter.
for instance if you have input like abc and you have code like:
fmt.Scanf("%d", &input)
// will return 1 error when if failed to parse "a" as integer
// and pass "bc" to next input
// but no other fmt.Scanf so nothing else to be feed with "bc"
and with same input you have the following code:
fmt.Scanf("%d", &input)
// input = 0; error failed to parse "a" as integer
// and pass "bc" to next input as parameter
for {
if number != input {
fmt.Println("wrong! try again:")
fmt.Scanf("%d",&input)
// input = 0; error 3 times as
// failed to parse "b", "c" as integer
// and unexpected new line
continue
} else {
fmt.Println("that's correct!")
break
}
}
hopefully it helps.
ref: https://golang.org/pkg/fmt/#Scanf

Correct way to filter a byte stream in Golang?

I want to filter the STDOUT from a command such that I only keep the first and last line of any contiguous block of \r terminated lines (to largely ignore progress indicators).
Here's my attempt (orig code does more, this is a simplified version, but basically the filtering has to happen as the input comes in, not at the end):
package main
import (
"bytes"
"fmt"
"os/exec"
)
var cr = []byte("\r")
var lf = []byte("\n")
func main() {
input1 := []byte("a\nb\n\nprogress 98%\r")
input2 := []byte("progress 99%\r")
input3 := []byte("progress 100%\r")
input4 := []byte("\n\nc\n")
var stream []byte
stream = append(stream, input1...)
stream = append(stream, input2...)
stream = append(stream, input3...)
stream = append(stream, input4...)
fmt.Printf("stream:\n%s\n", stream)
streamer := &myFilter{}
streamer.Write(input1)
streamer.Write(input2)
streamer.Write(input3)
streamer.Write(input4)
final := streamer.Bytes()
fmt.Printf("streamer:\n%s\n\n", final)
cmd := exec.Command("bash", "-c", "perl -e '$|++; print qq[a\nb\n\nprogress: 98%\r]; for (99..100) { print qq[progess: $_%\r]; sleep(1); } print qq[\n\nc\n]'")
cmd.Stdout = &myFilter{}
cmd.Start()
cmd.Wait()
fromCmd := cmd.Stdout.(*myFilter).Bytes()
fmt.Printf("fromCmd:\n%s\n", fromCmd)
}
type myFilter struct {
partialLine []byte
storage []byte
}
func (w *myFilter) Write(p []byte) (n int, err error) {
// in order to filter out all but the first and last line of a set of \r
// terminated lines (a progress bar), we need to collect whole \n terminated
// lines
lines := bytes.SplitAfter(p, lf)
if len(w.partialLine) > 0 || (len(lines) == 1 && !bytes.HasSuffix(p, lf)) {
w.partialLine = append(w.partialLine, lines[0]...)
partialComplete := false
if len(lines) > 1 {
lines = lines[1:]
partialComplete = true
} else {
lines = nil
if bytes.HasSuffix(p, lf) {
partialComplete = true
}
}
if partialComplete {
w.filterCR(w.partialLine)
w.partialLine = nil
}
}
lastLineIndex := len(lines) - 1
if lastLineIndex > -1 && !bytes.HasSuffix(p, lf) {
w.partialLine, lines = lines[lastLineIndex], lines[:lastLineIndex]
}
for _, line := range lines {
w.filterCR(line)
}
return len(p), nil
}
func (w *myFilter) filterCR(p []byte) {
if bytes.Contains(p, cr) {
lines := bytes.Split(p, cr)
w.store(lines[0])
w.store(lf)
if len(lines) > 2 {
w.store(lines[len(lines)-2])
w.store(lf)
}
} else {
w.store(p)
}
}
func (w *myFilter) store(p []byte) {
w.storage = append(w.storage, p...)
}
func (w *myFilter) Bytes() []byte {
if len(w.partialLine) > 0 {
w.filterCR(w.partialLine)
}
return w.storage
}
My output is:
stream:
a
b
progress 100%
c
streamer:
a
b
progress 98%
progress 100%
c
fromCmd:
a
b
ss: 100%
progess: 100%
c
What I want is the output you see from "fromCmd" to match the output I got from "streamer".
What am I doing wrong, why does my actual output seem "corrupt", why does the real command run behave differently to my "streamer" test, and what's a better way to filter STDOUT?
Your partial line algorithm isn't correct for all inputs.
You can replace myFilter with a bufio.Scanner, which will handle the partial line buffering correctly for you, and a []byte or bytes.Buffer to accumulate the output.
var out bytes.Buffer
scanner := bufio.NewScanner(stdout)
for scanner.Scan() {
p := scanner.Bytes()
lines := bytes.Split(p, cr)
out.Write(lines[0])
out.Write(lf)
if len(lines) > 1 {
out.Write(lines[len(lines)-1])
out.Write(lf)
}
}

How to replicate do while in go?

I want a set of code to be executed until user explicitly wants to exit the function. For eg: when a user runs the program, he will see 2 options:
Run again
Exit
this will be achieved using switch case structure. Here if user presses 1, set of functions associated with 1 will execute and if user presses 2, the program will exit. How should i achieve this scenario in golang ? In java, i believe this could be done using do while structure but go doesn't support do while loop. Following is my code which i tried but this goes in a infinite loop:
func sample() {
var i = 1
for i > 0 {
fmt.Println("Press 1 to run")
fmt.Println("Press 2 to exit")
var input string
inpt, _ := fmt.Scanln(&input)
switch inpt {
case 1:
fmt.Println("hi")
case 2:
os.Exit(2)
default:
fmt.Println("def")
}
}
}
The program irrespective of the input, prints only "hi". Could someone please correct me what wrong i am doing here ?
Thanks.
A do..while can more directly be emulated in Go with a for loop using a bool loop variable seeded with true.
for ok := true; ok; ok = EXPR { }
is more or less directly equivalent to
do { } while(EXPR)
So in your case:
var input int
for ok := true; ok; ok = (input != 2) {
n, err := fmt.Scanln(&input)
if n < 1 || err != nil {
fmt.Println("invalid input")
break
}
switch input {
case 1:
fmt.Println("hi")
case 2:
// Do nothing (we want to exit the loop)
// In a real program this could be cleanup
default:
fmt.Println("def")
}
}
Edit: Playground (with a dummied-out Stdin)
Though, admittedly, in this case it's probably overall clearer to just explicitly call (labelled) break, return, or os.Exit in the loop.
When this question was asked this was a better answer for this specific scenario (little did I know this would be the #1 result when searching Google for "do while loop golang"). For answering this question generically please see #LinearZoetrope's answer below.
Wrap your function in a for loop:
package main
import (
"fmt"
"os"
)
func main() {
fmt.Println("Press 1 to run")
fmt.Println("Press 2 to exit")
for {
sample()
}
}
func sample() {
var input int
n, err := fmt.Scanln(&input)
if n < 1 || err != nil {
fmt.Println("invalid input")
return
}
switch input {
case 1:
fmt.Println("hi")
case 2:
os.Exit(2)
default:
fmt.Println("def")
}
}
A for loop without any declarations is equivalent to a while loop in other C-like languages. Check out the Effective Go documentation which covers the for loop.
The do...while in go can be this:
func main() {
var value int
for {
value++
fmt.Println(value)
if value%6 != 0 {
break
}
}
}
a while loop in Go can be as easy as this:
package main
import `fmt`
func main() {
for {
var number float64
fmt.Print(`insert an Integer eq or gr than 10!!!`)
fmt.Scanf(`%f`, &number)
if number >= 10 { break }
fmt.Println(`sorry the number is lower than 10....type again!!!`)
}
Conside to use "for-break" as "do-while".
foo.go
package main
import (
"fmt"
)
func main() {
i := 0
for {
i++
if i > 10 {
break
}
fmt.Printf("%v ", i)
}
fmt.Println()
}
shell
$ go run foo.go
1 2 3 4 5 6 7 8 9 10
Maybe not what you're looking for, but if you're trying to do something like this:
int i = 0;
while (i < 10) {
cout << "incrementing i now" << endl;
i++
}
cout << "done"
You'll have to do something like this in go:
var i = 0
fmt.Println(i)
for {
if i < 10 {
fmt.Println("incrementing i now")
i++
} else {
break
}
}
fmt.Println("done")
sum := 1
for sum < 1000 {
sum += sum
}
Explanation :
The basic for loop has three components separated by semicolons:
-the init statement: executed before the first iteration.
-the condition expression: evaluated before every iteration
-the post statement: executed at the end of every iteration
The init and post statements are optional.
So you can just put in the condition expression.
// While (CONDITION = true){
//code to execute ....}
//In go :
for CONDITION = true {
//code to execute}
This is one of the cleanest ways:
num := 10
for num > 0 {
// do stuff here
num--
}

Strip consecutive empty lines in a golang writer

I've got a Go text/template that renders a file, however I've found it difficult to structure the template cleanly while preserving the line breaks in the output.
I'd like to have additional, unnecessary newlines in the template to make it more readable, but strip them from the output. Any group of newlines more than a normal paragraph break should be condensed to a normal paragraph break, e.g.
lines with
too many breaks should become lines with
normal paragraph breaks.
The string is potentially too large to store safely in memory, so I want to keep it as an output stream.
My first attempt:
type condensingWriter struct {
writer io.Writer
lastLineIsEmpty bool
}
func (c condensingWriter) Write(b []byte) (n int, err error){
thisLineIsEmpty := strings.TrimSpace(string(b)) == ""
defer func(){
c.lastLineIsEmpty = thisLineIsEmpty
}()
if c.lastLineIsEmpty && thisLineIsEmpty{
return 0, nil
} else {
return c.writer.Write(b)
}
}
This doesn't work because I naively assumed that it would buffer on newline characters, but it doesn't.
Any suggestions on how to get this to work?
Inspired by zmb's approach, I've come up with the following package:
//Package striplines strips runs of consecutive empty lines from an output stream.
package striplines
import (
"io"
"strings"
)
// Striplines wraps an output stream, stripping runs of consecutive empty lines.
// You must call Flush before the output stream will be complete.
// Implements io.WriteCloser, Writer, Closer.
type Striplines struct {
Writer io.Writer
lastLine []byte
currentLine []byte
}
func (w *Striplines) Write(p []byte) (int, error) {
totalN := 0
s := string(p)
if !strings.Contains(s, "\n") {
w.currentLine = append(w.currentLine, p...)
return 0, nil
}
cur := string(append(w.currentLine, p...))
lastN := strings.LastIndex(cur, "\n")
s = cur[:lastN]
for _, line := range strings.Split(s, "\n") {
n, err := w.writeLn(line + "\n")
w.lastLine = []byte(line)
if err != nil {
return totalN, err
}
totalN += n
}
rem := cur[(lastN + 1):]
w.currentLine = []byte(rem)
return totalN, nil
}
// Close flushes the last of the output into the underlying writer.
func (w *Striplines) Close() error {
_, err := w.writeLn(string(w.currentLine))
return err
}
func (w *Striplines) writeLn(line string) (n int, err error) {
if strings.TrimSpace(string(w.lastLine)) == "" && strings.TrimSpace(line) == "" {
return 0, nil
} else {
return w.Writer.Write([]byte(line))
}
}
See it in action here: http://play.golang.org/p/t8BGPUMYhb
The general idea is you'll have to look for consecutive newlines anywhere in the input slice and if such cases exist, skip over all but the first newline character.
Additionally, you have to track whether the last byte written was a newline, so the next call to Write will know to eliminate a newline if necessary. You were on the right track by adding a bool to your writer type. However, you'll want to use a pointer receiver instead of a value receiver here, otherwise you'll be modifying a copy of the struct.
You would want to change
func (c condensingWriter) Write(b []byte)
to
func (c *condensingWriter) Write(b []byte)
You could try something like this. You'll have to test with larger inputs to make sure it handles all cases correctly.
package main
import (
"bytes"
"io"
"os"
)
var Newline byte = byte('\n')
type ReduceNewlinesWriter struct {
w io.Writer
lastByteNewline bool
}
func (r *ReduceNewlinesWriter) Write(b []byte) (int, error) {
// if the previous call to Write ended with a \n
// then we have to skip over any starting newlines here
i := 0
if r.lastByteNewline {
for i < len(b) && b[i] == Newline {
i++
}
b = b[i:]
}
r.lastByteNewline = b[len(b) - 1] == Newline
i = bytes.IndexByte(b, Newline)
if i == -1 {
// no newlines - just write the entire thing
return r.w.Write(b)
}
// write up to the newline
i++
n, err := r.w.Write(b[:i])
if err != nil {
return n, err
}
// skip over immediate newline and recurse
i++
for i < len(b) && b[i] == Newline {
i++
}
i--
m, err := r.Write(b[i:])
return n + m, nil
}
func main() {
r := ReduceNewlinesWriter{
w: os.Stdout,
}
io.WriteString(&r, "this\n\n\n\n\n\n\nhas\nmultiple\n\n\nnewline\n\n\n\ncharacters")
}

Is there a ReadLine equivalent for a file in Go?

I want to ReadBytes until "\n" for a text file, not a bufio.
Is there a way to do this without converting to a bufio?
There are many ways to do it, but wrapping with bufio is what I would suggest. But if that doesn't work for you (why not?), you can go ahead and read single bytes like this:
Full working example:
package main
import (
"bytes"
"fmt"
"io"
)
// ReadLine reads a line delimited by \n from the io.Reader
// Unlike bufio, it does so rather inefficiently by reading one byte at a time
func ReadLine(r io.Reader) (line []byte, err error) {
b := make([]byte, 1)
var l int
for err == nil {
l, err = r.Read(b)
if l > 0 {
if b[0] == '\n' {
return
}
line = append(line, b...)
}
}
return
}
var data = `Hello, world!
I will write
three lines.`
func main() {
b := bytes.NewBufferString(data)
for {
line, err := ReadLine(b)
fmt.Println("Line: ", string(line))
if err != nil {
return
}
}
}
Output:
Line: Hello, world!
Line: I will write
Line: three lines.
Playground: http://play.golang.org/p/dfb0GHPpnm

Resources