homedir 是一个不借助 cgo 获取用户目录的 go 库。
使用 go 内置的 os/user 也可以获取用户目录,但是在 Darwin 系统中需要使用 cgo,这意味着任何使用了 os/user 的 go 代码都不能交叉编译。但是大多数时候使用 os/user 都只是为了获取用户目录。
使用
安装:
$ go get github.com/mitchellh/go-homedir
示例:
func TestDir(t *testing.T) {
dir, err := homedir.Dir()
expand, err := homedir.Expand("~/example/path")
if err != nil {
fmt.Println(err)
return
}
fmt.Println(dir) // C:\Users\用户名
fmt.Println(expand) // C:\Users\用户名\example\path
}
homedir 的使用非常简单,只有两个函数:Dir() 用于获取用户目录,Expand(path string) 用于将 ~ 转换为用户目录。
默认情况下 homedir 开启了缓存功能,获取到用户目录后会缓存到属性中,即使运行过程中修改了用户目录也会返回之前的内容。可以通过将 DisableCache 属性设置为 true 来关闭缓存,也可以通过 Reset() 方法重置缓存。
原理
homedir 库只有一个 homedir.go 文件,其属性和主要方法如下:
var DisableCache bool
var homedirCache string
var cacheLock sync.RWMutex
func Dir() (string, error)
func Expand(path string) (string, error)
func Reset()
排除缓存代码后,Dir() 方法关键实现代码如下:
if runtime.GOOS == "windows" {
result, err = dirWindows()
} else {
// Unix-like system, so just assume Unix
result, err = dirUnix()
}
根据系统类型调用不同方法。
dirWindows() 方法:
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 拼接。
dirUnix() 方法:
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)Mac系统下使用指令dscl -q . -read /Users/"$(whoami)" NFSHomeDirectory | sed 's/^[^ ]*: //'获取getent命令可以用来查看系统数据库中的相关记录,通过getent passwd [key]查询当前用户的信息,其中就包含了用户目录如果以上命令都失败了,因为
cd命令不加参数可以回到用户目录,所以可以通过cd && pwd返回用户目录。