Go: fmt: Scanf works differently on Windows and Linux

Created on 26 Jan 2018  ·  20Comments  ·  Source: golang/go

What version of Go are you using (go version)?

go version go1.9.3 windows/amd64 and go version go1.9.3 linux/amd64

Does this issue reproduce with the latest release?

yes

What operating system and processor architecture are you using (go env)?

on Windows:

set GOARCH=amd64
set GOBIN=
set GOEXE=.exe
set GOHOSTARCH=amd64
set GOHOSTOS=windows
set GOOS=windows
set GOPATH=D:\golang
set GORACE=
set GOROOT=C:\Go
set GOTOOLDIR=C:\Go\pkg\tool\windows_amd64
set GCCGO=gccgo
set CC=gcc
set GOGCCFLAGS=-m64 -mthreads -fmessage-length=0 -fdebug-prefix-map=C:\Users\ANS_AS~1\AppData\Local\Temp\go-build251374523=/tmp/go-build -gno-record-gcc-switches
set CXX=g++
set CGO_ENABLED=1
set CGO_CFLAGS=-g -O2
set CGO_CPPFLAGS=
set CGO_CXXFLAGS=-g -O2
set CGO_FFLAGS=-g -O2
set CGO_LDFLAGS=-g -O2
set PKG_CONFIG=pkg-config

on Linux:

GOARCH="amd64"
GOBIN=""
GOEXE=""
GOHOSTARCH="amd64"
GOHOSTOS="linux"
GOOS="linux"
GOPATH="/home/ans_ashkan/go"
GORACE=""
GOROOT="/usr/local/go"
GOTOOLDIR="/usr/local/go/pkg/tool/linux_amd64"
GCCGO="gccgo"
CC="gcc"
GOGCCFLAGS="-fPIC -m64 -pthread -fmessage-length=0 -fdebug-prefix-map=/tmp/go-build871792404=/tmp/go-build -gno-record-gcc-switches"
CXX="g++"
CGO_ENABLED="1"
CGO_CFLAGS="-g -O2"
CGO_CPPFLAGS=""
CGO_CXXFLAGS="-g -O2"
CGO_FFLAGS="-g -O2"
CGO_LDFLAGS="-g -O2"
PKG_CONFIG="pkg-config"

What did you do?

package main

import "fmt"

func main() {
    var firstString string
    var secondString string

    fmt.Printf("Please enter first string:\n")
    fmt.Scanf("%s", &firstString)
    fmt.Printf("Please enter second string:\n")
    n, err := fmt.Scanf("%s", &secondString)
    fmt.Println(err)
}

What did you expect to see?

Same behavior on Windows and Linux.
Either it should capture secondString on Windows and Linux,
or reject it on both operating systems.

What did you see instead?

on Windows: unexpected newline.
on Linux: no errors.

NeedsFix OS-Windows

Most helpful comment

The scanning code in fmt is cursed. cc @rsc

All 20 comments

@ans-ashkan thank you for raising this issue.

I can reproduce what you see. I added some debugging:

package main

import (
    "fmt"
    "os"
)

type myReader struct{}

func (r myReader) Read(p []byte) (n int, err error) {
    n, err = os.Stdin.Read(p)
    fmt.Fprintf(os.Stderr, "DEBUG: %q %v\n", p[:n], err)
    return n, err
}

func main() {
    input := myReader{}
    var firstString string
    var secondString string
    fmt.Printf("Please enter first string:\n")
    fmt.Fscanf(input, "%s", &firstString)
    fmt.Printf("Please enter second string:\n")
    _, err := fmt.Fscanf(input, "%s", &secondString)
    fmt.Println(err)
}

if I run this program, I see:

C:\>u:\test
Please enter first string:
first
DEBUG: "f" <nil>
DEBUG: "i" <nil>
DEBUG: "r" <nil>
DEBUG: "s" <nil>
DEBUG: "t" <nil>
DEBUG: "\r" <nil>
Please enter second string:
DEBUG: "\n" <nil>
unexpected newline

C:\>

on Windows. Windows has \r\n as line delimiters, instead of \n on Linux. I am not sure what to do here. Leaving for others to decide.

Alex

The documentation of the package says: In all the scanning functions, a carriage return followed immediately by a newline is treated as a plain newline (\r\n means the same as \n).

