Go 每日一庫之 go-homedir

熱烈歡迎你,相識是一種緣分,Echa 哥為了你的到來特意準備了一份驚喜,go學習資料《 》


Go 每日一庫之 go-homedir


簡介

今天我們來看一個很小,很實用的庫go-homedir。顧名思義,go-homedir用來獲取用戶的主目錄。 實際上,使用標準庫os/user我們也可以得到這個信息:

<code>package main

import (
"fmt"
"log"
"os/user"
)

func main() {
u, err := user.Current()
if err != nil {
log.Fatal(err)
}

fmt.Println("Home dir:", u.HomeDir)
}
複製代碼/<code>

那麼為什麼還要go-homedir庫?

在 Darwin 系統上,標準庫os/user的使用需要 cgo。所以,任何使用os/user的代碼都不能交叉編譯。 但是,大多數人使用os/user的目的僅僅只是想獲取主目錄。因此,go-homedir庫出現了。

快速使用

go-homedir是第三方包,使用前需要先安裝:

<code>$ go get github.com/mitchellh/go-homedir
複製代碼/<code>

使用非常簡單:

<code>package main

import (
"fmt"
"log"

"github.com/mitchellh/go-homedir"
)

func main() {
dir, err := homedir.Dir()
if err != nil {
log.Fatal(err)
}

fmt.Println("Home dir:", dir)

dir = "~/golang/src"
expandedDir, err := homedir.Expand(dir)
if err != nil {
log.Fatal(err)
}

fmt.Printf("Expand of %s is: %s\\n", dir, expandedDir)
}
複製代碼/<code>

go-homedir有兩個功能:

  • Dir:獲取用戶主目錄;
  • Expand:將路徑中的第一個~擴展成用戶主目錄。

高級用法

由於Dir的調用可能涉及一些系統調用和外部執行命令,多次調用費性能。所以go-homedir提供了緩存的功能。默認情況下,緩存是開啟的。 我們也可以將DisableCache設置為false來關閉它。

<code>package main

import (
"fmt"
"log"

"github.com/mitchellh/go-homedir"
)

func main() {
homedir.DisableCache = false

dir, err := homedir.Dir()
if err != nil {
log.Fatal(err)
}

fmt.Println("Home dir:", dir)
}
複製代碼/<code>

使用緩存時,如果程序運行中修改了主目錄,再次調用Dir還是返回之前的目錄。如果需要獲取最新的主目錄,可以先調用Reset清除緩存。

實現

go-homedir源碼只有一個文件homedir.go,今天我們大概看一下Dir的實現,去掉緩存相關代碼:

<code>func Dir() (string, error) {
var result string
var err error
if runtime.GOOS == "windows" {
result, err = dirWindows()
} else {
// Unix-like system, so just assume Unix
result, err = dirUnix()
}

if err != nil {
return "", err
}

return result, nil
}
複製代碼/<code>

判斷當前的系統是windows還是類 Unix,分別調用不同的方法。先看 windows 的,比較簡單:

<code>func dirWindows() (string, error) {
\t// First prefer the HOME environmental variable
\tif home := os.Getenv("HOME"); home != "" {
\t\treturn home, nil
\t}

\t// Prefer standard environment variable USERPROFILE
\tif home := os.Getenv("USERPROFILE"); home != "" {
\t\treturn home, nil
\t}

\tdrive := os.Getenv("HOMEDRIVE")
\tpath := os.Getenv("HOMEPATH")
\thome := drive + path
\tif drive == "" || path == "" {
\t\treturn "", errors.New("HOMEDRIVE, HOMEPATH, or USERPROFILE are blank")
\t}

\treturn home, nil
}
複製代碼/<code>

流程如下:

  • 讀取環境變量HOME,如果不為空,返回這個值;
  • 讀取環境變量USERPROFILE,如果不為空,返回這個值;
  • 讀取環境變量HOMEDRIVE和HOMEPATH,如果兩者都不為空,拼接這兩個值返回。

類 Unix 系統的實現稍微複雜一點:

<code>func dirUnix() (string, error) {
homeEnv := "HOME"
if runtime.GOOS == "plan9" {
// On plan9, env vars are lowercase.
homeEnv = "home"
}

// First prefer the HOME environmental variable
if home := os.Getenv(homeEnv); home != "" {
return home, nil
}

var stdout bytes.Buffer

// If that fails, try OS specific commands
if runtime.GOOS == "darwin" {
cmd := exec.Command("sh", "-c", `dscl -q . -read /Users/"$(whoami)" NFSHomeDirectory | sed 's/^[^ ]*: //'`)
cmd.Stdout = &stdout
if err := cmd.Run(); err == nil {
result := strings.TrimSpace(stdout.String())
if result != "" {
return result, nil
}
}
} else {
cmd := exec.Command("getent", "passwd", strconv.Itoa(os.Getuid()))
cmd.Stdout = &stdout
if err := cmd.Run(); err != nil {
// If the error is ErrNotFound, we ignore it. Otherwise, return it.
if err != exec.ErrNotFound {
return "", err
}
} else {
if passwd := strings.TrimSpace(stdout.String()); passwd != "" {
// username:password:uid:gid:gecos:home:shell
passwdParts := strings.SplitN(passwd, ":", 7)
if len(passwdParts) > 5 {
return passwdParts[5], nil
}
}
}
}

// If all else fails, try the shell
stdout.Reset()
cmd := exec.Command("sh", "-c", "cd && pwd")
cmd.Stdout = &stdout
if err := cmd.Run(); err != nil {
return "", err

}

result := strings.TrimSpace(stdout.String())
if result == "" {
return "", errors.New("blank output when reading home directory")
}

return result, nil
}
複製代碼/<code>

流程如下:

  • 先讀取環境變量HOME(注意 plan9 系統上為home),如果不為空,返回這個值;
  • 使用getnet命令查看系統的數據庫中的相關記錄,我們知道passwd文件中存儲了用戶信息,包括用戶的主目錄。使用getent命令查看passwd中當前用戶的那條記錄,然後從中找到主目錄部分返回;
  • 如果上一個步驟失敗了,我們知道cd後不加參數是直接切換到用戶主目錄的,而pwd可以顯示當前目錄。那麼就可以結合這兩個命令返回主目錄。

這裡分析源碼並不是表示使用任何庫都要熟悉它的源碼,畢竟使用庫就是為了方便開發。 但是源碼是我們學習和提高的一個非常重要的途徑。我們在使用庫遇到問題的時候也要有能力從文檔或甚至源碼中查找原因。

參考

  1. home-dir GitHub 倉庫


分享到:


相關文章: