iT邦幫忙

2019 iT 邦幫忙鐵人賽

DAY 7
1
Software Development

啥物碗Golang? 30天就Go系列 第 7

命令行參數

前面經過六天的基礎觀念建立,第七天我們來實際寫一些東西。go因為是編譯式語言,顧名思義,需要編譯過後才能執行,也因此無法提供像是ruby有的命令行環境(command line)。儘管如此,go還是可以做某種程度上的命令行互動,就是透過今天要講的命令行參數。透過在命令行加上參數,傳遞到go當中。

命令行參數

為了接受命令行的參數,我們需要使用os包,在程式一開頭引入進來:

package main

import (
    "fmt"
    "os"
)

func main() {
    var s, sep string
    for i := 1; i < len(os.Args); i++ {
        s += sep + os.Args[i]
        sep = ","
    }
    fmt.Println(s)
}

完成後,將檔案命名為cammand_line.go,接著我們就可測試效果:

$ go run cammand_line.go 1 2 3
1,2,3
$ go run cammand_line.go hello
hello

基本上就是把得到的參數印出來,如果參數不只一個,在中間加上逗號分隔。我們觀察一下for迴圈內,os.Args[i]透過索引i取得命令行參數,參數由1開始,0的位置是命令本身。我在第五天的時候有介紹過不同的For迴圈,我們來試試看用另外一種寫法:

for _, arg := range os.Args[1:] {
    s += sep + arg
    sep = ","
}

基本上可以跟原本的程式得到相同的結果。我們可以觀察到range會回傳兩個變數,這邊的範例因為沒有用到一個,所以使用_(blank identifier)來做拋棄,我們也可以把他印出來看看,其實他就是索引:

func main() {
    for i, arg := range os.Args[1:] {
        fmt.Println(i, arg)
    }
}

我把程式改寫為這樣,然後試著輸入多個參數:

$ go run cammand_line.go aa bb cc
0 aa
1 bb
2 cc

因為把印出放在迴圈內,所以作出換行的效果。如果只是想印出變數,不太在意印出的效果,也可以簡單寫為一行:

func main() {
	fmt.Println(os.Args[1:])
}

會得到去掉命令行本身的所有參數,放在一個Array裡面:

$ go run cammand_line.go 1 2 3
[1 2 3]

爬Html內容

既然能得到參數內容,代表我們可以做簡單的互動程式,比方說輸入網址,得到該網址的標題(Title)。前面有提過go先天對網路服務的支援很齊全,我們這次使用io/ioutilnet/http,範例如下:

package main

import (
    "fmt"
    "io/ioutil"
    "net/http"
    "os"
)

func main() {
    for _, url := range os.Args[1:] {
        resp, err := http.Get(url)
        if err != nil {
            fmt.Fprintf(os.Stderr, "fetch: %v\n", err)
            os.Exit(1)
        }
        b, err := ioutil.ReadAll(resp.Body)
        resp.Body.Close()
        if err != nil {
            fmt.Fprintf(os.Stderr, "fetch: reading %s: %v\n", url, err)
            os.Exit(1)
        }
        fmt.Printf("%s", b)
    }
}

將檔案命名為parse.go,我們就可以使用:

$ go run parse.go http://google.com
// 返回整包html檔案

接下來我們只要改寫一下,從整包html當中找出title,然後印出來。

package main

import (
	"fmt"
	"io/ioutil"
	"net/http"
	"os"
	"strings"
)

func main() {
	for _, url := range os.Args[1:] {
		resp, err := http.Get(url)
		if err != nil {
			fmt.Fprintf(os.Stderr, "fetch: %v\n", err)
			os.Exit(1)
		}
		b, err := ioutil.ReadAll(resp.Body)
		resp.Body.Close()
		if err != nil {
			fmt.Fprintf(os.Stderr, "fetch: reading %s: %v\n", url, err)
			os.Exit(1)
		}
		s := string(b)
		first_index := strings.Index(s, "<title>") + 7
		last_index := strings.Index(s, "</title>")
		fmt.Println(s[first_index:last_index])
	}
}

resp.Body經過ReadAll得到的是byte[],需要先轉為字串,接著透過字串的index去把網站標題的索引位置找出來。在html開頭的地方需要加上<title>本身的長度,不然這段也會跟著被印出來。取得頭尾後,再透過之前講過的字串方法取得字串中的特定值,我們來看一下實際操作:

$ go run parse.go http://google.com
Google
$ go run parse.go https://ithelp.ithome.com.tw/users/20103651
 iT 邦幫忙::一起幫忙解決難題,拯救 IT 人的一天

恩,非常完美。當然這段程式碼或許有可以改進的地方,第七天就以標題做個結尾吧!基本上能爬出標題,網頁上的靜態內容都可以取得,動態的話可能要另外下一番工夫。

我把程式放上github了,有需要的朋友可以直接pull下來玩玩看。

Reference


上一篇
函數 Function
下一篇
Web Server
系列文
啥物碗Golang? 30天就Go30

尚未有邦友留言

立即登入留言