Go 每日一庫之 go-homedir

簡介

今天咱們來看一個很小,很實用的庫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,若是不爲空,返回這個值;
  • 讀取環境變量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 發佈!

相關文章
相關標籤/搜索