10.8. os/path/filepath

10.8.1. 1.os

常见的关于操作系统的操作是文件系统的操作,比如读取目录、改变目录、创建文件、文件内容读取等。

绝大多数操作系统都是类UNIX操作系统,所以其文件系统也是类UNIX的文件信息。Linux就是类UNIX操作系统中的一种。Linux文件包含很多标识信息,比如所属组、属主、所属其他用户等。

Linux操作系统还提供了更改属性的相应操作。

chgrp:更改文件属组。

chown:更改文件属主。

chmod:更改文件的9个属性。

r:代表权限数字4。

w:代表权限数字2。

x:代表权限数字1。

比如更改文件属性:

func Getwd() (dir string, err error)
func Chdir(dir string) error
func Chmod(name string, mode FileMode) error
func Chown(name string, uid, gid int) error

内置库关于文件属性(读、写、可执行)的定义如下:

const (
    // Exactly one of O_RDONLY, O_WRONLY, or O_RDWR must be specified.
    O_RDONLY int = syscall.O_RDONLY // open the file read-only.
    O_WRONLY int = syscall.O_WRONLY // open the file write-only.
    O_RDWR   int = syscall.O_RDWR   // open the file read-write.
    // The remaining values may be or'ed in to control behavior.
    O_APPEND int = syscall.O_APPEND // append data to the file when writing.
    O_CREATE int = syscall.O_CREAT  // create a new file if none exists.
    O_EXCL   int = syscall.O_EXCL   // used with O_CREATE, file must not exist.
    O_SYNC   int = syscall.O_SYNC   // open for synchronous I/O.
    O_TRUNC  int = syscall.O_TRUNC  // truncate regular writable file when opened.
)

1.1 文件操作

对于文件和目录的操作,Go主要在os 提供了的相应函数:

func Mkdir(name string, perm FileMode) error
func Chdir(dir string) error
func TempDir() string
func Rename(oldpath, newpath string) error
func Chmod(name string, mode FileMode) error

func Open(name string) (*File, error) {
    return OpenFile(name, O_RDONLY, 0)
}

func Create(name string) (*File, error) {
    return OpenFile(name, O_RDWR|O_CREATE|O_TRUNC, 0666)
}

func OpenFile(name string, flag int, perm FileMode) (*File, error) {
    testlog.Open(name)
    return openFileNolog(name, flag, perm)
}

对文件的操作,常用的还是关于文件的读写。

type File
    func Create(name string) (file *File, err error)
    func Open(name string) (file *File, err error)
    func OpenFile(name string, flag int, perm FileMode) (file *File, err error)
    func NewFile(fd uintptr, name string) *File
    func Pipe() (r *File, w *File, err error)
    func (f *File) Name() string
    func (f *File) Stat() (fi FileInfo, err error)
    func (f *File) Fd() uintptr
    func (f *File) Chdir() error
    func (f *File) Chmod(mode FileMode) error
    func (f *File) Chown(uid, gid int) error
    func (f *File) Readdir(n int) (fi []FileInfo, err error)
    func (f *File) Readdirnames(n int) (names []string, err error)
    func (f *File) Truncate(size int64) error
    func (f *File) Read(b []byte) (n int, err error)
    func (f *File) ReadAt(b []byte, off int64) (n int, err error)
    func (f *File) Write(b []byte) (n int, err error)
    func (f *File) WriteString(s string) (ret int, err error)
    func (f *File) WriteAt(b []byte, off int64) (n int, err error)
    func (f *File) Seek(offset int64, whence int) (ret int64, err error)
    func (f *File) Sync() (err error)
    func (f *File) Close() error

可以看出,File具备Write和Read方法。

//io库的两个基本接口为Reader和Writer。

type Reader interface {
    Read(p []byte) (n int, err error)
}

type Writer interface {
    Write(p []byte) (n int, err error)
}

因为接口Writer和Reader分别具有Write和Read方法,所以File实现了接口Writer和Reader。

在日常开发中,关于文件的读写经常调用ioutil库中的ReadFile和WriteFile函数。阅读源代码可以发现,其实底层就是在调用os.File相关的操作。

// ReadFile reads the named file and returns the contents.
// A successful call returns err == nil, not err == EOF.
// Because ReadFile reads the whole file, it does not treat an EOF from Read
// as an error to be reported.
func ReadFile(name string) ([]byte, error) {
    f, err := Open(name)
    if err != nil {
        return nil, err
    }
    defer f.Close()

    var size int
    if info, err := f.Stat(); err == nil {
        size64 := info.Size()
        if int64(int(size64)) == size64 {
            size = int(size64)
        }
    }
    size++ // one byte for final read at EOF

    // If a file claims a small size, read at least 512 bytes.
    // In particular, files in Linux's /proc claim size 0 but
    // then do not work right if read in small pieces,
    // so an initial read of 1 byte would not work correctly.
    if size < 512 {
        size = 512
    }

    data := make([]byte, 0, size)
    for {
        if len(data) >= cap(data) {
            d := append(data[:cap(data)], 0)
            data = d[:len(data)]
        }
        n, err := f.Read(data[len(data):cap(data)])
        data = data[:len(data)+n]
        if err != nil {
            if err == io.EOF {
                err = nil
            }
            return data, err
        }
    }
}

内置的bufio、ioutil和os库都支持文件的读写。

1.2 示例-文件操作

func OsUsage() {
    // 判断文件是否存在,获取文件信息
    fileMode, err := os.Stat("log.log")
    if os.IsNotExist(err) {
        return
    }
    fmt.Println(fileMode.Name(), fileMode.Mode(), fileMode.Size())
}

