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 · 15 comments
Milestone

Comments

@ans-ashkan
Copy link

@ans-ashkan ans-ashkan commented Jan 26, 2018

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

This comment has been minimized.

Copy link
Member

@alexbrainman alexbrainman commented Jan 27, 2018

@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

This comment has been minimized.

Copy link
Member

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

This comment has been minimized.

Copy link
Author

@ans-ashkan ans-ashkan commented Jan 27, 2018

@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

This comment has been minimized.

Copy link
Contributor

@robpike robpike commented Jan 27, 2018

The scanning code in fmt is cursed. cc @rsc

@rsc rsc added the NeedsFix label Jan 29, 2018
@rsc

This comment has been minimized.

Copy link
Contributor

@rsc rsc commented Jan 29, 2018

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

@mattn

This comment has been minimized.

Copy link
Member

@mattn mattn commented Mar 20, 2018

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

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

@ans-ashkan

This comment has been minimized.

Copy link
Author

@ans-ashkan 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

This comment has been minimized.

Copy link
Member

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

This comment has been minimized.

Copy link
Author

@ans-ashkan ans-ashkan commented Mar 20, 2018

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

This comment has been minimized.

Copy link
Member

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

This comment has been minimized.

Copy link

@gopherbot gopherbot commented May 1, 2018

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

@peterGo

This comment has been minimized.

Copy link
Contributor

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

This comment has been minimized.

Copy link

@stefan-sakalik stefan-sakalik commented Dec 19, 2018

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

This comment has been minimized.

Copy link

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

This comment has been minimized.

Copy link

@pubblic 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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
You can’t perform that action at this time.