今天咱們來看一個很小,很實用的庫go-homedir。顧名思義,go-homedir
用來獲取用戶的主目錄。 實際上,使用標準庫os/user
咱們也能夠獲得這個信息:git
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
庫?github
在 Darwin 系統上,標準庫os/user
的使用須要 cgo。因此,任何使用os/user
的代碼都不能交叉編譯。 可是,大多數人使用os/user
的目的僅僅只是想獲取主目錄。所以,go-homedir
庫出現了。golang
go-homedir
是第三方包,使用前須要先安裝:shell
$ 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
有兩個功能:windows
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
清除緩存。bash
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
能夠顯示當前目錄。那麼就能夠結合這兩個命令返回主目錄。這裏分析源碼並非表示使用任何庫都要熟悉它的源碼,畢竟使用庫就是爲了方便開發。 可是源碼是咱們學習和提升的一個很是重要的途徑。咱們在使用庫遇到問題的時候也要有能力從文檔或甚至源碼中查找緣由。
歡迎關注個人微信公衆號【GoUpUp】,共同窗習,一塊兒進步~
本文由博客一文多發平臺 OpenWrite 發佈!