简介

今天我们来看一个很小,很实用的库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的使用需要 cgo。所以,任何使用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,如果不为空,返回这个值;
  • 读取环境变量HOMEDRIVEHOMEPATH,如果两者都不为空,拼接这两个值返回。

类 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可以显示当前目录。那么就可以结合这两个命令返回主目录。

这里分析源码并不是表示使用任何库都要熟悉它的源码,毕竟使用库就是为了方便开发。

但是源码是我们学习和提高的一个非常重要的途径。我们在使用库遇到问题的时候也要有能力从文档或甚至源码中查找原因。

参考

  1. home-dir GitHub 仓库

我的博客

欢迎关注我的微信公众号【GoUpUp】,共同学习,一起进步~

本文由博客一文多发平台 OpenWrite 发布!

Go 每日一库之 go-homedir的更多相关文章

  1. Go 每日一库之 flag

    缘起 我一直在想,有什么方式可以让人比较轻易地保持每日学习,持续输出的状态.写博客是一种方式,但不是每天都有想写的,值得写的东西. 有时候一个技术比较复杂,写博客的时候经常会写着写着发现自己的理解有偏 ...

  2. Go 每日一库之 viper

    简介 上一篇文章介绍 cobra 的时候提到了 viper,今天我们就来介绍一下这个库. viper 是一个配置解决方案,拥有丰富的特性: 支持 JSON/TOML/YAML/HCL/envfile/ ...

  3. Go 每日一库之 fsnotify

    简介 上一篇文章Go 每日一库之 viper中,我们介绍了 viper 可以监听文件修改进而自动重新加载. 其内部使用的就是fsnotify这个库,它是跨平台的.今天我们就来介绍一下它. 快速使用 先 ...

  4. go每日一库 [home-dir] 获取用户主目录

    关于我 我的博客|文章首发 顾名思义,go-homedir用来获取用户的主目录.实际上,通过使用标准库os/user我们也可以得到内容,使用以下方式 标准库使用 package main import ...

  5. Go 每日一库之 go-flags

    简介 在上一篇文章中,我们介绍了flag库.flag库是用于解析命令行选项的.但是flag有几个缺点: 不显示支持短选项.当然上一篇文章中也提到过可以通过将两个选项共享同一个变量迂回实现,但写起来比较 ...

  6. Go 每日一库之 go-ini

    简介 ini 是 Windows 上常用的配置文件格式.MySQL 的 Windows 版就是使用 ini 格式存储配置的. go-ini是 Go 语言中用于操作 ini 文件的第三方库. 本文介绍g ...

  7. Go 每日一库之 cobra

    简介 cobra是一个命令行程序库,可以用来编写命令行程序.同时,它也提供了一个脚手架, 用于生成基于 cobra 的应用程序框架.非常多知名的开源项目使用了 cobra 库构建命令行,如Kubern ...

  8. [python每日一库]——hotshot

    High performance logging profiler 官方文档:http://docs.python.org/2/library/hotshot.html#module-hotshot ...

  9. go每日新闻--2020-02-27

    go 语言中文网(每日资讯)_2020-02-27 一.Go 语言中文网 如何正确看待 Google 宣布 Fuchsia 操作系统没有选 Go 作为终端开发语言 Actor 还是 CSP?Go 中的 ...

随机推荐

  1. 不通过DataRow,直接往DataTable中添加新行DataTable.LoadDataRow(object[],bool)

    DataTable dtver = new DataTable();                dtver.Columns.Add("VERSION");            ...

  2. C# 单例类

    单例类 有时候我们不要在一个程序中创建太多的实例.只想用一个全局的实例和一个可以访问点.那么我们需要一个单例类. 因为是单例类啦,所以构造函数肯定是私有的. 需要了解的术语 懒汉式 顾名思义.什么时候 ...

  3. 【js】 vue 2.5.1 源码学习(六) initProxy initLifeCycle 渲染函数的作用域代理

    大体思路 (五) 1. initProxy 渲染函数的作用域代理 ==> es6 如果支持proxy (hasProxy) 就用proxy 不支持就用 defineProperty() prox ...

  4. P1001 A+B+C Problem

    题目描述 输入三个整数 \(a,b,c\) ,计算它们的和并将结果输出. 输入格式 输入一行,三个整数 \(a,b,c\) ,每个整数之间以一个空格隔开 \((0 \le a,b,c \le 1000 ...

  5. P1020 从大到小排序

    题目描述 给你n个整数,请你按照从大到小的顺序输出它们. 输入格式 输入的第一行包含一个整数 \(n(1 \le n \le 10^3)\) ,用于表示元素的个数. 输入的第二行包含 \(n\) 个整 ...

  6. SpringMVC 非注解配置

    web.xml配置: <servlet> <servlet-name>springmvc</servlet-name> <servlet-class>o ...

  7. Java内存溢出java.lang.OutOfMemoryError: PermGen space

    今天把以前的一个项目部署在tomcat,启动没问题.因为用到了webservice,当调用webservice中的方法时一直报内存溢出异常 Exception in thread "http ...

  8. ZR1158

    ZR1158 http://www.zhengruioi.com/contest/446/problem/1158 给定限制的问题大多数都是容斥或者二分,或者二分之后容斥 首先,这个问题的第一步我们还 ...

  9. buerdepepeqi 的模版

    buerdepepeqi的模板 头文件 #include <set> #include <map> #include <deque> #include <qu ...

  10. springmvc整合freemarker教程(转)

    1.介绍 我最近喜欢freemarker在网上找了大半天.都没有找到一个简单又容易理解的案例.虽然只是一个模板技术.但是相对刚开始什么都不知道的,很难入手.下面是自学(其实是谷歌和百度的东找西补).写 ...