Go语言os包用法简述

Go语言的 os包中提供了操作系统函数的接口,是一个比较重要的包。顾名思义,os 包的作用主要是在服务器上进行系统的基本操作,如文件操作、目录操作、执行命令、信号与中断、进程、系统状态等等。

os包中的常用函数
  1. Hostname
    Hostname函数会返回内核提供的主机名
    fmt.Println(os.Hostname()) // mayanan <nil>
  2. Environ
func main() {
	for _, v := range os.Environ() {
		fmt.Println(v)
	}
}

Eviron函数会返回所有的环境变量,返回值格式为“key=value”的字符串的切片拷贝。
3. GetEnv
fmt.Println(os.Getenv("Goland")) GetEnv会检索并返回名为key的环境变量的值,如果不存在该环境变量则会返回空字符串。
4. SetEnv

func main() {
	os.Setenv("myName", "马亚")
	fmt.Println(os.Getenv("myName"))  // 马亚
}

SetEnv函数可以设置名为key的环境变量,如果出错会返回该错误
5. Exit

func main() {
	defer func() {
		fmt.Println("defer 结束了")
	}()
	fmt.Println("开始了")
	os.Exit(0)
	fmt.Println("结束了")
}

输出结果
开始了 Exit函数可以让当前程序以给出的状态码code退出,一般来说,0表示成功,非0表示失败,程序会立刻终止,并且defer的函数不会被执行
6. Getuid
函数可以返回调用者的用户id
7. Getgid
函数可以返回调用者的组id
8. Getpid
fmt.Println(os.Getpid()) 函数可以返回调用者所在进程的进程id
9. Getwd
fmt.Println(os.Getwd()) Getwd函数可以返回一个对应当前工作目录的根路径,如果当前目录可以经过多条路径抵达(因为硬链接),Getwd会返回其中一个
10. Mkdir
os.Mkdir("马亚", 1) Mkdir 函数可以使用指定的权限和名称创建一个目录。如果出错,会返回 *PathError 底层类型的错误。
11. MkdirAll
fmt.Println(os.MkdirAll("马亚/南", 1)) MkdirAll 函数可以使用指定的权限和名称创建一个目录,包括任何必要的上级目录,并返回 nil,否则返回错误。权限位 perm 会应用在每一个被该函数创建的目录上。如果 path 指定了一个已经存在的目录,MkdirAll 不做任何操作并返回 nil。
12. Remove和RemoveAll

func main() {
	// 级联创建目录
	fmt.Println(os.MkdirAll("马亚/南", 1))
	// 级联删除目录
	fmt.Println(os.RemoveAll("马亚"))
}

Remove 函数会删除 name 指定的文件或目录。如果出错,会返回 *PathError 底层类型的错误。

RemoveAll 函数跟 Remove 用法一样,区别是会递归的删除所有子目录和文件。

在 os 包下,有 exec,signal,user 三个子包,下面来分别介绍一下。

os/exec执行外部命令

在环境变量 PATH 指定的目录中搜索可执行文件,如果 file 中有斜杠,则只在当前目录搜索。返回完整路径或者相对于当前目录的一个相对路径。

func main() {
	f, err := exec.LookPath("test")
	if err != nil {
		fmt.Println(err)
	}
	fmt.Println(f)
}

结果:test.exe

os/user获取当前用户信息

可以通过 os/user 包中的 Current() 函数来获取当前用户信息,该函数会返回一个 User 结构体,结构体中的 Username、Uid、HomeDir、Gid 分别表示当前用户的名称、用户 id、用户主目录和用户所属组 id,

func main() {
	u, _ := user.Current()
	fmt.Println(u.Username)  // 用户名
	fmt.Println(u.Uid)  // 用户id
	fmt.Println(u.HomeDir)  // 用户所在目录
	fmt.Println(u.Gid)  // 用户组id
	ids, _ := u.GroupIds()  // 用户所在所有组
	fmt.Println(ids)
}

运行结果如下:

MAYANAN\mayanan
S-1-5-21-1573046109-4027372347-245519327-1001
C:\Users\mayanan
S-1-5-21-1573046109-4027372347-245519327-513
[S-1-5-21-1573046109-4027372347-245519327-1002 S-1-5-32-544 S-1-5-21-1573046109-
4027372347-245519327-513]
os/signal信号处理

一个运行良好的程序在退出(正常退出或者强制退出,如 Ctrl+C,kill 等)时是可以执行一段清理代码的,将收尾工作做完后再真正退出。一般采用系统 Signal 来通知系统退出,如 kill pid,在程序中针对一些系统信号设置了处理函数,当收到信号后,会执行相关清理程序或通知各个子进程做自清理。

go语言中对信号的处理主要使用os/signal包中的两个方法,一个是Notify方法来监听收到的信号,一个是Stop方法来取消监听

