Golang中 println 与 fmt.Println 有什么区别
前言
在之前写的一个命令行工具,其中一个功能点是选择某个项目,并切换至该项目目录 (对应CLI的lab cs命令)。因为程序中不能直接修改shell当前路径,只好在命令执行后输出内容,再与shell内置命令搭配使用来实现。
伪代码如下:
1 | package main |
正常情况下,执行cd $(go run main.go)
后,shell路径会切换到/usr/local/bin
。
但在一次修改中,年轻的我以为println
与fmt.Println
的输出是等价的,都是写入标准输出,但println
是内置函数,不需要额外导入fmt包,就把项目中使用fmt.Println
的地方都替换成println
,从而埋下祸根。
今早在本地更新了该命令行后,发现lab cs
命令不好使了,虽然能在终端下看到该命令输出路径,但结合cd使用却没有效果,然而前一个版本,没问题的,间接定位到BUG是到改println的pr引入的。
1.输出位置不同
经紫月提醒,发现使用cd $(lab cs 2&>1)
能正确切换路径了,那么原因很明确了,println
把内容输出到标准错误中了。
查看fmt.Println
的源码,注释和代码中都很明显:writes to standard output,内容输出到了os.Stdout,也就是标准输出。
1 | // Println formats using the default formats for its operands and writes to standard output. |
再来看看println
,因为是内置函数,这里只写了函数说明,不过在注释中,可以找到这句:writes the result to standard error。说明println/print
确实是输出到stderr,也就是标准错误输出。
1 | // The println built-in function formats its arguments in an |
我们可以在go源码的 runtime/print.go
找到println
的具体实现。代码如下:
1 | func printnl() { |
在gwrite
函数中,这里主要看下writeErr
1 | func writeErr(b []byte) { |
可以看到writeErr传递了unitptr为2的fd参数,最终在write1处进行系统调用。而标准错误输出的文件描述符刚好是2。
在unix中,stdin,stdout,stderr三者对应三个文件描述符,分别是0,1,2
2.函数定义不同
平时都是直接使用println("?")
或 fmt.Println("?")
,不过两者的函数定义也有些不同。
1 | // println |
虽然两者都接受多个传参,但只有fmt.Println
是具有返回值的,其中第一位返回输出内容的字节数,对于Println
还需要加上末尾换行符\n
的一字节。
除此之外,println/print
不接受数组和结构体参数。
3.输出格式不同
如果实参实现了String()或Error()
方法时,在调用fmt.Println
打印该参数时,会调用这两个方法,而内置的println/print
则不会使用。