I would think this is a bug but maybe a fmt expert should confirm. /cc @robpike @martisch

@rasky I'm confused, if the problem is \r\n then what is happening in this playground. (this is using Fscanf though that is what Scanf does under the hood)

The scanning code in fmt is cursed. cc @rsc

We all agree that the \n after \r should be eaten by the first Scanf.

fmt.Fscanf doesn't read newline so you should do:

https://play.golang.org/p/S15xHVSzUnn

@mattn , but fmt.Scanf does read new lines and the code for fmt.Scanf is:

func Scanf(format string, a ...interface{}) (n int, err error) {
    return Fscanf(os.Stdin, format, a...)
}

so fmt.Scanf calls fmt.Fscanf and it reads newlines.

Hmm, fare enough.

diff --git a/src/fmt/scan.go b/src/fmt/scan.go
index ae79e39dee..f3f5bb49f0 100644
--- a/src/fmt/scan.go
+++ b/src/fmt/scan.go
@@ -139,7 +139,7 @@ func Fscanln(r io.Reader, a ...interface{}) (n int, err error) {
 // returns the number of items successfully parsed.
 // Newlines in the input must match newlines in the format.
 func Fscanf(r io.Reader, format string, a ...interface{}) (n int, err error) {
-   s, old := newScanState(r, false, false)
+   s, old := newScanState(r, true, false)
    n, err = s.doScanf(format, a)
    s.free(old)
    return

Anyone know why this part is "true"?

Anyone know why this part is "true"?

Which branch/commit?, I don't see "true" there. this is what it looks like in my version:

func Fscanf(r io.Reader, format string, a ...interface{}) (n int, err error) {
    s, old := newScanState(r, false, false)
    n, err = s.doScanf(format, a)
    s.free(old)
    return
}

Sorry, it's my typo. I want to know why this is false?

Change https://golang.org/cl/110595 mentions this issue: fmt: Scanning newlines should work the same way on Windows and Linux

The test case used to open this issue, after CL https://golang.org/cl/110595 is applied,

i23562.go:

package main

import "fmt"

func main() {
    var firstString string
    var secondString string

    fmt.Printf("Please enter first string:\n")
    n, err := fmt.Scanf("%s", &firstString)
    fmt.Println(n, err, firstString)
    fmt.Printf("Please enter second string:\n")
    n, err = fmt.Scanf("%s", &secondString)
    fmt.Println(n, err, secondString)
}

/*

Linux:

$ go run i23562.go
Please enter first string:
first
1 <nil> first
Please enter second string:
second
1 <nil> second
$ 

Windows:

>go run i23562.go
Please enter first string:
first
1 <nil> first
Please enter second string:
second
1 <nil> second
>

*/

I've encountered a similar issue. The following code:

package main

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

func main() {
    const data = "1\n2\n"

    f, _ := ioutil.TempFile("", "strtest")
    defer os.Remove(f.Name())
    if _, err := f.WriteString(data); err != nil {
        panic(err.Error())
    }
    if _, err := f.Seek(0, 0); err != nil {
        panic(err.Error())
    }

    for i, r := range []io.Reader{strings.NewReader(data), f} {
        a, b := 0, 0
        fmt.Fscanf(r, "%d", &a)
        fmt.Fscanf(r, "\n")
        fmt.Fscanf(r, "%d", &b)
        fmt.Printf("Reader %d: %d %d\n", i, a, b)
    }
}

produces

Reader 0: 1 2
Reader 1: 1 0

, but when I comment out the fmt.Fscanf(r, "\n"), it flips:

Reader 0: 1 0
Reader 1: 1 2

I've tested this on go 1.10 linux/amd64 and on https://play.golang.org/ that it supposed to be running 1.11.

Hi, I just want to give another use case (using numbers). So Scanf gives no error on Linux but does not work on Windows. (Using go 1.11.4)

The code:

package main

import "fmt"

func main() {
    // Variables
    var firstNumber float64
    var secondNumber float64

    // First number
    fmt.Print("Please enter the first number: ")
    n1, err1 := fmt.Scanf("%f", &firstNumber)
    fmt.Println("Info: ", n1, err1) // debug info
    fmt.Printf("The first number is: %v \n", firstNumber)

    // Second number
    fmt.Print("Please enter the second number: ")
    n2, err2 := fmt.Scanf("%f", &secondNumber)
    fmt.Println("Info: ", n2, err2) // debug info
    fmt.Printf("The second number is: %v \n", secondNumber)
}

Linux output

Please enter the first number: 20
Info:  1 <nil>
The first number is: 20
Please enter the second number: 30
Info:  1 <nil>
The second number is: 30

Windows output
Here the program dosen't even let to write the number, just defaults to 0

Please enter the first number: 20
Info:  1 <nil>
The first number is: 20
Please enter the second number: Info:  0 unexpected newline
The second number is: 0

I encounters the same problem.

error-reproducible testing code: https://play.golang.org/p/s_VfYgM6ftj

The problem, I think, happens because fmt package works depending on io.Reader's underlying type.

I encounter the same problem. Programming language should support portability, and I think that fmt.Fscanf with CRLF or just LF should behave the same.

I'm using windows and vagrant (ubuntu 18.04 with go inside) and it's a pain in the ass, please fix.

This looks to becoming more of a common issue over the past few weeks, as we've seen an influx of newbies looking at Go due to quarantine. I've added a reply to the one outstanding comment on the change.

The tl;dr is while I could be visualizing the code incorrectly, I believe CL 110595 should be considered for merge. I've rebased the change on top of master locally, and can confirm I was able to build the Go toolchain and all tests passed.

@bcmills I see you were the last to make a status change on the CL. If we can resolve the comment from @griesemer, is there anything else blocking this change?

cc @rsc

Well, Hi i had a problem with scanf today
here is the code :
`package main

import (
"fmt"
)
//
func checkrow(n int, line int,column int, matrizSize int, matrix [100][100]int,r int) (int,bool){
if column!=0{
for c:=0;c if n == matrix[line][c] {
return r + 1, false
}
}
}
return r,true
}
func checkcolumn(n int,line int,column int,matrix [100][100]int,c int,matrizSize int) (int,bool){
if line!=0 {
for l := 0; l if n == matrix[l][column] {
return c + 1, false
}
}
}
return c,true
}

func main() {
var matrix [100][100]int
k:=0 //tracer (sum of main diagonal numbers)
c:=0 //number of columns that have repeated numbers
r:=0 //number of rows that contain repetead numbers
T:=0 // numbers of texts
row:=true
col:=true
var matrizSize int // size of matriz
fmt.Scanf("%d ",&T)
fmt.Scanf("%d",&matrizSize)
fmt.Scanf("%d","")
for i:=0; i for line := 0; line < matrizSize; line++ {//row
for column := 0; column < matrizSize; column++ {//column
fmt.Scanf("%d ", &matrix[line][column])
if column==line {
k = k + matrix[line][column]
}
if row==true {
r, row = checkrow(matrix[line][column], line,column, matrizSize, matrix,r)
}
if col==true {
c, col = checkcolumn(matrix[line][column],line, column, matrix, c, matrizSize)
}
}
col=true
row=true
}
fmt.Printf("Case n° %d: %d %d %d\n",i+1,k,r,c)

    i++
    if i<T-1 {

        fmt.Scanf("  %d", &matrizSize)
        fmt.Scanf("%d","")
    }
    k=0
    r=0
    c=0

}

}`
To resume i was getting the secound scanf, being satisfied by something, probably a "\n" from the first one, so the solution, was kinda similiar that i used with scanf on C give a space after and sometimes use a empty scanf. Is sad see that, as far i know, Go don't have a simple way, like a standard func, to clear the keyboard buffer, at least scanf should clear after receive a input, so it will make multiples scanf, run without a problem.

I encounter the same problem. My ugly workaround for windows is using "github.com/eiannone/keyboard". Hope the KeyEnter is the same on Linux.

func ScanKeyb() string {
    var input string
    for {
        char, key, err := keyboard.GetKey()
        if err != nil {
            panic(err)
        } else if key == keyboard.KeyEnter {
            fmt.Println()
            break
        }
        fmt.Printf("%c", char)
        input += fmt.Sprintf("%c", char)
    }
    return input
}

Hi , i used this to solve the problem:
fmt.Println("test1") fmt.Scanf("%s\n", &nr) fmt.Println("test2") fmt.Scanln(&n1)

the first scan i used scanf and the second i used scanln. I hope this helps

Was this page helpful?
0 / 5 - 0 ratings