其中,第一个参数表示接收信号的 channel,第二个及后面的参数表示设置要监听的信号,如果不设置表示监听所有的信号。

示例1:使用Notify方法来监听收到的信号

func main() {
	ch := make(chan os.Signal)
	signal.Notify(ch)
	// 阻塞到信号接收
	s := <- ch
	fmt.Println("Got signal", s)
}

运行该程序,然后在 CMD 窗口中通过 Ctrl+C 来结束该程序,便会得到输出结果:
Got signal interrupt

示例2:使用Stop方法来取消监听

func main() {
	ch := make(chan os.Signal)
	// 监听信号
	signal.Notify(ch)
	// 取消监听信号
	signal.Stop(ch)
	s := <- ch
	fmt.Println("Got signal", s)
}

因为使用Stop方法取消了Notify方法的监听,所以运行程序没有输出结果。

go语言flag包,命令行参数解析

在编写命令行程序(工具、server)时,我们有时需要对命令参数进行解析,各种编程语言一般都会提供解析命令行参数的方法或库,以方便程序员使用。在Go语言中的 flag 包中,提供了命令行参数解析的功能。

下面我们就来看一下 flag 包可以做什么,它具有什么样的能力。
这里介绍几个概念:

  • 命令行参数(或参数):是指运行程序时提供的参数
  • 已定义命令行参数:是指程序中通过flag.Type这种形式定义了的参数
  • 非 flag(non-flag)命令行参数(或保留的命令行参数):可以简单理解为 flag 包不能解析的参数。
flag包概述

go语言内置的flag包实现了命令行参数的解析,flag 包使得开发命令行工具更为简单。

flag参数类型

flag 包支持的命令行参数类型有 bool、int、int64、uint、uint64、float、float64、string、duration,如下表所示:

flag 参数

有效值

字符串 flag

合法字符串

整数 flag

1234、0664、0x1234 等类型,也可以是负数

浮点数 flag

合法浮点数

bool 类型 flag

1、0、t、f、T、F、true、false、TRUE、FALSE、True、False

时间段 flag

任何合法的时间段字符串,如“300ms”、“-1.5h”、“2h45m”,

合法的单位有“ns”、“us”、“µs”、“ms”、“s”、“m”、“h”

flag包基本使用

有以下两种常用的定义命令行 flag 参数的方法:

  1. flag.Type()
    Type 可以是 Int、String、Bool 等,返回值为一个相应类型的指针,例如我们要定义姓名、年龄、婚否三个命令行参数,我们可以按如下方式定义:
func main() {
	name := flag.String("name", "马亚南", "姓名")
	age := flag.Int("age", 18, "年龄")
	married := flag.Bool("married", true, "性别")
	delay := flag.Duration("delay", 0, "时间")

	// 必须有这一行
	flag.Parse()

	fmt.Println(*name, *age, *married, *delay)
}

需要注意的是:此时name、age、married、delay均为指针类型。
执行命令:go run .\test.go --name=张三 --age=88 --married=f --delay=2h5m66s 2. flag.TypeVar
基本格式如下:
flag.TypeVar(Type 指针, flag 名, 默认值, 帮助信息) TypeVar 可以是 IntVar、StringVar、BoolVar 等,其功能为将 flag 绑定到一个变量上,例如我们要定义姓名、年龄、婚否三个命令行参数,我们可以按如下方式定义:

func main() {
	var name string
	var age int
	var married bool
	var delay time.Duration

	flag.StringVar(&name, "name", "马亚南", "姓名")
	flag.IntVar(&age, "age", 18, "年龄")
	flag.BoolVar(&married, "married", false, "是否已婚")
	flag.DurationVar(&delay, "delay", time.Hour*2+time.Minute*2+time.Second*2, "时间")
	flag.Parse()

	fmt.Println(name, age, married, delay)
}

执行命令:
go run .\test.go --name=马馨彤 --age=5 --married=f --delay=5h5m5s 也可以这样指定
go run .\test.go --name 马馨彤 --age 5 --married=f --delay 5h5m5s 但是必须要注意的是,布尔类型必须使用=号来指定。

flag包的其它函数
  • flag.Args() // 返回命令行参数后的其它参数,以[]string类型
  • flag.NArg() // 返回命令行参数后的其它参数个数
  • flag.NFlag() // 返回使用的命令行参数个数
自定义Value

另外,我们还可以创建自定义 flag,只要实现 flag.Value 接口即可(要求 receiver 是指针类型),这时候可以通过如下方式定义该 flag:
flag.Var(&flagVal, "name", "help message for flagname") 示例:解析喜欢的变成语言,并直接解析到slice中,我们可以定义如下sliceValue类型,然后实现Value接口,实现flag包中的Value接口,将命令行接收到的值用,分隔存到slice里面