func OSUsageWith() {
    // 创建文件,并以追加的方式写入内容
    file, _ := os.OpenFile("os.log", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
    fmt.Println(file.Name())
    file.WriteString("Hello")
    file.WriteString("HelloWorld")
    ioutil.ReadFile("os.log")
}

内置了打开和创建文件的两种快速操作:os.Open和os.Create,底层其实是os.OpenFile。

func Open(name string) (*File, error) {
    return OpenFile(name, O_RDONLY, 0)
}

func Create(name string) (*File, error) {
    return OpenFile(name, O_RDWR|O_CREATE|O_TRUNC, 0666)
}

1.3 示例-三种读取文件方式的比较

  • file.Read 方式读文件

  • bufio 方式读文件

  • ioutil 方式读文件

package main

import (
    "bufio"
    "fmt"
    "io"
    "io/ioutil"
    "os"
    "time"
)

/*
file.Read 方式读文件
*/
func read1(path string) {
    fi, err := os.Open(path)
    if err != nil {
        panic(err)
    }
    defer fi.Close()
    buf := make([]byte, 1024)
    for {
        n, err := fi.Read(buf)
        if err != nil && err != io.EOF {
            panic(err)
        }
        if 0 == n {
            break
        }
    }
}

/*
bufio方式读文件
*/
func read2(path string) {
    fi, err := os.Open(path)
    if err != nil {
        panic(err)
    }
    defer fi.Close()
    r := bufio.NewReader(fi)
    buf := make([]byte, 1024)
    for {
        n, err := r.Read(buf)
        if err != nil && err != io.EOF {
            panic(err)
        }
        if 0 == n {
            break
        }
    }
}

/*
ioutil方式读文件
 */
func read3(path string) {
    fi, err := os.Open(path)
    if err != nil {
        panic(err)
    }
    defer fi.Close()
    _, err = ioutil.ReadAll(fi)
}

func main() {
    file := "D:\\go_studay\\go_path\\src\\github.com\\medallion42\\IO操作\\system.log"
    start := time.Now()
    read1(file)
    t1 := time.Now()
    fmt.Printf("Cost time %v\n", t1.Sub(start))

    read2(file)
    t2 := time.Now()
    fmt.Printf("Cost time %v\n", t2.Sub(t1))

    read3(file)
    t3 := time.Now()
    fmt.Printf("Cost time %v\n", t3.Sub(t2))
}

/*
Cost time 24.9336ms
Cost time 10.0036ms
Cost time 19.9605ms
*/

bufio < ioutil < file.Read bufio读同一文件耗费时间最少, 效果稳稳地保持在最佳。

1.4 示例-文件操作实战

package main

import (
    "fmt"
    "io"
    "os"
)

/*创建文件,同时写入数据
filename:写入文件名
info1:写入文件详细内容
*/
func Touch_File(filename string, info1 string) bool {
    file, err := os.OpenFile(filename, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0666)
    if err != nil {
        fmt.Println("open file failed, err:", err)
        return false
    }
    defer file.Close()
    _, err2 := file.WriteString(info1)
    if err2 != nil {
        fmt.Println("写入文件内容失败...")
        return false
    } else {
        fmt.Println("写入文件成功....")
    }
    return true

}

/*CopyFile 拷贝文件函数.
dstName:目标文件名称
srcName:源文件名称
*/
func CopyFile(dstName, srcName string) (written int64, err error) {
    // 以读方式打开源文件
    src, err2 := os.Open(srcName)
    if err2 != nil {
        fmt.Printf("open %s failed, err:%v.\n", srcName, err)
        return
    }
    defer src.Close()
    // 以写|创建的方式打开目标文件
    dst, err3 := os.OpenFile(dstName, os.O_WRONLY|os.O_CREATE, 0644)
    if err3 != nil {
        fmt.Printf("open %s failed, err:%v.\n", dstName, err)
        return
    }
    defer dst.Close()
    return io.Copy(dst, src) //调用io.Copy()拷贝内容
}

func main() {
    path, erra := os.Getwd()
    if erra != nil {
        panic(erra)
    }
    pwd_file := path + "\\day03\\sample07\\hujianli.txt"
    Touch_File(pwd_file,"this is test file")
}

1.5 示例-读取文件并输出重复的行数

1.os.open实现

package main

import (
    "bufio"
    "fmt"
    "os"
)

func main() {
    counts := make(map[string]int)
    files := os.Args[1:]
    if len(files) == 0 {
        countLines(os.Stdin, counts)
    } else {
        for _, arg := range files {
            f, err := os.Open(arg)
            if err != nil {
                fmt.Fprintf(os.Stderr, "dnp2: %v\n", err)
                continue
            }
            countLines(f, counts)
            f.Close()
        }
    }
    for line, n := range counts {
        if n > 1 {
            fmt.Printf("%d\t%s\n", n, line)
        }
    }
}

func countLines(f *os.File, counts map[string]int) {
    input := bufio.NewScanner(f)
    for input.Scan() {
        counts[input.Text()]++
    }
}

2.ioutil.ReadFile实现

package main

import (
    "fmt"
    "io/ioutil"
    "os"
    "strings"
)

func main() {
    counts := make(map[string]int)
    for _, filename := range os.Args[1:] {
        data, err := ioutil.ReadFile(filename)
        if err != nil {
            fmt.Fprintf(os.Stderr, "dup3: %v\n", err)
            continue
        }
        for _, line := range strings.Split(string(data), "\n") {
            counts[line]++
        }
    }
    for line, n := range counts {
        if n > 1 {
            fmt.Printf("%d\t%s\n", n, line)
        }
    }
}

1.6 速查表

fmt.Println(os.Hostname())             //bogon <nil>(返回内核提供的主机名)
fmt.Println(os.Getpagesize())          //4096(返回底层的系统内存页的尺寸)
fmt.Println(os.Environ())              //(环境变量的格式为"key=value"的字符串的切片拷贝)
fmt.Println(os.Getenv("GOPATH"))       //(名为key的环境变量的值,不存在返回空)
fmt.Println(os.Setenv("Name", "Nick")) //(设置名为key的环境变量)
os.Clearenv()    //(删除所有环境变量)
os.Exit(0)       //(以给出的状态码code退出,程序会立刻终止,defer的函数不会被执行)

fmt.Println(os.Getuid())    //501(调用者的用户ID)
fmt.Println(os.Geteuid())   //501(调用者的有效用户ID)
fmt.Println(os.Getgid())    //20(调用者的组ID)
fmt.Println(os.Getegid())   //20(调用者的有效组ID)
fmt.Println(os.Getgroups()) //[20 701 12 61 79 80 81 ...](调用者所属的所有用户组的组ID)
fmt.Println(os.Getpid())    //62027(调用者所在进程的进程ID)
fmt.Println(os.Getppid())   //62020(调用者所在进程的父进程的进程ID)

fmt.Println(os.Getwd())                      //返回一个对应当前工作目录的根路径
os.Chdir("/tmp/")                            //将当前工作目录修改为dir指定的目录
os.Mkdir("/tmp/test", 0666)                  //用指定的权限和名称创建一个目录
os.MkdirAll("/tmp/test1/test2/test3", 0666)  //使用指定的权限和名称创建一个目录,递归上级目录
os.Rename("/tmp/test", "/tmp/test1/testNew") //修改一个文件的名字,移动一个文件
os.Remove("/tmp/test1/test2/test3")          //删除name指定的文件或目录
os.RemoveAll("/tmp/test1/")                  //删除path指定的文件

os.Create("/tmp/test")      //0666,创建文件,存在会清空
os.Open("/tmp/test")        //O_RDONLY,打开一个文件
os.OpenFile("/tmp/test", os.O_CREATE|os.O_WRONLY, 0666)

// Args保管了命令行参数,第一个是程序名。

10.8.2. 2.os/exec执行命令

如何执行操作系统命令?

比如用户在Linux操作系统中经常需要在终端下执行命令,如何使用编程语言实现在终端下执行命令的效果?

os/exec库提供了这样的功能,可以在代码中执行命令,再进行后续操作,比如根据执行命令的结果再执行其他操作等。

../../_images/image-20220706174924414.png

exec库提供的操作

os/exec库就是用来执行命令的,通常有如下两种用法:

(1)查找命令的目录。

(2)执行命令。

2.1 示例-执行命令

// 执行命令
func OSExecUsage() {
    dockerPath, err := exec.LookPath("docker")
    if err != nil {
        return
    }
    fmt.Println(dockerPath) // /bin/docker

    pwdPath, err := exec.LookPath("pwd")
    if err != nil {
        return
    }
    fmt.Println(pwdPath) // /bin/pwd

    // 1
    cmd := exec.Command("docker", "ps")
    stdout, _ := cmd.StdoutPipe()
    cmd.Start()
    opBytes, err := ioutil.ReadAll(stdout)
    fmt.Println(cmd.Dir, cmd.Path, string(opBytes))

    // 2
    pwd, _ := os.Getwd()
    cmd2 := exec.Command("ls", pwd)
    var buf bytes.Buffer
    cmd2.Stdout = &buf
    cmd2.Run()
    fmt.Println(buf.String())

    // 3
    cmd3 := exec.Command("cat", "log.log")
    out, _ := cmd3.Output()
    //out2, _ := cmd3.CombinedOutput()
    fmt.Println(string(out))

    // 4
    cmd4 := exec.Command("sh", "os.sh")
    out4, _ := cmd4.CombinedOutput()
    fmt.Println(string(out4))

}
  • LookPath:搜索命令的路径,类似linux中的which命令,如果报错,就表示路径不存在。

  • exec.Command:实例化结构体exec.Cmd,一般的实例化都是以New_X开头的。

  • Cmd.Run和Cmd.Start方法可用来执行命令,主要区别是Run会等待命令执行完毕。

  • Cmd.CombinedOutput()和Cmd.Output()可以直接得到命令执行的结果。

同样,支持将执行命令的结果输出到指定的目标或设备,例如将Cmd.Stdout用于赋值操作:

// 1
pwd, _ := os.Getwd()
cmd2 := exec.Command("ls", pwd)
var buf bytes.Buffer
cmd2.Stdout = &buf


// 5
cmd5 := exec.Command("ls", pwd)
stdout5, _ := cmd5.StdoutPipe()
if err := cmd5.Start(); err != nil {
    fmt.Println(err)
}

bytes, err := ioutil.ReadAll(stdout5)

err = ioutil.WriteFile("file.log", bytes, 0644)
if err != nil {
    panic(err)
}

支持外部命令的操作大概就是这些,开发者可以根据自己的需求选择合适的方法。

2.1.1 执行命令不获取返回结果

直接调用 Cmd 对象的 Run 函数,返回的只有成功和失败,获取不到任何输出的结果。

package main

import (
    "log"
    "os/exec"
)

func main() {
    cmd := exec.Command("ls", "-l", "/etc")
    err := cmd.Run()
    if err != nil {
        log.Fatalln(err)
    }
}

2.2.2 执行命令获取返回结果

不区分stdout和stderr

以调用 Cmd 的 CombinedOutput 函数可以获取命令的执行结果。

package main

import (
    "fmt"
    "os/exec"
)

func main() {
    cmd := exec.Command("ls", "-l", "/etc1")
    result, err := cmd.CombinedOutput()
    fmt.Println(string(result))
    if err != nil {
        fmt.Println(err)
    }
}

区分stdout和stderr

下面的执行命令的方式可以明确获得标准错误和标准输出。

package main

import (
    "bytes"
    "fmt"
    "os/exec"
)

func main() {
    // exec执行命令时不能识别下面这种写法的通配符,等同于shell命令行中:
    // ls -l "/var/log/*.log"
    cmd := exec.Command("ls", "-l", "/var/log/*.log")
    var stdout, stderr bytes.Buffer
    cmd.Stdout = &stdout
    cmd.Stderr = &stderr
    err := cmd.Run()
    outStr, errStr := string(stdout.Bytes()), string(stderr.Bytes())
    fmt.Println(outStr)
    fmt.Println(errStr)
    if err != nil {
        fmt.Println(err)
    }
}

2.2.3 使用管道

将上一条命令的结果,做为下一跳命令的参数。作用类似于shell命令中的 |。

package main

import (
    "fmt"
    "os/exec"
)

// 定义结构体,实现 io.Writer 接口
type result struct {
    data []byte
}

func (r *result) Write(p []byte) (n int, err error) {
    fmt.Println(string(p))
    r.data = p
    return len(p), err
}

func main() {
    c1 := exec.Command("grep", "libvirt", "/var/log/dpkg.log")
    c2 := exec.Command("wc", "-l")
    r := &result{}
    c2.Stdin, _ = c1.StdoutPipe()
    c2.Stdout = r
    _ = c2.Start()
    _ = c1.Run()
    _ = c2.Wait()
    fmt.Println(string(r.data))
}

2.2.4 设置命令级别的环境变量

使用os库的Setenv函数来设置的环境变量,是作用于整个进程的生命周期。

package main

import (
    "fmt"
    "log"
    "os"
    "os/exec"
)

func main() {
    os.Setenv("NAME", "Tom")
    cmd := exec.Command("echo", os.ExpandEnv("$NAME"))
    output, err := cmd.CombinedOutput()
    if err != nil {
        log.Fatalln(err)
    }
    fmt.Println(string(output))
}

2.2 Go执行shell命令

1.示例-执行单个命令

go执行shell示例1:

package main

import (
    "fmt"
    "log"
    "os/exec"
)

func main() {
    out, err := exec.Command("date","+%F").Output()
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("The date is %s\n", out)
}

2.示例-执行多个命令

go执行shell示例2:

package main

import (
    "bytes"
    "fmt"
    "log"
    "os/exec"
    "strings"
)

func main() {
    cmd := exec.Command("tr", "a-z", "A-Z")
    cmd.Stdin = strings.NewReader("some input")
    var out bytes.Buffer
    cmd.Stdout = &out
    err := cmd.Run()
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("in all caps: %q\n", out.String())


    cmd2 :=exec.Command("uptime")
    cmd3 :=exec.Command("pwd")
    var out1 bytes.Buffer
    var out2 bytes.Buffer
    cmd2.Stdout = &out1
    cmd3.Stdout = &out2
    err2 :=cmd2.Run()
    err3 :=cmd3.Run()
    if err2 != nil {
        log.Fatal(err2)
    }
    if err3 !=nil{
        log.Fatal(err3)
    }
    fmt.Println(out1.String())
    fmt.Println(out2.String())
}

示例3

package main

import (
    "bufio"
    "fmt"
    "os/exec"
)

func main() {
    cmd0 := exec.Command("echo","-n","My first command comes form golang")

    stdout0,error := cmd0.StdoutPipe()
    if error != nil {
        fmt.Printf("Error: Could't obtain the stdout pipe for command NO.0: %s\n",error)
        return
    }
    if err := cmd0.Start();err !=nil {
        fmt.Printf("Error : the command No.0 can not be startup: %s\n",err)
        return
    }

    //output0 := make([]byte,100)
    //n,err1 := stdout0.Read(output0)
    //if err1 != nil {
    //  fmt.Printf("Error: Cloud't read data form the pipe: %s\n",err1)
    //  return
    //}
    //fmt.Printf("%s\n",output0[:n])

    outputBuf0 := bufio.NewReader(stdout0)
    output0,_,err := outputBuf0.ReadLine()
    if err != nil {
        fmt.Printf("Error: Cloudn't read data from the pipe: %s\n",err)
        return
    }
    fmt.Printf("%s\n",string(output0))
}

3.示例-实现两条命令的管道功能

实现shell命令ps aux|grep sshd

package main

import (
    "bytes"
    "fmt"
    "os/exec"
)

func main() {
    cmd1 := exec.Command("ps aux")
    cmd2 := exec.Command("grep sshd")

    // 设置cmd1的stdout字段,然后启动cmd1,并等待它运行完毕
    var outputBuf1 bytes.Buffer
    cmd1.Stdout = &outputBuf1
    if err := cmd1.Start(); err != nil {
        fmt.Printf("Error: The first command can not be startup %s\n", err)
        return
    }
    if err := cmd1.Wait(); err != nil {
        fmt.Printf("Error: Couldn't wait for the first command:%s\n", err)
        return
    }
    // 设置cmd2的stdin和stdout字段,然后启动cmd2,并等待它运行完毕
    cmd2.Stdin = &outputBuf1
    var outputBuf2 bytes.Buffer
    cmd2.Stdout = &outputBuf2
    if err := cmd2.Start(); err != nil {
        fmt.Printf("Error: The Second command can not be startup %s\n", err)
        return
    }
    if err := cmd2.Wait(); err != nil {
        fmt.Printf("Error: Couldn't wait for the Second command:%s\n", err)
        return
    }
    fmt.Printf("%s\n",outputBuf2.Bytes())

    // 模拟出ps aux|grep sshd
}

4.示例-封装为函数

package main

import (
    "bufio"
    "bytes"
    "fmt"
    "io"
    "os/exec"
)

func main() {
    runCmd()
    fmt.Println()
    runCmdWithPipe()
}

func runCmdWithPipe() {
    fmt.Println("Run command `ps aux | grep apipe`: ")
    cmd1 := exec.Command("ps", "aux")
    cmd2 := exec.Command("grep", "apipe")
    var outputBuf1 bytes.Buffer
    cmd1.Stdout = &outputBuf1
    if err := cmd1.Start(); err != nil {
        fmt.Printf("Error: The first command can not be startup %s\n", err)
        return
    }
    if err := cmd1.Wait(); err != nil {
        fmt.Printf("Error: Couldn't wait for the first command: %s\n", err)
        return
    }
    cmd2.Stdin = &outputBuf1
    var outputBuf2 bytes.Buffer
    cmd2.Stdout = &outputBuf2
    if err := cmd2.Start(); err != nil {
        fmt.Printf("Error: The second command can not be startup: %s\n", err)
        return
    }
    if err := cmd2.Wait(); err != nil {
        fmt.Printf("Error: Couldn't wait for the second command: %s\n", err)
        return
    }
    fmt.Printf("%s\n", outputBuf2.Bytes())
}

func runCmd() {
    useBufferedIO := false
    fmt.Println("Run command `echo -n \"My first command comes from golang.\"`: ")
    cmd0 := exec.Command("echo", "-n", "My first command comes from golang.")
    stdout0, err := cmd0.StdoutPipe()
    if err != nil {
        fmt.Printf("Error: Couldn't obtain the stdout pipe for command No.0: %s\n", err)
        return
    }
    if err := cmd0.Start(); err != nil {
        fmt.Printf("Error: The command No.0 can not be startup: %s\n", err)
        return
    }
    if !useBufferedIO {
        var outputBuf0 bytes.Buffer
        for {
            tempOutput := make([]byte, 5)
            n, err := stdout0.Read(tempOutput)
            if err != nil {
                if err == io.EOF {
                    break
                } else {
                    fmt.Printf("Error: Couldn't read data from the pipe: %s\n", err)
                    return
                }
            }
            if n > 0 {
                outputBuf0.Write(tempOutput[:n])
            }
        }
        fmt.Printf("%s\n", outputBuf0.String())
    } else {
        outputBuf0 := bufio.NewReader(stdout0)
        output0, _, err := outputBuf0.ReadLine()
        if err != nil {
            fmt.Printf("Error: Couldn't read data from the pipe: %s\n", err)
            return
        }
        fmt.Printf("%s\n", string(output0))
    }
}
package main

import (
    "bytes"
    "errors"
    "fmt"
    "io"
    "os"
    "os/exec"
    "os/signal"
    "runtime/debug"
    "strconv"
    "strings"
    "sync"
    "syscall"
    "time"
)

func main() {
    go func() {
        time.Sleep(5 * time.Second)
        sendSignal()
    }()
    handleSignal()
}

func handleSignal() {
    sigRecv1 := make(chan os.Signal, 1)
    sigs1 := []os.Signal{syscall.SIGINT, syscall.SIGQUIT}
    fmt.Printf("Set notification for %s... [sigRecv1]\n", sigs1)
    signal.Notify(sigRecv1, sigs1...)
    sigRecv2 := make(chan os.Signal, 1)
    sigs2 := []os.Signal{syscall.SIGQUIT}
    fmt.Printf("Set notification for %s... [sigRecv2]\n", sigs2)
    signal.Notify(sigRecv2, sigs2...)

    var wg sync.WaitGroup
    wg.Add(2)
    go func() {
        for sig := range sigRecv1 {
            fmt.Printf("Received a signal from sigRecv1: %s\n", sig)
        }
        fmt.Printf("End. [sigRecv1]\n")
        wg.Done()
    }()
    go func() {
        for sig := range sigRecv2 {
            fmt.Printf("Received a signal from sigRecv2: %s\n", sig)
        }
        fmt.Printf("End. [sigRecv2]\n")
        wg.Done()
    }()

    fmt.Println("Wait for 2 seconds... ")
    time.Sleep(2 * time.Second)
    fmt.Printf("Stop notification...")
    signal.Stop(sigRecv1)
    close(sigRecv1)
    fmt.Printf("done. [sigRecv1]\n")
    wg.Wait()
}

func sendSignal() {
    defer func() {
        if err := recover(); err != nil {
            fmt.Printf("Fatal Error: %s\n", err)
            debug.PrintStack()
        }
    }()
    // ps aux | grep "signal" | grep -v "grep" | grep -v "go run" | awk '{print $2}'
    cmds := []*exec.Cmd{
        exec.Command("ps", "aux"),
        exec.Command("grep", "signal"),
        exec.Command("grep", "-v", "grep"),
        exec.Command("grep", "-v", "go run"),
        exec.Command("awk", "{print $2}"),
    }
    output, err := runCmds(cmds)
    if err != nil {
        fmt.Printf("Command Execution Error: %s\n", err)
        return
    }
    pids, err := getPids(output)
    if err != nil {
        fmt.Printf("PID Parsing Error: %s\n", err)
        return
    }
    fmt.Printf("Target PID(s):\n%v\n", pids)
    for _, pid := range pids {
        proc, err := os.FindProcess(pid)
        if err != nil {
            fmt.Printf("Process Finding Error: %s\n", err)
            return
        }
        sig := syscall.SIGQUIT
        fmt.Printf("Send signal '%s' to the process (pid=%d)...\n", sig, pid)
        err = proc.Signal(sig)
        if err != nil {
            fmt.Printf("Signal Sending Error: %s\n", err)
            return
        }
    }
}

func getPids(strs []string) ([]int, error) {
    var pids []int
    for _, str := range strs {
        pid, err := strconv.Atoi(strings.TrimSpace(str))
        if err != nil {
            return nil, err
        }
        pids = append(pids, pid)
    }
    return pids, nil
}

// 返回进程ID列表
func runCmds(cmds []*exec.Cmd) ([]string, error) {
    if cmds == nil || len(cmds) == 0 {
        return nil, errors.New("The cmd slice is invalid!")
    }
    first := true
    var output []byte
    var err error
    for _, cmd := range cmds {
        fmt.Printf("Run command: %v\n", getCmdPlaintext(cmd))
        if !first {
            var stdinBuf bytes.Buffer
            stdinBuf.Write(output)
            cmd.Stdin = &stdinBuf
        }
        var stdoutBuf bytes.Buffer
        cmd.Stdout = &stdoutBuf
        if err = cmd.Start(); err != nil {
            return nil, getError(err, cmd)
        }
        if err = cmd.Wait(); err != nil {
            return nil, getError(err, cmd)
        }
        output = stdoutBuf.Bytes()
        //fmt.Printf("Output:\n%s\n", string(output))
        if first {
            first = false
        }
    }
    var lines []string
    var outputBuf bytes.Buffer
    outputBuf.Write(output)
    for {
        line, err := outputBuf.ReadBytes('\n')
        if err != nil {
            if err == io.EOF {
                break
            } else {
                return nil, getError(err, nil)
            }
        }
        lines = append(lines, string(line))
    }
    return lines, nil
}

func getCmdPlaintext(cmd *exec.Cmd) string {
    var buf bytes.Buffer
    buf.WriteString(cmd.Path)
    for _, arg := range cmd.Args[1:] {
        buf.WriteRune(' ')
        buf.WriteString(arg)
    }
    return buf.String()
}

func getError(err error, cmd *exec.Cmd, extraInfo ...string) error {
    var errMsg string
    if cmd != nil {
        errMsg = fmt.Sprintf("%s  [%s %v]", err, (*cmd).Path, (*cmd).Args)
    } else {
        errMsg = fmt.Sprintf("%s", err)
    }
    if len(extraInfo) > 0 {
        errMsg = fmt.Sprintf("%s (%v)", errMsg, extraInfo)
    }
    return errors.New(errMsg)
}

参考文献:

http://golang.iswbm.com/en/latest/c05/c05_02.html

10.8.3. 3.path

3.1目录操作

关于文件系统的另一个比较重要的操作就是对目录的操作,文件都是置于某个目录下的。开发过程中对文件的操作都伴随着对文件所在目录的操作,比如获取当前目录、文件的目录、绝对路径、相对路径等。

在类UNIX操作系统下,路径都是以“/”分隔的,比如/Users/xiewei/go/src/GopherBook,关于Path的操作包括哪些呢?如图:

../../_images/image-20220706174103232.png

path库提供的操作

1.示例-目录操作

func OSPathUsage() {
    dir, _ := os.Getwd()
    fmt.Println(dir, path.Base(dir))
    fmt.Println(dir, path.Dir(dir))
    parentDir := path.Dir(dir)
    fmt.Println(dir, path.Join(parentDir, "Chapter3"))
}
获取路径最后一个元素:Base。

获取文件路径:Dir。

将路径拆分和组合:Split、Join。

获取文件扩展名:Ext。

路径在本质上是一串带分隔符“/”的字符串,所以这些Base、Dir、Split和Join的函数是对包含特定特征“/”字符(分隔符)的字符串的操作。查看源代码进行验证亦是如此。

func Base(path string) string {
    if path == "" {
        return "."
    }
    // Strip trailing slashes.
    for len(path) > 0 && path[len(path)-1] == '/' {
        path = path[0 : len(path)-1]
    }
    // Find the last element
    if i := lastSlash(path); i >= 0 {
        path = path[i+1:]
    }
    // If empty now, it had only slashes.
    if path == "" {
        return "/"
    }
    return path
}

由此可以看出,其实就是对字符串进行截取操作。

func Join(elem ...string) string {
    size := 0
    for _, e := range elem {
        size += len(e)
    }
    if size == 0 {
        return ""
    }
    buf := make([]byte, 0, size+len(elem)-1)
    for _, e := range elem {
        if len(buf) > 0 || e != "" {
            if len(buf) > 0 {
                buf = append(buf, '/')
            }
            buf = append(buf, e...)
        }
    }
    return Clean(string(buf))
}

同样可以看出是对字符串进行拼接的操作。

Windows下的目录格式和Linux下的目录格式有很大的不同,Path库只处理以“/”作为分隔符的路径操作。如果要对兼容操作系统执行路径操作,那么使用filepath,因为filepath几乎提供了相似的路径操作功能,基本可以完全替代path。

filepath提供了针对非Linux平台的3个函数:

VolumeName获取磁盘名称,比如Windows C盘。

ToSlash将路径分隔符“\\”转换为“/”分隔符。

FromSlash将“/”分隔符的路径转换为带“\\”的分隔符。

如何创建目录、删除目录、对目录进行遍历操作呢?

os.Mkdir/os.MkdirAll:创建目录。

os.Remove/os.RemoveAll:删除目录。

filepath.Walk:遍历目录。

2.示例-目录遍历

// 遍历当前目录下的所有文件
func OSDirUsage() {
    path, _ := os.Getwd()
    filepath.Walk(path, func(path string, info os.FileInfo, err error) error {
        if info.IsDir() {
            return nil
        }
        //fmt.Println("file:", info.Name(), "in directory:", path)

        return nil
    })

}

3.示例-path常见操作

package main

import (
    "fmt"
    "os"
    "path"
)

func main() {
    // Path操作
    fmt.Println("Path操作-----------------")
    fmt.Println(path.Base("http://www.baidu.com/file/aa.jpg")) // aa.jpg
    fmt.Println(path.Clean("c:\\file//abc///aa.jpg"))          // c:\file/abc/aa.jpg
    fmt.Println(os.Getwd())                                    // E:\go_projects\go-gin-example\pkg\file <nil>
    fmt.Println(path.Dir("http://www.baidu.com/aa/aaa.jpg"))   // http:/www.baidu.com/aa
    fmt.Println(path.Dir("c:/a/b/c/d.txt"))                    // c:/a/b/c
    fmt.Println(path.Dir("c:\\a/b.txt"))                       // c:\a
    fmt.Println(path.Ext("c:\\a/b.txt"))                       // .txt
    fmt.Println(path.IsAbs("c:/wind/aa/bb/b.txt"))             // false
    fmt.Println(path.Join("c:", "aa", "bb", "cc.txt"))         // c:/aa/bb/cc.txt
    isMatch, err := path.Match("c:/windows/*/", "c:/windows/system/")
    fmt.Println(isMatch, err)                            // true <nil>
    fmt.Println(path.Split("c:/windows/system/aaa.jpg")) // c:/windows/system/ aaa.jpg
}

10.8.4. 4.filepath

4.1 示例-filepath常见操作

package main

import (
    "fmt"
    "os"
    "path/filepath"
)

func WalkFunc(path string, info os.FileInfo, err error) error {
    fmt.Println("File:", path, "IsDir:", info.IsDir(), "size:", info.Size())
    return nil
}

func main() {
    // FilePath操作
    fmt.Println("FilePath操作-----------------")
    fmt.Println(filepath.IsAbs("c:\\wind\\aa\\bb\\b.txt"))                 // true
    fmt.Println(filepath.Abs("."))                                         // E:\go_projects\go-gin-example\pkg\file <nil>
    fmt.Println(filepath.Base("c:\\aa\\baa.exe"))                          // baa.exe
    fmt.Println(filepath.Clean("c:\\\\aa/c\\baa.exe"))                     // c:\aa\c\baa.exe
    fmt.Println(filepath.Clean("aa/c\\baa.exe"))                           // aa\c\baa.exe
    fmt.Println(filepath.Dir("aa/c\\baa.exe"))                             // aa\c
    fmt.Println(filepath.EvalSymlinks("./path.exe"))                       //可以用来判断文件或文件夹是否存在。 //path.exe <nil>
    fmt.Println(filepath.Ext("./path.exe"))                                // .exe
    fmt.Println(filepath.FromSlash("c:\\windows\\aa//bb/cc//path.exe"))    // 将路径中的\\更换为/ c:\windows\aa\\bb\cc\\path.exe
    fmt.Println(filepath.ToSlash("c:\\windows\\aa/bb/cc/path.exe"))        // 将路径中的/替换为\\  c:/windows/aa/bb/cc/path.exe
    fmt.Println(filepath.VolumeName("c:\\windows\\"))                      // 获取卷标 c:
    fmt.Println(filepath.Glob("c:\\windows\\*.exe"))                     // 获取所有c:\\windows\\目录下exe文件
    fmt.Println(filepath.IsAbs("http://www.baidu.com/aa.jpg"))             // false
    fmt.Println(filepath.Join("a", "\\bb\\", "cc", "/d", "e\\", "ff.txt")) // a\bb\cc\d\e\ff.txt
    fmt.Println(filepath.Match("c:/windows/*/", "c:/windows/system/"))     // true <nil>
    fmt.Println(filepath.Rel("c:/windows", "c:/windows/system/"))       // 取得第二参的路径中,相对于前面的路径的相对路径 system <nil>
    fmt.Println(string(filepath.Separator))                                // windows下返回 \
    fmt.Println(filepath.Split("c:/windows/system/abc.exe"))               // c:/windows/system/ abc.exe
    fmt.Println(filepath.SplitList("c:/windows/system/abc.exe"))           // [c:/windows/system/abc.exe]
    filepath.Walk("./", WalkFunc)  // 遍历目录
}

10.8.5. 5.os实践

os包提供了操作系统的系列函数,这些接口不依赖平台。设计为Unix风格的,错误处理是go风格的;调用失败会返回错误值而非错误码。通常错误值里包含更多信息。

os包的接口在所有操作系统中都是一致的。非公用的属性可以从操作系统特定的syscall包获取。

1.操作系统基本命令

1.1 相关方法

// 获取当前工作目录的根路径
func Getwd() (dir string, err error)

// 将工作目录修改为dir
func Chdir(dir string) error

// 修改name文件或文件夹的权限(对应linux的chmod命令)
func Chmod(name string, mode FileMode) error

// 修改name文件或文件夹的用户和组(对应linux的chmod命令)
func Chown(name string, uid, gid int) error

// 使用指定的权限和名称创建一个文件夹(对于linux的mkdir命令)
func Mkdir(name string, perm FileMode) error

 // 使用指定的权限和名称创建一个文件夹,并自动创建父级目录(对于linux的mkdir -p目录)
func MkdirAll(path string, perm FileMode) error

// 修改一个文件或文件夹的文字(对应linux的mv命令)
func Rename(oldpath, newpath string) error

// 删除指定的文件夹或者目录  ,不能递归删除,只能删除一个空文件夹或一个文件(对应linux的 rm命令)
func Remove(name string) error

 // 递归删除文件夹或者文件(对应linux的rm -rf命令)
func RemoveAll(path string) error

1.2 示例代码

func main() {
    // 为了减少代码的篇幅,基本所有的错误在这篇文字里面我都丢弃
    wd, _ := os.Getwd()
    println("获取当前工作目录的根路径:", wd)

    _ = os.Chdir(path.Join(wd, "go_os/demo1"))
    w, _ := os.Getwd()
    println("获取x修改后的当前工作目录的根路径:", w)

    _ = os.MkdirAll("dirs/dir1", 0777)
    _ = os.Mkdir("dirs/dir2", 0777)
    _ = os.Rename("dirs/dir1", "dirs/dir3")
    _ = os.Remove("dirs/dir2")
    _ = os.RemoveAll("dirs")

}

2.创建、写入、打开、读取文件

2.1 相关方法

 // 创建一个空文件,注意当文件已经存在时,会直接覆盖掉原文件,不会报错
func Create(name string) (file *File, err error)

 // 打开一个文件,注意打开的文件只能读,不能写
func Open(name string) (file *File, err error)

 // 以指定的权限打开文件
func OpenFile(name string, flag int, perm FileMode) (file *File, err error)

2.2 创建和写入文件

func main() {
    wd, _ := os.Getwd()
    file, _ := os.Create(wd + "/go_os/demo1/1.txt")
    defer file.Close()
    println(file.Name())
    file_info,_ := file.Stat()
    fmt.Println(file_info)
    _,_ = file.Write([]byte("hello world!\n"))
    _,_ = file.WriteString("张亚飞")
}

2.3 追加文件

func main()  {
    wd, _ := os.Getwd()
    f, err := os.OpenFile(wd + "/go_os/demo1/1.txt", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0644)
    defer f.Close()
    if err != nil {
        // 打开文件失败处理
        fmt.Println(err)
        return

    }
    content := "\n写入的文件内容"
    _,_ = f.Write([]byte(content))
}

2.4 读取文件

func main(){
    wd, _ := os.Getwd()
    file, _ := os.Open(wd + "/go_os/demo1/1.txt")
    defer file.Close() // 不要忘记关闭文件
    b := make([]byte, 4) // 文件内容不多,我们一次性读4个字节,多读几次,不一次性读完
    var str string
    for {
        n, err := file.Read(b)
        if err != nil {
            if err == io.EOF { // EOF表示文件读取完毕
                break // 退出
            }
        }
        str += string(b[:n]) // 保存文件内容
    }
    println(str) // 打印文件
}

2.5 查看文件信息

func main()  {
    wd, _ := os.Getwd()

    file, _ := os.Open(wd + "/go_os/demo1/1.txt") // 以只读的方式打开文件
    defer file.Close() // 不要忘记关闭文件
    // 获取文件的信息
    fInfo, _ := file.Stat()
    println("是否是一个目录:", fInfo.IsDir())
    println("文件的修改时间:", fInfo.ModTime().String())
    println("文件的名字:", fInfo.Name())
    println("文件的大小:", fInfo.Size())
    println("文件的权限:", fInfo.Mode().String())
    /*
    是否是一个目录: false
    文件的修改时间: 2020-06-17 09:52:05.7987495 +0800 CST
    文件的名字: 1.txt
    文件的大小: 24
    文件的权限: -rw-rw-rw-
    */
}

3.获取操作系统信息

3.1 相关方法

 // 获取主机名
func Hostname() (name string, err error)

 // 获取某个环境变量
func Getenv(key string) string

// 设置一个环境变量,失败返回错误,经测试当前设置的环境变量只在 当前进程有效(当前进程衍生的所以的go程都可以拿到,子go程与父go程的环境变量可以互相获取);进程退出消失
func Setenv(key, value string) error

 // 删除当前程序已有的所有环境变量。不会影响当前电脑系统的环境变量,这些环境变量都是对当前go程序而言的
func Clearenv()

// 让当前程序以给出的状态码(code)退出。一般来说,状态码0表示成功,非0表示出错。程序会立刻终止,defer的函数不会被执行。
func Exit(code int)

// 获取调用者的用户id
func Getuid() int

 // 获取调用者的有效用户id
func Geteuid() int

 // 获取调用者的组id
func Getgid() int

 // 获取调用者的有效组id
func Getegid() int

// 获取调用者所在的所有组的组id
func Getgroups() ([]int, error)

 // 获取调用者所在进程的进程id
func Getpid() int

// 获取调用者所在进程的父进程的进程id
func Getppid() int

3.2 代码实现

func main()  {
    hostname, _ := os.Hostname()
    println("获取主机名,", hostname)

    println("获取gopath环境变量:", os.Getenv("GOPATH"))

    _ = os.Setenv("test", "test") // 设置环境变量
    println("获取上一步设置的test环境变量:", os.Getenv("test"))

    os.Clearenv() // 清除当前程序的所以环境变量
    println("获取清理后的环境变量test和GOPATH:", os.Getenv("test"), os.Getenv("GOPATH"))

    println("获取调用者的用户id", os.Getuid())
    println("获取调用者的有效用户id", os.Geteuid())
    println("获取调用者的组id", os.Getgid())
    println("获取调用者的有效组id", os.Getegid())

    sli, _ := os.Getgroups()
    println("获取调用者所在的所有组的组id", sli) //

    println("获取调用者所在进程的进程id", os.Getpid())
    println("获取调用者所在进程的父进程的进程id", os.Getppid())
    /*
    获取主机名, home-fei
    获取gopath环境变量: E:\go\project
    获取上一步设置的test环境变量: test
    获取清理后的环境变量test和GOPATH:
    获取调用者的用户id -1
    获取调用者的有效用户id -1
    获取调用者的组id -1
    获取调用者的有效组id -1
    获取调用者所在的所有组的组id [0/0]0x0
    获取调用者所在进程的进程id 4968
    获取调用者所在进程的父进程的进程id 11588
     */
}

4.其他

4.1 相关方法

// 退出系统进程
Exit()

// 判断字c是否是一个路径分隔符
func IsPathSeparator(c uint8) bool

// 判断一个错误是否表示一个文件或文件夹是否已存在,ErrExist和一些系统调用错误会使它返回真。
func IsExist(err error) bool

// 判断一个错误是否表示一个文件或文件夹是否不存在,ErrNotExist和一些系统调用错误会使它返回真。
func IsNotExist(err error) bool

// 判断一个错误是否表示权限不足,ErrPermission和一些系统调用错误会使它返回真。
func IsPermission(err error) bool

4.2 示例代码

func exit()  {
    // 模拟条件
    if 1 != 2 {
        println("程序启动失败,xxx条件不满足!")
        os.Exit(1)
    }
    println("程序启动成功!")
}
// 程序启动失败,xxx条件不满足!<br>
func os_path()  {
    print("判断 / \\ : 是否是路径分隔符: ")
    println(os.IsPathSeparator('/'), os.IsPathSeparator('\\'), os.IsPathSeparator(':'))
}<br>// 判断 / \ : 是否是路径分隔符: true true false

5.常用函数

5.1 判断文件或目录是否存在

func main() {
    filePath := "E:/go"
    exist, err := PathIsExist(filePath)
    if err != nil{
        fmt.Printf("PathIsExists(%s),err(%v)\n", filePath, err)
    }
    if exist {
        fmt.Printf("path %s 存在\n",filePath)
    } else {
        fmt.Printf("path %s 不存在\n", filePath)
        err := os.Mkdir(filePath, os.ModePerm)
        if err != nil {
            fmt.Printf("mkdir failed![%v]\n", err)
        } else {
            fmt.Printf("mkdir success!\n")
        }
    }
}

/*
   判断文件或文件夹是否存在
   如果返回的错误为nil,说明文件或文件夹存在
   如果返回的错误类型使用os.IsNotExist()判断为true,说明文件或文件夹不存在
   如果返回的错误为其它类型,则不确定是否在存在
*/
func PathIsExist(filePath string) (bool, error) {
    _, err := os.Stat(filePath)
    if err == nil { return true, nil }
    if os.IsNotExist(err) { return false, nil }
    return false, err
}

5.2 循环创建文件夹

func CreateDir(dirs ...string) (err error) {
    for _, v := range dirs {
        exist, err := PathIsExist(v)
        if err != nil {
            log.Println(fmt.Sprintf("get dir error![%v]\n", err))
            return err
        }
        if exist {
            log.Println(fmt.Sprintf("has dir![%v]\n", v))
        } else {
            log.Println(fmt.Sprintf("no dir![%v]\n", v))
            // 创建文件夹
            err = os.Mkdir(v, os.ModePerm)
            if err != nil {
                log.Println(fmt.Sprintf("mkdir error![%v]\n",err))
            } else {
                log.Println("mkdir success!\n")
            }
        }
    }
    return err
}

5.3 实现拷贝功能

示例1

package main

import (
    "fmt"
    "io"
    "log"
    "os"
    "strings"
)

/* 处理错误 */
func handleError(err error) {
    if err != nil {
        log.Fatal(err)
    }
}

/* 基于Read和Write方法实现拷贝功能 */
func copyFile1(srcFile, destFile string) (int, error) {
    // 打开文件
    src, err := os.Open(srcFile)
    handleError(err)
    dest, err := os.OpenFile(destFile, os.O_CREATE|os.O_WRONLY, 0644)
    handleError(err)

    // 关闭文件
    defer src.Close()
    defer dest.Close()

    var bs = make([]byte, 1024, 1024)
    total := 0
    for {
        // 读取文件
        n, readErr := src.Read(bs)
        if n == 0 || readErr == io.EOF {
            fmt.Println("拷贝完成...")
            break
        }
        handleError(readErr)

        // 写入文件
        _, writeErr := dest.Write(bs[:n])
        handleError(writeErr)
        total += n
        fmt.Printf("已经拷贝了 %d 字节数据\n", total)
    }
    return total, nil
}

/* 使用io.Copy() 方法实现文件拷贝 */
func copyFile2(srcFile, destFile string) (int64, error) {
    // 打开文件
    src, err := os.Open(srcFile)
    handleError(err)
    dest, err := os.OpenFile(destFile, os.O_CREATE|os.O_WRONLY, 0644)
    handleError(err)

    // 关闭文件
    defer src.Close()
    defer dest.Close()

    // 拷贝文件
    return io.Copy(dest, src)
}

func main() {
    srcFileName := "C:\\Users\\T470P\\Desktop\\goland\\1.jpg"
    destFileName := srcFileName[strings.LastIndex(srcFileName, "\\")+1:]
    fmt.Println(destFileName)

    total1, err := copyFile1(srcFileName, destFileName)
    fmt.Println(total1, err)

    total2, err := copyFile2(srcFileName, destFileName)
    handleError(err)
    fmt.Printf("一共拷贝了 %d 字节数据\n", total2)
}

示例2

package main

import (
    "fmt"
    "io"
    "os"
)

func copyFile(srcFile, destFile string) (int64, error) {
    file1, err := os.Open(srcFile)
    if err != nil {
        return 0, err
    }
    file2, err := os.OpenFile(destFile, os.O_RDWR|os.O_CREATE, os.ModePerm)
    if err != nil {
        return 0, err
    }
    defer file1.Close()
    defer file2.Close()
    return io.Copy(file2, file1)
}
func main() {
    srcFile := "blockchain.txt"
    destFile := "blockchain.txt_2022"
    total, err := copyFile(srcFile, destFile)
    if err != nil {
        fmt.Println(err.Error())
    } else {
        fmt.Println("复制ok", total)
    }

}

5.4 拷贝文件(显示百分比)

package main

import (
    "fmt"
    "io"
    "io/ioutil"
    "log"
    "os"
    "time"
)

/*
实现文件拷贝功能,并在控制台打印拷贝数据的百分比.
*/

const (
    Format1 = "\b\b%d%%"
    Format2 = "\b\b\b%d%%"
    Format3 = "\b\b\b%d%%\b"
)

// rate 打印百分比
func rate(ch chan int64) {
    format := Format1
    var lastV int64 = 0
    for v := range ch {
        if lastV > 10 && v > 10 && v < 100 {
            format = Format2
        } else if v >= 100 {
            v = 100
            format = Format3
        }
        fmt.Printf(format, v)
        lastV = v
    }
}

func main() {
    // 1.打开文件
    file, err := os.Open("./src/11.png")
    if err != nil {
        log.Fatalln(err)
    }
    // 获取文件信息(fileInfo.Size())
    fileInfo, err := file.Stat()
    if err != nil {
        log.Fatalln(err)
    }
    // 定义channel
    var fsChan = make(chan int64)
    defer close(fsChan)

    var fs = make([]byte, 0)
    fmt.Print("rate:0%")
    go rate(fsChan)

    // 2.循环读取文件,数据填充到fs中,
    // 通过fs的长度计算已经读取的文件的百分比
    for {
        buf := make([]byte, 10240)
        n, err := file.Read(buf)
        if n == 0 {
            break
        }
        if err == io.EOF {
            log.Println("file read over!")
            break
        } else if err != nil {
            log.Fatalln("read file failed, err:", err)
        }
        fs = append(fs, buf...)
        time.Sleep(time.Second)
        go func() {
            fsChan <- int64(len(fs) * 100) / fileInfo.Size()
        }()
    }
    // 3.写入文件
    ioutil.WriteFile("./dst/11.png", fs, 0644)
}

5.5 添加断点续传功能

package main

import (
    "fmt"
    "io"
    "log"
    "os"
    "strconv"
    "strings"
)

// 处理错误
func handleError(err error) {
    if err != nil {
        log.Fatal(err)
    }
}

// 拷贝文件,实现断点续传功能
func copyFile(srcFile, destFile string) (int, error) {
    // 打开文件
    src, err := os.Open(srcFile)
    handleError(err)
    dest, err := os.OpenFile(destFile, os.O_CREATE|os.O_WRONLY, 0644)
    handleError(err)
    // 保存拷贝进度临时文件
    tmpFile := destFile + "tmp.txt"
    fmt.Println(tmpFile)
    tmp, err := os.OpenFile(tmpFile, os.O_CREATE|os.O_RDWR, 0644)
    handleError(err)

    // 关闭文件
    defer src.Close()
    defer dest.Close()

    // 读取tmp,获取拷贝起点
    tmp.Seek(0, io.SeekStart)
    bs := make([]byte, 1024, 1024)
    n1, err := tmp.Read(bs)
    if err != io.EOF {  // 第一次拷贝,tmp文件为空,读取会有EOF错误,忽略即可.
        handleError(err)
    }
    countStr := string(bs[:n1])  // byte 转 string
    count, err := strconv.ParseInt(countStr, 10, 64)  // string 转 int64
    fmt.Println(count)

    // 设置读取起点
    src.Seek(count, io.SeekStart)
    //设置写入起点
    dest.Seek(count, io.SeekStart)

    // 读取文件切片
    data := make([]byte, 1024, 1024)

    // 已读取的总量
    total := int(count)  // 转 int

    for {
        // 读取文件数据
        n, readErr := src.Read(data)
        if n == 0 || readErr == io.EOF {
            fmt.Println("文件拷贝完了...")
            tmp.Close()  // 关闭临时文件
            os.Remove(tmpFile)  // 删除临时文件
            break
        }
        handleError(readErr)

        // 写入文件
        _, writeErr := dest.Write(data[:n])
        handleError(writeErr)

        // 记录拷贝到了哪里
        total += n
        fmt.Printf("已经拷贝了%d字节的数据\n", total)
        tmp.Seek(0, io.SeekStart)
        _, tmpErr := tmp.WriteString(strconv.Itoa(total))
        handleError(tmpErr)

        //if total > 3276800 {
        //  panic("模拟拷贝数据中断...")
        //}
    }
    return total, nil
}

func main() {
    srcFileName := "C:\\Users\\T470P\\Desktop\\goland\\wei.mp4"
    destFileName := srcFileName[strings.LastIndex(srcFileName, "\\")+1:]
    fmt.Println(destFileName)

    total, err := copyFile(srcFileName, destFileName)
    handleError(err)
    fmt.Printf("拷贝成功,文件一共%d字节\n", total)
}

10.8.6. 6.参考文献

https://www.cnblogs.com/zhangyafei/p/13151697.html