Go 每日一庫之 go-homedir
阿新 • • 發佈:2020-01-15
簡介
今天我們來看一個很小,很實用的庫go-homedir。顧名思義,go-homedir
用來獲取使用者的主目錄。
實際上,使用標準庫os/user
我們也可以得到這個資訊:
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)
}
那麼為什麼還要go-homedir
庫?
在 Darwin 系統上,標準庫os/user
os/user
的程式碼都不能交叉編譯。但是,大多數人使用
os/user
的目的僅僅只是想獲取主目錄。因此,go-homedir
庫出現了。
快速使用
go-homedir
是第三方包,使用前需要先安裝:
$ go get github.com/mitchellh/go-homedir
使用非常簡單:
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) }
go-homedir
有兩個功能:
Dir
:獲取使用者主目錄;Expand
:將路徑中的第一個~
擴充套件成使用者主目錄。
高階用法
由於Dir
的呼叫可能涉及一些系統呼叫和外部執行命令,多次呼叫費效能。所以go-homedir
提供了快取的功能。預設情況下,快取是開啟的。
我們也可以將DisableCache
設定為false
來關閉它。
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) }
使用快取時,如果程式執行中修改了主目錄,再次呼叫Dir
還是返回之前的目錄。如果需要獲取最新的主目錄,可以先呼叫Reset
清除快取。
實現
go-homedir
原始碼只有一個檔案homedir.go,今天我們大概看一下Dir
的實現,去掉快取相關程式碼:
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
}
判斷當前的系統是windows
還是類 Unix,分別呼叫不同的方法。先看 windows 的,比較簡單:
func dirWindows() (string, error) {
// First prefer the HOME environmental variable
if home := os.Getenv("HOME"); home != "" {
return home, nil
}
// Prefer standard environment variable USERPROFILE
if home := os.Getenv("USERPROFILE"); home != "" {
return home, nil
}
drive := os.Getenv("HOMEDRIVE")
path := os.Getenv("HOMEPATH")
home := drive + path
if drive == "" || path == "" {
return "", errors.New("HOMEDRIVE, HOMEPATH, or USERPROFILE are blank")
}
return home, nil
}
流程如下:
- 讀取環境變數
HOME
,如果不為空,返回這個值; - 讀取環境變數
USERPROFILE
,如果不為空,返回這個值; - 讀取環境變數
HOMEDRIVE
和HOMEPATH
,如果兩者都不為空,拼接這兩個值返回。
類 Unix 系統的實現稍微複雜一點:
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
}
流程如下:
- 先讀取環境變數
HOME
(注意 plan9 系統上為home
),如果不為空,返回這個值; - 使用
getnet
命令檢視系統的資料庫中的相關記錄,我們知道passwd
檔案中儲存了使用者資訊,包括使用者的主目錄。使用getent
命令檢視passwd
中當前使用者的那條記錄,然後從中找到主目錄部分返回; - 如果上一個步驟失敗了,我們知道
cd
後不加引數是直接切換到使用者主目錄的,而pwd
可以顯示當前目錄。那麼就可以結合這兩個命令返回主目錄。
這裡分析原始碼並不是表示使用任何庫都要熟悉它的原始碼,畢竟使用庫就是為了方便開發。
但是原始碼是我們學習和提高的一個非常重要的途徑。我們在使用庫遇到問題的時候也要有能力從文件或甚至原始碼中查詢原因。
參考
- home-dir GitHub 倉庫
我
我的部落格
歡迎關注我的微信公眾號【GoUpUp】,共同學習,一起進步~
本文由部落格一文多發平臺 OpenWrite 釋出!