在 Go 中,从文件读取(或写入)是非常容易的。程序只需要使用 os 包就可以从文件
/etc/passwd 中读取数据。
// 从文件读取(无缓冲)
package main
import "os"
func main() {
buf := make([]byte, 1024)
f, _ := os.Open("/etc/passwd") // 打开文件,os.Open 返回一个实现了 io.Reader 和 io.Writer 的 *os.File;
de fer f.Close() // 确保关闭了 f;
for {
n, _ := f.Read(buf) // 一次读取 1024 字节;
if n == 0 { break } // 到达文件末尾;
os.Stdout.Write(buf[:n]) // 将内容写入 os.Stdout
}
}
如果想要使用缓冲 IO,则有 bufio 包:
package main
import ( "os" ; "bufio")
func main() {
io.Reader 91
buf := make([]byte, 1024)
f, _ := os.Open("/etc/passwd") // 打开文件;
de fer f.Close()
r := bufio.NewReader(f) // 转换 f 为有缓冲的 Reader
w := bufio.NewWriter(os.Stdout)
defer w.Flush()
for {
n, _ := r.Read(buf) // 从 Reader 读取,而向 Writer 写入,然后向屏幕输出文件
if n == 0 { break }
w.Write(buf[0:n])
}
}
io.Reader
在前面已经提到 io.Reader 接口对于 Go 语言来说非常重要。许多函数需要通过 io.Reader 读取一些数据作为输入。为了满足这个接口,只需要实现一个方法:Read(p []byte) (n int, err error)。写入则是(你可能已经猜到了)实现了 Write 方法的 io.Writer。如果你让自己的程序或者包中的类型实现了io.Reader 或者 io.Writer 接口,那么整个 Go 标准库都可以使用这个类型.
行读取
f, _ := os.Open("/etc/passwd") ; de fer f.Close()
r := bufio.NewReader(f) // 使其成为一个 bufio,以便访问 ReadString 方法
s, ok := r.ReadString('\n') { // 从输入中读取一行
// ... s 保存了字符串,通过 string 包就可以解析它
更加通用(但是也更加复杂)的方法是 ReadLine,可以参阅包 bufio 的文档了解更多内容。
在 shell 脚本中通常遇到的场景是需要检查某个目录是否存在,如果不存在,就创建一
个。我们可以用go实现一下:
if f, e := os.Stat("name") ; e !=nil {
os.Mkdir("name", 0755)
} else {
// error
}
这个例子展示了 Go 拥有的 “脚本” 化特性,用 Go 编写程序感觉上类似使用动态语言(Python、Ruby、Perl 或者 PHP)。
命令行参数
来自命令行的参数在程序中通过字符串 slice os.Args 获取,导入包 os 即可。flag 包有
着精巧的接口,同样提供了解析标识的方法。这个例子是一个 DNS 查询工具:
dnssec := flag.Bool("dnssec", false, "Request DNSSEC records") // 定义 bool 标识,-dnssec。变量必须是指针,否则 package 无法设置其值
port := flag.String("port", "53", "Set the query port") // 类似的,port 选项
flag.Usage = func() { // 简单的重定义 Usage 函数,有点罗嗦;
fmt.Fprintf(os.Stderr, "Usage: %s [OPTIONS] [name ...]\n", os.
Args[0])
flag.PrintDefaults() // 指定的每个标识,PrintDefaults 将输出帮助信息
}
flag.Parse() // 解析标识,并填充变量
// 当参数被解析之后,就可以被使用:
if *dnssec { // 定义传入参数 dnssec
// 做点啥
}
命令执行
os/exec 包有函数可以执行外部命令,这也是在 Go 中主要的执行命令的方法。通过定
义一个有着数个方法的 *exec.Cmd 结构来使用。
执行ls -l的例子:
// 不对返回的数据进行处理
import "os/exec"
cmd := exec.Command("/bin/ls", "-l")
err := cmd.Run()
// 从命令行的标准输出中获得信息
import "exec"
cmd := exec.Command("/bin/ls", "-l")
buf, err := cmd.Output() // buf 是一个 []byte
网络
所有网络相关的类型和函数可以在 net 包中找到。其中最重要的函数是 Dial。当Dial 到远程系统,这个函数返回 Conn 接口类型,可以用于发送或接收信息。函数Dial 简单的抽象了网络层和传输层。因此 IPv4 或者 IPv6,TCP 或者 UDP 可以共用一个接口。
通过 TCP 连接到远程系统(端口 80),然后是 UDP,最后是 TCP 通过 IPv6,大致是这
样:
conn, e := Dial("tcp", "192.0.32.10:80")
conn, e := Dial("udp", "192.0.32.10:80")
conn, e := Dial("tcp", "[2620:0:2d0:200::10]:80") // 方括号是强制的
如果没有错误(由 e 返回),就可以使用 conn 从套接字中读写。在包 net 中的原始定
义是:
Read(b []byte)(n int, err error) // 使conn成为了io.Reader
Write(b []byte)(n int, err error) // 使 conn 成为了 io.Writer
这都是些比较低层的东西,我们一般用的使更高层的包,比如http:
package main
import ( "io/ioutil" ; "net/http" ; "fmt" )
func main() {
r, err := http.Get("http://www.google.com/robots.txt") // 使用 http 的 Get 获取 html
if err != nil { fmt.Printf("%s\n", err.String()) ; return } //错误处理
b, err := ioutil.ReadAll(r.Body) // 将整个内容读入 b
r.Body.Close()
if err == nil { fmt.Printf("%s", s t ri n g(b)) } // 如果一切 OK ,打印内容