Go管道教程展示了如何在Golang中使用管道。
管道
管道是一种从一个进程重定向到另一个进程的形式。它是一个单向数据通道,可用于进程间通信。
io.Pipe
函数创建一个同步内存管道。它可用于连接需要io.Reader
的代码和需要io.Writer
的代码。
$ go version go version go1.18.1 linux/amd64
我们使用Go版本1.18。
Go管道简单示例
以下示例演示了io.Pipe
函数的用法。
package main import ( "fmt" "io" "log" "os" ) func main() { r, w := io.Pipe() go func() { fmt.Fprint(w, "Hello there\n") w.Close() }() _, err := io.Copy(os.Stdout, r) if err != nil { log.Fatal(err) } }
在代码示例中,我们使用io.Pipe
创建了一个管道。我们在goroutine中写入管道的写入器,然后使用io.Copy
将数据从管道的读取器复制到标准输出。
go func() { fmt.Fprint(w, "Hello there\n") w.Close() }()
我们在goroutine中将一些数据写入管道的writer。对PipeWriter
的每次写入都会阻塞,直到它满足来自PipeReader
的一次或多次读取,这些读取完全消耗了写入的数据。
$ go run simple.go Hello there
转到命令标准输出管道
命令的StdoutPipe
返回一个管道,该管道将在命令启动时连接到命令的标准输出。
package main import ( "bufio" "fmt" "log" "os" "os/exec" ) func main() { cmd := exec.Command("ping", "webcode.me") stdout, err := cmd.StdoutPipe() if err != nil { log.Fatal(err) } cmd.Start() buf := bufio.NewReader(stdout) num := 0 for { line, _, _ := buf.ReadLine() if num > 3 { os.Exit(0) } num += 1 fmt.Println(string(line)) } }
在代码示例中,我们启动了一个ping
命令并读取了它的四行输出。
cmd := exec.Command("ping", "webcode.me")
我们创建一个启动ping
的命令来测试webcode.me
网站的可用性。
stdout, err := cmd.StdoutPipe()
我们得到命令的标准输出。
buf := bufio.NewReader(stdout)
创建了标准输出的阅读器。
for { line, _, _ := buf.ReadLine() if num > 3 { os.Exit(0) } num += 1 fmt.Println(string(line)) }
在for循环中,我们读取四行并将它们打印到控制台。
$ go run pingcmd.go PING webcode.me (46.101.248.126) 56(84) bytes of data. 64 bytes from 46.101.248.126 (46.101.248.126): icmp_seq=1 ttl=54 time=29.7 ms 64 bytes from 46.101.248.126 (46.101.248.126): icmp_seq=2 ttl=54 time=35.9 ms 64 bytes from 46.101.248.126 (46.101.248.126): icmp_seq=3 ttl=54 time=37.4 ms
通过管道传输POSTJSON数据
在下面的示例中,我们将JSON数据发布到https://httpbin.org/post
。
package main import ( "encoding/json" "fmt" "io" "io/ioutil" "log" "net/http" ) type PayLoad struct { Content string } func main() { r, w := io.Pipe() go func() { defer w.Close() err := json.NewEncoder(w).Encode(&PayLoad{Content: "Hello there!"}) if err != nil { log.Fatal(err) } }() resp, err := http.Post("https://httpbin.org/post", "application/json", r) if err != nil { log.Fatal(err) } body, err := ioutil.ReadAll(resp.Body) if err != nil { log.Fatal(err) } fmt.Println(string(body)) }
该示例向网站发布JSON负载并读取其正文响应。
go func() { defer w.Close() err := json.NewEncoder(w).Encode(&PayLoad{Content: "Hello there!"}) if err != nil { log.Fatal(err) } }()
我们在goroutine中将JSON负载写入PipeWriter
。
resp, err := http.Post("https://httpbin.org/post", "application/json", r)
http.Post
方法需要一个读者作为它的第三个参数;我们在那里传递了一个PipeReader
。
body, err := ioutil.ReadAll(resp.Body) if err != nil { log.Fatal(err) } fmt.Println(string(body))
最后,我们读取响应体并打印它。
$ go run post_json.go { "args": {}, "data": "{\"Content\":\"Hello there!\"}\n", "files": {}, "form": {}, "headers": { ...
通过管道读取标准输入
以下示例创建了一个Go程序,它读取通过管道从标准输入传递过来的数据。
package main import ( "bufio" "fmt" "io" "log" "os" ) func main() { nBytes, nChunks := int64(0), int64(0) r := bufio.NewReader(os.Stdin) buf := make([]byte, 0, 4*1024) for { n, err := r.Read(buf[:cap(buf)]) buf = buf[:n] if n == 0 { if err == nil { continue } if err == io.EOF { break } log.Fatal(err) } nChunks++ nBytes += int64(len(buf)) fmt.Println(string(buf)) if err != nil && err != io.EOF { log.Fatal(err) } } fmt.Println("Bytes:", nBytes, "Chunks:", nChunks) }
该示例从标准输入读取数据并打印数据以及读取的字节数和块数。
r := bufio.NewReader(os.Stdin)
我们从标准输入创建一个阅读器。
buf := make([]byte, 0, 4*1024)
这里我们创建一个4KB的缓冲区。
n, err := r.Read(buf[:cap(buf)]) buf = buf[:n]
我们从标准输入读取数据到缓冲区。
nChunks++ nBytes += int64(len(buf))
我们计算读取的块数和字节数。
fmt.Println(string(buf))
我们将缓冲区的内容打印到终端。
$ date | go run read_stdin.go Sun 15 Nov 2020 01:08:13 PM CET Bytes: 32 Chunks: 1
我们将|
运算符的输出传递给我们的程序。Go程序读取数据,计算字节数并将数据打印到控制台。
统计
Stat
函数返回描述文件的FileInfo结构。我们可以用它来检查终端上是否有来自管道的数据。
package main import ( "bufio" "fmt" "log" "os" ) func main() { stat, _ := os.Stdin.Stat() if (stat.Mode() & os.ModeCharDevice) == 0 { var buf []byte scanner := bufio.NewScanner(os.Stdin) for scanner.Scan() { buf = append(buf, scanner.Bytes()...) } if err := scanner.Err(); err != nil { log.Fatal(err) } fmt.Printf("Hello %s!\n", buf) } else { fmt.Print("Enter your name: ") var name string fmt.Scanf("%s", &name) fmt.Printf("Hello %s!\n", name) } }
该示例通过管道或提示获取用户的输入。
stat, _ := os.Stdin.Stat()
我们得到了标准输入的FileInfo结构。
if (stat.Mode() & os.ModeCharDevice) == 0 {
我们检查输入数据是否来自管道。
var buf []byte scanner := bufio.NewScanner(os.Stdin) for scanner.Scan() { buf = append(buf, scanner.Bytes()...) }
我们读取通过终端上的|
管道运算符传递的数据。
} else { fmt.Print("Enter your name: ") var name string fmt.Scanf("%s", &name) fmt.Printf("Hello %s!\n", name) }
如果管道中没有数据,我们从提示中读取数据。
$ echo "Peter" | go run hello.go Hello Peter! $ go run hello.go Enter your name: Peter Hello Peter!
首先,我们通过管道将数据传递给程序,然后通过提示。
在HTTP处理程序中使用管道
在下面的示例中,我们在HTTP处理程序中使用管道。
package main import ( "fmt" "io" "net/http" "os/exec" ) func handler(w http.ResponseWriter, r *http.Request) { cmd := exec.Command("date") pr, pw := io.Pipe() defer pw.Close() cmd.Stdout = pw cmd.Stderr = pw go io.Copy(w, pr) cmd.Run() } func main() { http.HandleFunc("/", handler) fmt.Println("server started on port 8080") http.ListenAndServe(":8080", nil) }
该示例启动了一个简单的服务器,它返回今天的日期。
cmd := exec.Command("date")
我们将执行日期命令。
pr, pw := io.Pipe() defer pw.Close()
我们创建一个Go管道。
cmd.Stdout = pw cmd.Stderr = pw
我们将PipeWriter
传递给命令的标准输出和标准错误输出。
go io.Copy(w, pr)
在协程中,我们将PipeReader
的内容复制到http.ResponseWriter
。
cmd.Run()
命令通过Run
执行。
$ go run handler.go server started on port 8080
我们运行服务器。
$ curl localhost:8080 Sun 15 Nov 2020 02:18:07 PM CET
在不同的终端窗口中,我们使用curl
创建一个GET请求。
在本教程中,我们使用了Go中的管道。
列出所有Go教程。