// 自定义一个类型,用来增加该类型昂发
type sliceValue []string
// new一个存放命令行参数值的slice
func newSliceValue(vals []string, p *[]string) *sliceValue {
	*p = vals
	return (*sliceValue)(p)
}
func (s *sliceValue) Set(val string) error {
	*s = sliceValue(strings.Split(val, ","))
	return nil
}
// flag为slice的默认值default is me,和return返回值没有关系
func (s *sliceValue) String() string {
	*s = sliceValue(strings.Split("default is me", ","))
	return "It's none of m business"
}
/*
可执行文件名 -slice="java,go"  最后将输出[java,go]
可执行文件名 最后将输出[default is me]
*/

func main() {
	var languages []string
	flag.Var(newSliceValue([]string{}, &languages), "languages", "语言")
	flag.Parse()
	fmt.Println(languages)
}

通过-slice go,php 这样的形式传递参数,languages 得到的就是 [go, php],如果不加-slice 参数则打印默认值[default is me],如下所示:
flag 中对 Duration 这种非基本类型的支持,使用的就是类似这样的方式,即同样实现了 Value 接口。

go语言go mode包依赖管理工具

最早的时候,Go语言所依赖的所有的第三方库都放在 GOPATH 这个目录下面,这就导致了同一个库只能保存一个版本的代码。如果不同的项目依赖同一个第三方的库的不同版本,应该怎么解决?

Windows 下开启 GO111MODULE 的命令为: set GO111MODULE=on 或者 set GO111MODULE=auto
MacOS 或者 Linux 下开启 GO111MODULE 的命令为: export GO111MODULE=on 或者 export GO111MODULE=auto

GOPROXY

proxy 顾名思义就是代理服务器的意思。大家都知道,国内的网络有防火墙的存在,这导致有些Go语言的第三方包我们无法直接通过go get命令获取。GOPROXY 是Go语言官方提供的一种通过中间代理商来为用户提供包下载服务的方式。要使用 GOPROXY 只需要设置环境变量 GOPROXY 即可。
目前公开的代理服务器地址有:
goproxy.io
goproxy.cn // 推荐(由国内的七牛云提供)

windows下设置GOPROXY的命令为go env -w GOPROXY=https://goproxy.cn

使用go get命令下载指定版本的依赖包
  • 执行go get 命令,在下载依赖包的同时还可以指定依赖包的版本。
  • 运行go get -u命令会将项目中的包升级到最新的次要版本或者修订版本;
  • 运行go get -u=patch命令会将项目中的包升级到最新的修订版本;
  • 运行go get [包名]@[版本号]命令会下载对应包的指定版本或者将对应包升级到指定的版本。

提示:go get [包名]@[版本号]命令中版本号可以是 x.y.z 的形式,例如 go get foo@v1.2.3,也可以是 git 上的分支或 tag,例如 go get foo@master,还可以是 git 提交时的哈希值,例如 go get foo@e3702bed2。

示例1:

  1. 在GOPATH目录之外新建一个目录,并使用go mod init命令初始化生成go.mod文件
    go mod init hello go.mod 文件一旦创建后,它的内容将会被 go toolchain 全面掌控,go toolchain 会在各类命令执行时,比如go get、go build、go mod等修改和维护 go.mod 文件。
    go mod 提供了module、require、replace和exclude四个命令
module 语句指定包的名字(路径);
require 语句指定的依赖项模块;
replace 语句可以替换依赖项模块;
exclude 语句可以忽略依赖项模块。
  1. 添加依赖
    新建一个main.go文件,写入如下代码
import (
	"github.com/labstack/echo"
	"net/http"
)
func main() {
	e := echo.New()
	e.GET("/", func(ctx echo.Context) error {
		return ctx.String(http.StatusOK, "Hello, World!")
	})
	e.Logger.Fatal(e.Start(":1323"))
}

下载依赖:go get github.com/labstack/echo go module 安装 package 的原则是先拉取最新的 release tag,若无 tag 则拉取最新的 commit,详见 Modules 官方介绍。

示例2改造现有项目:
项目目录结构为:

├─ main.go
│
└─ api
      └─ apis.go

main.go的源码为:

import (
	"github.com/labstack/echo"
	api "./api"  // 这里使用的是相对路径
)
func main() {
	e := echo.New()
	e.GET("/", api.HelloWorld)
	e.Logger.Fatal(e.Start(":1323"))
}

api/apis.go的源码为:

package api
import (
	"github.com/labstack/echo"
	"net/http"
)
func HelloWorld(ctx echo.Context) error {
	return ctx.String(http.StatusOK, "Hello World!")
}

(1)使用go mod init mayanan初始化go.mod
(2)导入包的方式是需要修改的

package main

import (
	"github.com/labstack/echo"
	api "mayanan/api"  // 这里使用的是相对路径
)
func main() {
	e := echo.New()
	e.GET("/", api.HelloWorld)
	e.Logger.Fatal(e.Start(":1323"))
}