os包中实现了不依赖平台的操作系统函数接口(平台无关的接口),设计向Unix风格,但是错误处理是go风格,当os包使用时,如果失败之后返回错误类型而不是错误数量,返回错误值而非错误码,可以包含更多信息。
os
os 依赖于 syscall。在实际编程中,我们应该总是优先使用 os 中提供的功能,而不是 syscall。
os包提供了操作系统函数的不依赖平台的接口。一般都是linux下的一些基本命令的操作,比如文件,目录操作之类。
我们运行程序常用的命令行参数就是在这个包中可以获取
var Args []string
Args保管了命令行参数,第一个是程序名。
文件io
文件IO就是对文件的读写操作,我们先了解一些os中的基本概念。
基本概念
文件描述符
所有 I/O 操作以文件描述符 ( 一个非负整数 , 通常是小整数 ) 来指代打开的文件。文件描述符用以表示所有类型的已打开文件,包括管道(pipe)、FIFO、socket、终端、设备和普通文件。
在 Go 中,文件描述符封装在 os.File 结构中,通过 File.Fd() 可以获得底层的文件描述符:fd。
File结构体
type File struct {
*file
}
// file is the real representation of *File.
// The extra level of indirection ensures that no clients of os
// can overwrite this data, which could cause the finalizer
// to close the wrong file descriptor.
type file struct {
fd int
name string
dirinfo *dirInfo // nil unless directory being read
}
// Auxiliary information if the File describes a directory
type dirInfo struct {
buf []byte // buffer for directory I/O
nbuf int // length of buf; return value from Getdirentries
bufp int // location of next record in buf.
}
标准定义
按照惯例,大多数程序都期望能够使用 3 种标准的文件描述符:0- 标准输入;1- 标准输出;2- 标准错误。os 包提供了 3 个 File 对象,分别代表这 3 种标准描述符:Stdin、Stdout 和 Stderr,它们对应的文件名分别是:/dev/stdin、/dev/stdout 和 /dev/stderr。
基本操作
创建
func NewFile(fd uintptr, name string) *File
NewFile使用给出的Unix文件描述符和名称创建一个文件。
正常使用create来创建一个文件,比如文件不存在,就创建一个
file,er:=os.Open("xxx")
defer func(){file.Close()}()
if er!=nil && os.IfNotExist(er
r){
file = os.Create("xx")
}
打开
func Open(name string) (file *File, err error)
Open打开一个文件用于读取。如果操作成功,返回的文件对象的方法可用于读取数据;对应的文件描述符具有O_RDONLY模式。如果出错,错误底层类型是*PathError。
func OpenFile(name string, flag int, perm FileMode) (file *File, err error)
OpenFile是一个更一般性的文件打开函数,大多数调用者都应用Open或Create代替本函数。它会使用指定的选项(如O_RDONLY等)、指定的模式(如0666等)打开指定名称的文件。如果操作成功,返回的文件对象可用于I/O。如果出错,错误底层类型是*PathError。
位掩码参数 flag 用于指定文件的访问模式,可用的值在 os 中定义为常量(以下值并非所有操作系统都可用):
const (
O_RDONLY int = syscall.O_RDONLY // 只读模式打开文件
O_WRONLY int = syscall.O_WRONLY // 只写模式打开文件
O_RDWR int = syscall.O_RDWR // 读写模式打开文件
O_APPEND int = syscall.O_APPEND // 写操作时将数据附加到文件尾部
O_CREATE int = syscall.O_CREAT // 如果不存在将创建一个新文件
O_EXCL int = syscall.O_EXCL // 和 O_CREATE 配合使用,文件必须不存在
O_SYNC int = syscall.O_SYNC // 打开文件用于同步 I/O
O_TRUNC int = syscall.O_TRUNC // 如果可能,打开时清空文件
)
O_TRUNC这个参数可以用来清空文件,如果可以的话,还可以用这个函数
os.Truncate(name, size)
或者
func (f *File) Truncate(size int64) error
size 填0 就把文件清空了。
下面有详细的说明
位掩码参数 perm 指定了文件的模式和权限位,类型是 os.FileMode,文件模式位常量定义在 os 中:
const (
// 单字符是被 String 方法用于格式化的属性缩写。
ModeDir FileMode = 1 << (32 - 1 - iota) // d: 目录
ModeAppend // a: 只能写入,且只能写入到末尾
ModeExclusive // l: 用于执行
ModeTemporary // T: 临时文件(非备份文件)
ModeSymlink // L: 符号链接(不是快捷方式文件)
ModeDevice // D: 设备
ModeNamedPipe // p: 命名管道(FIFO)
ModeSocket // S: Unix 域 socket
ModeSetuid // u: 表示文件具有其创建者用户 id 权限
ModeSetgid // g: 表示文件具有其创建者组 id 的权限
ModeCharDevice // c: 字符设备,需已设置 ModeDevice
ModeSticky // t: 只有 root/ 创建者能删除 / 移动文件
// 覆盖所有类型位(用于通过 & 获取类型位),对普通文件,所有这些位都不应被设置
ModeType = ModeDir | ModeSymlink | ModeNamedPipe | ModeSocket | ModeDevice
ModePerm FileMode = 0777 // 覆盖所有 Unix 权限位(用于通过 & 获取类型位)
)
read
func (f *File) Read(b []byte) (n int, err error)
Read 方法从 f 中读取最多 len(b) 字节数据并写入 b。它返回读取的字节数和可能遇到的任何错误。文件终止标志是读取 0 个字节且返回值 err 为 io.EOF。
从方法声明可以知道,File 实现了 io.Reader 接口。
func (f *File) ReadAt(b []byte, off int64) (n int, err error)
ReadAt 从指定的位置(相对于文件开始位置)读取长度为 len(b) 个字节数据并写入 b。它返回读取的字节数和可能遇到的任何错误。当 n<len(b) 时,本方法总是会返回错误;如果是因为到达文件结尾,返回值 err 会是 io.EOF。它对应的系统调用是 pread。
实例
var chunks []byte
buf := make([]byte, 1024)
var count = 0
for {
n, err := f.Read(buf)
if err != nil && err != io.EOF {
panic(err)
}
if 0 == n {
break
}
count = count + n
chunks = append(chunks, buf[:n]...)
}
r.logger.Debugf("read file content : %s",string(chunks[:count]))
这边这个实例主要是要说明一下几个重点:
1、buf必须make,不然会panic
2、read必须for循环,直到io.EOF
write
func (f *File) Write(b []byte) (n int, err error)
Write 向文件中写入 len(b) 字节数据。它返回写入的字节数和可能遇到的任何错误。如果返回值 n!=len(b),本方法会返回一个非 nil 的错误。
从方法声明可以知道,File 实现了 io.Writer 接口。
Write 与 WriteAt 的区别同 Read 与 ReadAt 的区别一样。为了方便,还提供了 WriteString 方法,它实际是对 Write 的封装。
注意:Write 调用成功并不能保证数据已经写入磁盘,因为内核会缓存磁盘的 I/O 操作。如果希望立刻将数据写入磁盘(一般场景不建议这么做,因为会影响性能),有两种办法:
1. 打开文件时指定 `os.O_SYNC`;
2. 调用 `File.Sync()` 方法。
说明:File.Sync() 底层调用的是 fsync 系统调用,这会将数据和元数据都刷到磁盘;如果只想刷数据到磁盘(比如,文件大小没变,只是变了文件数据),需要自己封装,调用 fdatasync(syscall.Fdatasync) 系统调用。
close
close() 系统调用关闭一个打开的文件描述符,并将其释放回调用进程,供该进程继续使用。当进程终止时,将自动关闭其已打开的所有文件描述符。
func (f *File) Close() error
os.File.Close() 是对 close() 的封装。我们应该养成关闭不需要的文件的良好编程习惯。文件描述符是资源,Go 的 gc 是针对内存的,并不会自动回收资源,如果不关闭文件描述符,长期运行的服务可能会把文件描述符耗尽。
以下两种情况会导致 Close 返回错误:
1. 关闭一个未打开的文件;
2. 两次关闭同一个文件;
通常,我们不会去检查 Close 的错误
seek
func (f *File) Seek(offset int64, whence int) (ret int64, err error)
Seek 设置下一次读 / 写的位置。offset 为相对偏移量,而 whence 决定相对位置:0 为相对文件开头,1 为相对当前位置,2 为相对文件结尾。它返回新的偏移量(相对开头)和可能的错误。使用中,whence 应该使用 os 包中的常量:SEEK_SET、SEEK_CUR 和 SEEK_END
实例
file.Seek(0, os.SEEK_SET) // 文件开始处
file.Seek(0, SEEK_END) // 文件结尾处的下一个字节
file.Seek(-1, SEEK_END) // 文件最后一个字节
file.Seek(-10, SEEK_CUR) // 当前位置前 10 个字节
file.Seek(1000, SEEK_END) // 文件结尾处的下 1001 个字节
trucate
trucate 和 ftruncate 系统调用将文件大小设置为 size 参数指定的值;Go 语言中相应的包装函数是 os.Truncate 和 os.File.Truncate。
func Truncate(name string, size int64) error
func (f *File) Truncate(size int64) error
如果文件当前长度大于参数 size,调用将丢弃超出部分,若小于参数 size,调用将在文件尾部添加一系列空字节或是一个文件空洞。
remove
func Remove(name string) error
Remove删除name指定的文件或目录。如果出错,会返回*PathError底层类型的错误。
文件属性
文件信息
可以通过包里的函数 Stat、Lstat 和 File.Stat 可以得到os.FileInfo 接口的信息。这三个函数对应三个系统调用:stat、lstat 和 fstat。
这三个函数的区别:
- stat 会返回所命名文件的相关信息。
- lstat 与 stat 类似,区别在于如果文件是符号链接,那么所返回的信息针对的是符号链接自身(而非符号链接所指向的文件)。
- fstat 则会返回由某个打开文件描述符(Go 中则是当前打开文件 File)所指代文件的相关信息。
Stat 和 Lstat 无需对其所操作的文件本身拥有任何权限,但针对指定 name 的父目录要有执行(搜索)权限。而只要 File 对象 ok,File.Stat 总是成功。
func (f *File) Stat() (fi FileInfo, err error)
Stat返回描述文件f的FileInfo类型值。如果出错,错误底层类型是*PathError。这个方法也可以用于检查文件是否有问题,上面说到文件的信息是存储在FileInfo 接口中的,我们来看一下这个接口
FileInfo是一个接口,如下
// A FileInfo describes a file and is returned by Stat and Lstat.
type FileInfo interface {
Name() string // base name of the file 文件的名字(不含扩展名)
Size() int64 // length in bytes for regular files; system-dependent for others 普通文件返回值表示其大小;其他文件的返回值含义各系统不同
Mode() FileMode // file mode bits 文件的模式位
ModTime() time.Time // modification time 文件的修改时间
IsDir() bool // abbreviation for Mode().IsDir() 等价于 Mode().IsDir()
Sys() interface{} // underlying data source (can return nil) 底层数据来源(可以返回 nil)
}
该接口提供了一个sys函数,Sys() 底层数据的 C 语言 结构 statbuf 格式如下:
struct stat {
dev_t st_dev; // 设备 ID
ino_t st_ino; // 文件 i 节点号
mode_t st_mode; // 位掩码,文件类型和文件权限
nlink_t st_nlink; // 硬链接数
uid_t st_uid; // 文件属主,用户 ID
gid_t st_gid; // 文件属组,组 ID
dev_t st_rdev; // 如果针对设备 i 节点,则此字段包含主、辅 ID
off_t st_size; // 常规文件,则是文件字节数;符号链接,则是链接所指路径名的长度,字节为单位;对于共享内存对象,则是对象大小
blksize_t st_blsize; // 分配给文件的总块数,块大小为 512 字节
blkcnt_t st_blocks; // 实际分配给文件的磁盘块数量
time_t st_atime; // 对文件上次访问时间
time_t st_mtime; // 对文件上次修改时间
time_t st_ctime; // 文件状态发生改变的上次时间
}
Go 中 syscal.Stat_t 与该结构对应。
如果我们要获取 FileInfo 接口没法直接返回的信息,比如想获取文件的上次访问时间,示例如下:
fileInfo, err := os.Stat("test.log")
if err != nil {
log.Fatal(err)
}
sys := fileInfo.Sys()
stat := sys.(*syscall.Stat_t)
fmt.Println(time.Unix(stat.Atimespec.Unix()))
正常返回的是实现这个接口的结构体,也就是fileStat,如下
// A fileStat is the implementation of FileInfo returned by Stat and Lstat.
type fileStat struct {
name string
size int64
mode FileMode
modTime time.Time
sys syscall.Stat_t
}
func (fs *fileStat) Size() int64 { return fs.size }
func (fs *fileStat) Mode() FileMode { return fs.mode }
func (fs *fileStat) ModTime() time.Time { return fs.modTime }
func (fs *fileStat) Sys() interface{} { return &fs.sys }
func sameFile(fs1, fs2 *fileStat) bool {
return fs1.sys.Dev == fs2.sys.Dev && fs1.sys.Ino == fs2.sys.Ino
}
其中有一个syscall.Stat_t,源于syscall的结构体,这个结构体是需要区分系统的,不同的系统调用不一样,不然编译不通过,报错如下
registry/delete.go:49:27: stat.Ctimespec undefined (type *syscall.Stat_t has no field or method Ctimespec)
是因为在linux下结构体成名名是Ctim,在drawin下是Ctimespec,导致跨平台编译报错。
文件时间
通过包里的Chtimes函数可以显式改变文件的访问时间和修改时间。
func Chtimes(name string, atime time.Time, mtime time.Time) error
Chtimes 修改 name 指定的文件对象的访问时间和修改时间,类似 Unix 的 utime() 或 utimes() 函数。底层的文件系统可能会截断 / 舍入时间单位到更低的精确度。如果出错,会返回 *PathError 类型的错误。在 Unix 中,底层实现会调用 utimenstat(),它提供纳秒级别的精度
文件权限
系统调用 chown、lchown 和 fchown 可用来改变文件的属主和属组,Go 中os包中对应的函数或方法:
func Chown(name string, uid, gid int) error
func Lchown(name string, uid, gid int) error
func (f *File) Chown(uid, gid int) error
它们的区别和上文提到的 Stat 相关函数类似。
在文件相关操作报错时,可以通过 os.IsPermission 检查是否是权限的问题。
func IsPermission(err error) bool
返回一个布尔值说明该错误是否表示因权限不足要求被拒绝。ErrPermission 和一些系统调用错误会使它返回真。
另外,syscall.Access 可以获取文件的权限。这对应系统调用 access。
os.Chmod 和 os.File.Chmod 可以修改文件权限(包括 sticky 位),分别对应系统调用 chmod 和 fchmod。
目录
在 Unix 文件系统中,目录的存储方式类似于普通文件。目录和普通文件的区别有二:
- 在其 i-node 条目中,会将目录标记为一种不同的文件类型。
- 目录是经特殊组织而成的文件。本质上说就是一个表格,包含文件名和 i-node 标号
目录操作
创建
func Mkdir(name string, perm FileMode) error
Mkdir 使用指定的权限和名称创建一个目录。如果出错,会返回 *PathError 类型的错误。
- name 参数指定了新目录的路径名,可以是相对路径,也可以是绝对路径。如果已经存在,则调用失败并返回 os.ErrExist 错误。
- perm 参数指定了新目录的权限。对该位掩码值的指定方式和 os.OpenFile 相同,也可以直接赋予八进制数值。注意,perm 值还将于进程掩码相与(&)。如果 perm 中设置了 sticky 位,那么将对新目录设置该权限。
因为 Mkdir 所创建的只是路径名中的最后一部分,如果父目录不存在,创建会失败。os.MkdirAll 用于递归创建所有不存在的目录
func MkdirAll(path string, perm FileMode) error
MkdirAll使用指定的权限和名称创建一个目录,包括任何必要的上级目录,并返回nil,否则返回错误。权限位perm会应用在每一个被本函数创建的目录上。如果path指定了一个已经存在的目录,MkdirAll不做任何操作并返回nil。
删除
func Remove(name string) error
Remove 删除 name 指定的文件或目录。如果出错,会返回 *PathError 类型的错误。如果目录不为空,Remove 会返回失败。
func RemoveAll(path string) error
RemoveAll 删除 path 指定的文件,或目录及它包含的任何下级对象。它会尝试删除所有东西,除非遇到错误并返回。如果 path 指定的对象不存在,RemoveAll 会返回 nil 而不返回错误。
RemoveAll 的内部实现逻辑如下:
- 调用 Remove 尝试进行删除,如果成功或返回 path 不存在,则直接返回 nil;
- 调用 Lstat 获取 path 信息,以便判断是否是目录。注意,这里使用 Lstat,表示不对符号链接解引用;
- 调用 Open 打开目录,递归读取目录中内容,执行删除操作。
读
func (f *File) Readdirnames(n int) (names []string, err error)
Readdirnames 读取目录 f 的内容,返回一个最多有 n 个成员的[]string,切片成员为目录中文件对象的名字,采用目录顺序。对本函数的下一次调用会返回上一次调用未读取的内容的信息。
- 如果 n>0,Readdirnames 函数会返回一个最多 n 个成员的切片。这时,如果 Readdirnames 返回一个空切片,它会返回一个非 nil 的错误说明原因。如果到达了目录 f 的结尾,返回值 err 会是 io.EOF。
- 如果 n<=0,Readdirnames 函数返回目录中剩余所有文件对象的名字构成的切片。此时,如果 Readdirnames 调用成功(读取所有内容直到结尾),它会返回该切片和 nil 的错误值。如果在到达结尾前遇到错误,会返回之前成功读取的名字构成的切片和该错误。
Readdir 内部会调用 Readdirnames,将得到的 names 构造路径,通过 Lstat 构造出 []FileInfo。
func (f *File) Readdir(n int) (fi []FileInfo, err error)
连接
func Link(oldname, newname string) error
Link 创建一个名为 newname 指向 oldname 的硬链接。如果出错,会返回 *LinkError 类型的错误。
func Symlink(oldname, newname string) error
Symlink 创建一个名为 newname 指向 oldname 的符号链接。如果出错,会返回 *LinkError 类型的错误。
func Readlink(name string) (string, error)
Readlink 获取 name 指定的符号链接指向的文件的路径。如果出错,会返回 *PathError 类型的错误。
更改文件名
func Rename(oldpath, newpath string) error
Rename 修改一个文件的名字或移动一个文件。如果 newpath 已经存在,则替换它。注意,可能会有一些个操作系统特定的限制。
os/singal
类型
Signal是一个接口,所有的信号都实现了这个接口,可以直接传递,我们传递信号的时候,需要定义这个类型的channel来传递信号。
type Signal interface {
String() string
Signal() // to distinguish from other Stringers
}
syscall 包中定义了所有的信号常量,比如syscall.SIGINT,其实就是一个int的数字信号。
SIGINT = Signal(0x2)
type Signal int
func (s Signal) Signal() {}
func (s Signal) String() string {
if 0 <= s && int(s) < len(signals) {
str := signals[s]
if str != "" {
return str
}
}
return "signal " + itoa(int(s))
}
函数
Notify
singnal主要是用于信号的传递,一般程序中需要使用信号的时候使用。主要使用下面两个方法。
func Notify(c chan<- os.Signal, sig ...os.Signal)
Notify函数让signal包将输入信号转发到c。如果没有列出要传递的信号,会将所有输入信号传递到c;否则只传递列出的输入信号。
signal包不会为了向c发送信息而阻塞(就是说如果发送时c阻塞了,signal包会直接放弃):调用者应该保证c有足够的缓存空间可以跟上期望的信号频率。对使用单一信号用于通知的通道,缓存为1就足够了。
可以使用同一通道多次调用Notify:每一次都会扩展该通道接收的信号集。可以使用同一信号和不同通道多次调用Notify:每一个通道都会独立接收到该信号的一个拷贝。
实例
package main
import "fmt"
import "os"
import "os/signal"
import "syscall"
func main() {
sigs := make(chan os.Signal, 1)
done := make(chan bool, 1)
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
go func() {
sig := <-sigs
fmt.Println()
fmt.Println(sig)
done <- true
}()
fmt.Println("awaiting signal")
<-done
fmt.Println("exiting")
}
stop
唯一从信号集去除信号的方法是调用Stop。
func Stop(c chan<- os.Signal)
Stop函数让signal包停止向c转发信号。它会取消之前使用c调用的所有Notify的效果。当Stop返回后,会保证c不再接收到任何信号。
os/exec
进程io
exec包用于执行外部命令。它包装了os.StartProcess函数以便更容易的修正输入和输出,使用管道连接I/O。主要用于创建一个子进程来执行相关的命令。创建子进程一定要wait,不能出现僵死进程。
调用脚本命令
在golang标准库中提供了两种方式可以用来启动进程调用脚本
第一种是在os库中的Process类型,Process类型包含一系列方法用来启动进程并对进程进行操作(参考: https://golang.org/pkg/os/#Process)
示例 使用Process执行脚本
package main
import (
"fmt"
"os"
)
func main() {
shellPath := "/home/xx/test.sh"
argv := make([]string, 1)
attr := new(os.ProcAttr)
newProcess, err := os.StartProcess(shellPath, argv, attr) //运行脚本
if err != nil {
fmt.Println(err)
}
fmt.Println("Process PID", newProcess.Pid)
processState, err := newProcess.Wait() //等待命令执行完
if err != nil {
fmt.Println(err)
}
fmt.Println("processState PID:", processState.Pid())//获取PID
fmt.Println("ProcessExit:", processState.Exited())//获取进程是否退出
}
第二种是在os/exec库种通过Cmd类型的各个函数实现对脚本的调用,实际上Cmd是对Process中各种方法的高层次封装(参考: https://golang.org/pkg/os/exec/)
1、LookPath
func LookPath(file string) (string, error)
在环境变量PATH指定的目录中搜索可执行文件,如file中有斜杠,则只在当前目录搜索。返回完整路径或者相对于当前目录的一个相对路径。
func main() {
output, err := exec.LookPath("ls")
if err != nil {
log.Fatal(err)
}
fmt.Printf(output)
}
output:
[ `go run test.go` | done: 616.254982ms ]
/bin/ls
2、Cmd
Cmd代表一个正在准备或者在执行中的外部命令
type Cmd struct {
// Path是将要执行的命令的路径。
//
// 该字段不能为空,如为相对路径会相对于Dir字段。
Path string
// Args保管命令的参数,包括命令名作为第一个参数;如果为空切片或者nil,相当于无参数命令。
//
// 典型用法下,Path和Args都应被Command函数设定。
Args []string
// Env指定进程的环境,如为nil,则是在当前进程的环境下执行。
Env []string
// Dir指定命令的工作目录。如为空字符串,会在调用者的进程当前目录下执行。
Dir string
// Stdin指定进程的标准输入,如为nil,进程会从空设备读取(os.DevNull)
Stdin io.Reader
// Stdout和Stderr指定进程的标准输出和标准错误输出。
//
// 如果任一个为nil,Run方法会将对应的文件描述符关联到空设备(os.DevNull)
//
// 如果两个字段相同,同一时间最多有一个线程可以写入。
Stdout io.Writer
Stderr io.Writer
// ExtraFiles指定额外被新进程继承的已打开文件流,不包括标准输入、标准输出、标准错误输出。
// 如果本字段非nil,entry i会变成文件描述符3+i。
//
// BUG: 在OS X 10.6系统中,子进程可能会继承不期望的文件描述符。
// http://golang.org/issue/2603
ExtraFiles []*os.File
// SysProcAttr保管可选的、各操作系统特定的sys执行属性。
// Run方法会将它作为os.ProcAttr的Sys字段传递给os.StartProcess函数。
SysProcAttr *syscall.SysProcAttr
// Process是底层的,只执行一次的进程。
Process *os.Process
// ProcessState包含一个已经存在的进程的信息,只有在调用Wait或Run后才可用。
ProcessState *os.ProcessState
// 内含隐藏或非导出字段
}
可以使用Command来创建cmd
func Command(name string, arg ...string) *Cmd
函数返回一个*Cmd,用于使用给出的参数执行name指定的程序。返回值只设定了Path和Args两个参数。
如果name不含路径分隔符,将使用LookPath获取完整路径;否则直接使用name。参数arg不应包含命令名
使用Run运行cmd命令
func (c *Cmd) Run() error
Run执行c包含的命令,并阻塞直到完成。如果命令成功执行,stdin、stdout、stderr的转交没有问题,并且返回状态码为0,方法的返回值为nil;如果命令没有执行或者执行失败,会返回错误;
使用Start和wait来运行命令
func (c *Cmd) Start() error
Start开始执行c包含的命令,但并不会等待该命令完成即返回。可以配合使用Wait方法来达到和Run一样的效果。wait方法会返回命令的返回状态码并在命令返回后释放相关的资源。
func (c *Cmd) Wait() error
Wait会阻塞直到该命令执行完成,该命令必须是被Start方法开始执行的。
通过Run的源码可以看出其实Run方法内部也是调用了Start和Wait方法。Run方法如下:
func (c *Cmd) Run() error {
if err := c.Start(); err != nil {
return err
}
return c.Wait()
}
实例
func main() {
cmd := exec.Command("tr", "a-z", "A-Z")
cmd.Stdin = strings.NewReader("abc def")
var out bytes.Buffer
cmd.Stdout = &out
err := cmd.Run()
if err != nil {
log.Fatal(err)
}
fmt.Printf("GOGOGO: %q\n", out.String())
}
output:
[ `go run test.go` | done: 286.798242ms ]
GOGOGO: "ABC DEF"
使用Output输出
func (c *Cmd) Output() ([]byte, error)
执行命令并返回标准输出的切片。
func (c *Cmd) CombinedOutput() ([]byte, error)
执行命令并返回标准输出和错误输出合并的切片.
实例
func main() {
out, err := exec.Command("date").Output()
if err != nil {
log.Fatal(err)
}
fmt.Printf("The date is %s\n", out)
}
output:
[ `go run test.go` | done: 585.495467ms ]
The date is Tue Aug 1 19:24:11 CST 2017
使用pipe
func (*Cmd) StdinPipe
func (c *Cmd) StdinPipe() (io.WriteCloser, error)
StdinPipe方法返回一个在命令Start后与命令标准输入关联的管道。Wait方法获知命令结束后会关闭这个管道。必要时调用者可以调用Close方法来强行关闭管道,例如命令在输入关闭后才会执行返回时需要显式关闭管道。
func (*Cmd) StdoutPipe
func (c *Cmd) StdoutPipe() (io.ReadCloser, error)
StdoutPipe方法返回一个在命令Start后与命令标准输出关联的管道。Wait方法获知命令结束后会关闭这个管道,一般不需要显式的关闭该管道。但是在从管道读取完全部数据之前调用Wait是错误的;同样使用StdoutPipe方法时调用Run函数也是错误的。例子如下:
func (*Cmd) StderrPipe
func (c *Cmd) StderrPipe() (io.ReadCloser, error)
StderrPipe方法返回一个在命令Start后与命令标准错误输出关联的管道。Wait方法获知命令结束后会关闭这个管道,一般不需要显式的关闭该管道。但是在从管道读取完全部数据之前调用Wait是错误的;同样使用StderrPipe方法时调用Run函数也是错误的。请参照StdoutPipe的例子。
实例
func main() {
cmd := exec.Command("echo", "-n", `{"Name": "Bob", "Age": 32}`)
stdout, err := cmd.StdoutPipe()
if err != nil {
log.Fatal(err)
}
if err := cmd.Start(); err != nil {
log.Fatal(err)
}
var person struct {
Name string
Age int
}
json.NewDecoder(r)
if err := json.NewDecoder(stdout).Decode(&person); err != nil {
log.Fatal(err)
}
if err := cmd.Wait(); err != nil {
log.Fatal(err)
}
fmt.Printf("%s is %d years old\n", person.Name, person.Age)
}
获取命令返回值
实际上脚本或命令执行完后,会将结果返回到ProcessState中的status去, 但是status不是export的,所以我们需要通过一些手段将脚本返回值从syscall.WaitStatus找出来
ProcessState定义
type ProcessState struct {
pid int // The process's id.
status syscall.WaitStatus // System-dependent status info.
rusage *syscall.Rusage
}
对于上面使用Cmd的例子,可以在进程退出后可以通过以下语句获取到返回值
fmt.Println("Exit Code", command.ProcessState.Sys().(syscall.WaitStatus).ExitStatus())
使用Process方式的也可以通过对ProcessState通过相同的方式获取到返回结果。
os/user
os/user 模块的主要作用是通过用户名或者 id 从而获取系统用户的相关属性。
类型
User 结构体
type User struct {
Uid string
Gid string
Username string
Name string
HomeDir string
}
User 代表一个用户账户:
- Uid :用户的 ID
- Gid :用户所属组的 ID,如果属于多个组,那么此 ID 为主组的 ID
- Username :用户名
- Name :属组名称,如果属于多个组,那么此名称为主组的名称
- HomeDir :用户的宿主目录
方法
返回当前用户。
func Current() (*User, error)
通过用户名查找用户,如果没有找到这个用户那么将返回 UnknownUserError 错误类型。
func Lookup(username string) (*User, error)
通过用户 ID 查找用户,如果没有找到这个用户那么将返回 UnknownUserIdError 错误类型。
func LookupId(uid string) (*User, error)
实例
package main
import (
"fmt"
"os/user"
"reflect"
)
func main() {
fmt.Println("== 测试 Current 正常情况 ==")
if u, err := user.Current(); err == nil {
fmt.Println("用户ID: " + u.Uid)
fmt.Println("主组ID: " + u.Gid)
fmt.Println("用户名: " + u.Username)
fmt.Println("主组名: " + u.Name)
fmt.Println("家目录: " + u.HomeDir)
}
fmt.Println("== 测试 Lookup 正常情况 ==")
if u, err := user.Lookup("root"); err == nil {
fmt.Println("用户ID: " + u.Uid)
fmt.Println("主组ID: " + u.Gid)
fmt.Println("用户名: " + u.Username)
fmt.Println("主组名: " + u.Name)
fmt.Println("家目录: " + u.HomeDir)
}
fmt.Println("== 测试 Lookup 异常情况 ==")
if _, err := user.Lookup("roo"); err == nil {
} else {
fmt.Println("错误信息: " + err.Error())
fmt.Print("错误类型: ")
fmt.Println(reflect.TypeOf(err))
}
fmt.Println("== 测试 LookupId 正常情况 ==")
if u, err := user.LookupId("0"); err == nil {
fmt.Println("用户ID: " + u.Uid)
fmt.Println("主组ID: " + u.Gid)
fmt.Println("用户名: " + u.Username)
fmt.Println("主组名: " + u.Name)
fmt.Println("家目录: " + u.HomeDir)
}
fmt.Println("== 测试 LookupId 异常情况 ==")
if _, err := user.LookupId("10000"); err == nil {
} else {
fmt.Println("错误信息: " + err.Error())
fmt.Print("错误类型: ")
fmt.Println(reflect.TypeOf(err))
}
}
输出结果如下:
== 测试 Current 正常情况 ==
用户ID: 0
主组ID: 0
用户名: root
主组名: root
家目录: /root
== 测试 Lookup 正常情况 ==
用户ID: 0
主组ID: 0
用户名: root
主组名: root
家目录: /root
== 测试 Lookup 异常情况 ==
错误信息: user: unknown user roo
错误类型: user.UnknownUserError
== 测试 LookupId 正常情况 ==
用户ID: 0
主组ID: 0
用户名: root
主组名: root
家目录: /root
== 测试 LookupId 异常情况 ==
错误信息: user: unknown userid 10000
错误类型: user.UnknownUserIdError