Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fmt: Scanf works differently on Windows and Linux #23562

Open
ans-ashkan opened this issue Jan 26, 2018 · 23 comments
Open

fmt: Scanf works differently on Windows and Linux #23562

ans-ashkan opened this issue Jan 26, 2018 · 23 comments
Labels
NeedsFix The path to resolution is known, but the work has not been done. OS-Windows
Milestone

Comments

@ans-ashkan
Copy link

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.

@ans-ashkan ans-ashkan changed the title Different code behavior between Windows and Linux Different code behavior on Windows and Linux Jan 26, 2018
@alexbrainman alexbrainman changed the title Different code behavior on Windows and Linux fmt: Scanf works differently on Windows and Linux Jan 27, 2018
@alexbrainman
Copy link
Member

@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

@rasky rasky added OS-Windows NeedsDecision Feedback is required from experts, contributors, and/or the community before a change can be made. labels Jan 27, 2018
@rasky
Copy link
Member

rasky commented Jan 27, 2018

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

@ans-ashkan
Copy link
Author

@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)

@robpike
Copy link
Contributor

robpike commented Jan 27, 2018

The scanning code in fmt is cursed. cc @rsc

@rsc rsc added the NeedsFix The path to resolution is known, but the work has not been done. label Jan 29, 2018
@gopherbot gopherbot removed the NeedsDecision Feedback is required from experts, contributors, and/or the community before a change can be made. label Jan 29, 2018
@rsc
Copy link
Contributor

rsc commented Jan 29, 2018

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

@mattn
Copy link
Member

mattn commented Mar 20, 2018

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

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

@ans-ashkan
Copy link
Author

ans-ashkan commented Mar 20, 2018

@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.

@mattn
Copy link
Member

mattn commented Mar 20, 2018

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"?

@ans-ashkan
Copy link
Author

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
}

@mattn
Copy link
Member

mattn commented Mar 20, 2018

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

@andybons andybons added this to the Go1.11 milestone Mar 26, 2018
@gopherbot
Copy link

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

@peterGo
Copy link
Contributor

peterGo commented May 1, 2018

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
>

*/

@ianlancetaylor ianlancetaylor modified the milestones: Go1.11, Go1.12 Jun 29, 2018
@ianlancetaylor ianlancetaylor modified the milestones: Go1.12, Go1.13 Dec 12, 2018
@stefan-sakalik
Copy link

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.

@yaazkal
Copy link

yaazkal commented Jan 4, 2019

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

@andybons andybons modified the milestones: Go1.13, Go1.14 Jul 8, 2019
@pubblic
Copy link

pubblic commented Jul 14, 2019

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.

@rsc rsc modified the milestones: Go1.14, Backlog Oct 9, 2019
@0xd114
Copy link

0xd114 commented Mar 3, 2020

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.

@theckman
Copy link
Contributor

theckman commented Apr 4, 2020

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

@ancogamer
Copy link

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<column;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 <line; 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<T; {//test
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.

@jspille
Copy link

jspille commented Apr 30, 2020

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
}

@Safouene1
Copy link

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

@gopherbot
Copy link

Change https://golang.org/cl/375214 mentions this issue: fmt: treat \r\n as \n for Scanf on windows

@iwdgo
Copy link
Contributor

iwdgo commented Jan 6, 2022

After spending some time on a possible fix, the behavior of Scanf() seems consistent. Using the same case, adding a blank after the input gets the same error on macOS and no more on windows.

Documentation of Scanf states:

Newlines in the input must match newlines in the format

The suggested case uses format "%s" which does not end with a newline. Using "%s\n" gets the same behavior on windows and macos. When the newline is expected, any "white space" preceding the '\n', including '\r' on windows or blank space on macOS is collapsed in the newline ending the input and the format in accordance with the documentation in the code which states:

// Spaces in the format before the newline are collapsed into the newline.

As mentioned in some comments, the input buffer is not cleared but read according to format which may exhaust before the input without contradiction.

The current behavior is necessary to read an input stream using multiple Scanf() as in the example. The error is produced because the '\n' is not consumed by the first Scanf().

@gopherbot
Copy link

Change https://go.dev/cl/506016 mentions this issue: fmt: ignore \r when input before \n

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
NeedsFix The path to resolution is known, but the work has not been done. OS-Windows
Projects
None yet
Development

No branches or pull requests