Go文件处理

一、操作目录

Go语言对文件和目录操作,主要通过os包和path包实现。

1、创建目录

Go语言创建目录,主要使用Mkdir()MkdirAll() 两个函数。其中Mkdir() 函数的定义如下:

func Mkdir(name string, perm FileMode) error {

}

其中,name是需要创建的目录名字,perm 为权限设置码。比如perm为0700,表示该目录对所有用户都可读写及可执行

例如,创建一个名为”test”的目录,perm权限为0700的示例如下:

package main

import (
	"log"
	"os"
)
func main(){
        // 创建一个名为"test"的目录,权限为777
	err := os.Mkdir("test", 777)
	if err != nil {
		log.Panicln(err)
	}

}

MkdirAll()函数的定义如下:

func MkdirAll(path string, perm FileMode) error {
}

其中path为目录的路径(例如 “dir1/dir2/dir3”),perm为权限设置码

用MkdirAll()函数创建目录的示例如下:

package main

import (
	"log"
	"os"
)
func main(){
        // 根据path创建多级子目录
	err := os.MkdirAll("test1/test2/test3", 777)
	if err != nil {
		log.Panicln(err)
	}
}

多级目录一般用的比较多的地方是上传文件,例如可以创建一个目录结构为”static/upload/2020/10/1”的多级目录来保存上传的文件。

package main

import (
	"log"
	"os"
	"time"
)

func main() {
        
	uploadPath := "static/upload/" + time.Now().Format("2006/01/02")
	err := os.MkdirAll(uploadPath, 777)
	if err != nil {
		log.Panicln(err)
	}
}
// 创建成功后可以查看目录结构
[root@newperiodical ~]# ll static/
total 0
dr----x--x 3 root root 18 Aug 25 10:10 upload
[root@newperiodical ~]# ll static/upload/
total 0
dr----x--x 3 root root 16 Aug 25 10:10 2023
[root@newperiodical ~]# 
[root@newperiodical ~]# ll static/upload/2023/08/
total 0
dr----x--x 2 root root 6 Aug 25 10:10 25
[root@newperiodical ~]# ll static/upload/2023/08/25/
total 0
2、重命名目录

在Go中的os包邮一个Rename()函数用来对目录和文件进行重命名。该函数也可以用于移动一个文件。该函数定义为:

func Rename(oldpath, newpath string) error {
	return rename(oldpath, newpath)
}

其中,参数oldpath 为旧的目录名或多级目录的路径,参数newpath为新目录的路径。如果newpath已经存在,则替换它。

package main

import (
	"log"
	"os"
)

func main() {
        // 创建一个名为"dir_name1" 的目录,权限为777
	err := os.Mkdir("dir_name1", 777)
	if err != nil {
		log.Panicln(err)
	}
	oldName := "dir_name1"
	newName := "dir_new"
        // 将dir_name1 重命名为dir_new
	err = os.Rename(oldName, newName)
	if err != nil {
		log.Panicln(err)
	}
}

mac电脑可能会报权限错误

2023/08/25 10:40:13 rename dir_name1 dir_new: permission denied
panic: rename dir_name1 dir_new: permission denied

可以使用exec包来执行命令

package main

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

func main() {

	if _, err := os.Stat("dir_name1"); err == nil {
		fmt.Println("原目录已存在不需要创建")
	} else {
		err := os.Mkdir("dir_name1", 777)
		if err != nil {
			log.Panicln(err)
		}
	}
	oldName := "dir_name1"
	newName := "dir_new"
	cmd := exec.Command("sudo", "mv", oldName, newName)
	cmd.Stdin = os.Stdin
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	err := cmd.Run()
	if err != nil {
		log.Panicln(err)
	}
}
3、删除目录

go删除目录的函数定义如下:

func Remove(name string) error {
}

其中,参数name为目录的名字。Remove()函数有一个局限性:当目录下有文件或其他目录时会出错。如下

2023/08/25 10:42:46 remove dir: directory not empty
panic: remove dir: directory not empty

如果要删除多级目录,则可以使用RemoveAll()函数

func RemoveAll(path string) error {
	return removeAll(path)
}

其中,参数path为要删除的多级子目录。如果path是单个名称,则该目录下的子目录将全部被删除

err := os.RemoveAll("dir")
if err != nil {
	log.Panicln(err)
}
4、遍历目录

在go语言的path/filepath包中,提供了Walk()函数来遍历目录

func Walk(root string, fn WalkFunc) error {
	info, err := os.Lstat(root)
	if err != nil {
		err = fn(root, nil, err)
	} else {
		err = walk(root, info, fn)
	}
	if err == SkipDir {
		return nil
	}
	return err
}

其中,参数root为遍历的初始根目录,参数walkFn为自定义函数(例如,显示所有文件夹、文件、子文件)用Walk()函数遍历目录的示例如下:

package main

import (
	"fmt"
	"io/fs"
	"path/filepath"
)

func scan(path string, info fs.FileInfo, err error) error {
	fmt.Printf("Scan:%s\n", path)     
	fmt.Printf("%v\n", info.IsDir())   // 是否是目录
	fmt.Printf("%v\n", info.Name())    // 文件或目录名
	fmt.Printf("%v\n", info.Mode())    // 文件或目录权限
	fmt.Printf("%v\n", info.ModTime()) // 创建时间
	fmt.Printf("%v\n", info.Size())    //
	fmt.Println("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~")
	return nil
}

func main() {
	root := "./test_walk"
	err := filepath.Walk(root, scan)
	fmt.Printf("filepath.Walk() returned %v \n", err)
}
// 输出
Scan:./test_walk
true
test_walk
drwxr-xr-x
2023-08-25 10:48:38.415947217 +0800 CST
96
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Scan:test_walk/dir2
true
dir2
drwxr-xr-x
2023-08-25 10:48:43.947354911 +0800 CST
96
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Scan:test_walk/dir2/dir3
true
dir3
drwxr-xr-x
2023-08-25 10:55:53.152220343 +0800 CST
96
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Scan:test_walk/dir2/dir3/test
false
test
-rw-r--r--
2023-08-25 10:55:53.152114752 +0800 CST
665464
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

将需要遍历的目录下的所有文件移动到static目录中

package main

import (
	"fmt"
	"io/fs"
	"os"
	"os/exec"
	"path/filepath"
)

func scan(path string, info fs.FileInfo, err error) error {
	fmt.Printf("Scan:%s\n", path)
	fmt.Printf("%v\n", info.IsDir())   // 是否是目录
	fmt.Printf("%v\n", info.Name())    // 文件或目录名
	fmt.Printf("%v\n", info.Mode())    // 文件或目录权限
	fmt.Printf("%v\n", info.ModTime()) // 创建时间
	fmt.Printf("%v\n", info.Size())    //
	fmt.Println("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~")
        // 判断是否是目录,如果不是则将文件移动
	if !info.IsDir() {
		cmd := exec.Command("mv", path, "static")
		cmd.Stdout = os.Stdin
		cmd.Stderr = os.Stderr
		cmd.Stdin = os.Stdin
		err = cmd.Run()
		return err
	}
	return nil
}

func main() {
	root := "./test_walk"
	err := filepath.Walk(root, scan)
	fmt.Printf("filepath.Walk() returned %v \n", err)

二、文件操作

2.1、创建文件

Go语言os包中提供了Create()函数来创建文件,其定义如下:

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

其中,参数name为文件名字的字符串,返回值为指针型文件描述符

用Create()函数创建一个名为name的文件,默认采用666.如果文件存在,则他会被重置为空文件,如果成功,则返回文件描述符对象,可用于文件的读写

func main() {

    // 创建文件,Create()函数会根据传入的文件名创建文件,默认权限是666
    file, err := os.Create("./demo.test") // 如果已经存在则将文件清空
    if err != nil {
        // 创建文件失败的原因有:
        // 1、路径不存在,2、权限不足 3、打开文件数量超过上限 4、磁盘空间不足等
        log.Panicln(err)
    }
    fmt.Println(file)
    defer file.Close()  // 关闭文件,释放资源
}
2.2、打开与关闭文件

在go语言的os包中提供了Open()函数和OpenFile()函数用来打开文件。在Open()OpenFile() 函数使用完毕后,必须调用Close()方法来关闭文件

####### 1、Open()函数
文件的打开使用os包中的Open() 函数,其定义如下:

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

其中参数name为文件名字的字符串,返回值为文件描述符对象。

文件关闭用Close()方法

func (f *FIle) Close() error{
}

其中,参数f为文件描述符指针;Close() 方法可使文件不能用于读写,他的返回值为可能出现的错误。

file, err := os.Open("demo.test")
if err != nil {
	fmt.Printf("打开文件出错%v\n", err)
}
fmt.Println(file)
defer file.Close()

如果在代码所在文件夹中没有名为demo.test的文件,则报如下错误:

打开文件出错open demo.tests: no such file or directory
<nil>
2、OpenFile()函数

OpenFile()函数比Open()函数更加强大,可以定义文件的名字、文件打开方式,以及文件权限设置,其定义如下:

	file, err := os.OpenFile("demo.test", os.O_APPEND|os.O_CREATE, 777)
	if err != nil {
		fmt.Println("文件打开失败", err)
	}
	defer file.Close()

// 所有权限
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.  // 和create搭配使用,文件必须不存在
	O_SYNC   int = syscall.O_SYNC   // open for synchronous I/O.  
	O_TRUNC  int = syscall.O_TRUNC  // truncate regular writable file when opened.   // 清空文件
)
3、读写文件

####### 3.1、读文件
读文件有如下两种函数

  1. 用带缓冲方式读取

这种方式使用bufio包中的NewReader()函数

func NewReader(rd io.Reader) *Reader {
	return NewReaderSize(rd, defaultBufSize)
}
// 打开文件
file, err := os.OpenFile("demo.test", os.O_APPEND|os.O_CREATE, 777)
if err != nil {
	fmt.Println("文件打开失败", err)
}

defer file.Close()
reader := bufio.NewReader(file)
for {
	line, err := reader.ReadString('\n') // 读取一个换行符就结束
	if err == io.EOF {                   // io.EOF 表示文件的末尾
		break
	}
	// 输出每行内容
	fmt.Print(line)
}
  1. 直接读取到内存(ioutil.ReadFile)

如果想将文件直接读取到内存中,则可以使用io/ioutil 包中的ReadFile() 函数,其定义如下:

func ReadFile(name string) ([]byte, error) {
	filePath := "demo.test"
	content, err := ioutil.ReadFile(filePath)
	if err != nil {
		log.Panicln(err)
	}
	fmt.Println(content)
	fmt.Println(string(content))
// 输出
[231 172 172 228 184 128 232 161 140 97 98 99 10 231 172 172 228 186 140 232 161 140 100 101 114 10 231 172 172 228 184 137 232 161 140 99 99 99 10 100 100 100 10]
第一行abc
第二行der
第三行ccc
ddd

####### 3.2、写文件
Go语言中os包中提供了一个名为File的对象来处理文件,该对象有Write()WriteAt()WriteString() 3种方法可以用于写文件

  1. Write()方法

Write() 方法用于写入[]byte 类型的信息到文件中,其定义如下

file, err := os.OpenFile("demo.test", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 777)
if err != nil {
	log.Panicln(err)
}
defer file.Close()

n, err := file.Write([]byte("你好世界!"))
if err != nil {
	log.Panicln(err)
}
fmt.Println(n)
  1. WriteAt()方法

WriteAt()方法用于在指定位置开始写入[]byte 类型的信息,其定义如下:

file, err := os.OpenFile("demo.test", os.O_CREATE|os.O_WRONLY, 777)
if err != nil {
	log.Panicln(err)
}
defer file.Close()
n, err := file.WriteAt([]byte("测试一下"), 3)
if err != nil {
	log.Panicln(err)
}
fmt.Println(n)

该方法表示从基本输入源的偏移量off处开始,将len(p)个字节读取到p中。他返回读取的字节数n(0<= n <=len(p)),以及任何遇到的错误

  1. WriteString()方法

WriteString()方法用于将字符串写入文件,其定义如下:

func (f *File) WriteString(s string) (n int, err error) {

其中参数s为string类型的字符串。

file, err := os.OpenFile("demo.test", os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 777)
if err != nil {
	log.Panicln(err)
}
defer file.Close()
n, err := file.WriteString("我擦擦擦擦擦擦")
if err != nil {
	log.Panicln(err)
}
fmt.Println(n)

WriteString() 方法的本质上是对 Write() 方法的调用。WriteString()方法的返回值就是Write()的返回值。WriteString()的方法体如下:

func (f *File) WriteString(s string) (n int, err error) {
	var b []byte
	hdr := (*unsafeheader.Slice)(unsafe.Pointer(&b))
	hdr.Data = (*unsafeheader.String)(unsafe.Pointer(&s)).Data
	hdr.Cap = len(s)
	hdr.Len = len(s)
	return f.Write(b)
}

WriteString() 方法和 Write() 方法的区别是参数形式:WriteString() 方法的参数是字符串,Write()方法的参数是[]byte(s)

fout, err := os.Create("demo.test")
if err != nil {
	log.Panicln(err)
}
defer fout.Close()
for i := 0; i < 5; i++ {
	outstr := fmt.Sprintf("%s:%d\r\n", "Hello Go", i)  // sprintf格式化
	fout.WriteString(outstr) // string 信息
	fout.Write([]byte("i love go \r\n"))  // byte 类型
}
2.3、移动与重命名文件

Go语言的移动和重命名可以通过Rename()函数实现,其参数既可以是目录也可以是文件。

定义如下:

// 创建一个名为demo.test 的文件
fout, err := os.Create("demo.test")
if err != nil {
	log.Panicln(err)
}
defer fout.Close()
// 创建一个名为fuck的目录,权限为777
err = os.Mkdir("fuck", 777)
if err != nil {
	log.Panicln(err)
}
// 将demo.test 文件移动到fuck目录下,并改名为demo.txt
err = os.Rename("demo.test", "fuck/demo.txt")
if err != nil {
	log.Panicln(err)
}
2.4、删除文件

和删除目录一样,在Go语言中删除文件也可以通过Remove()函数和RemoveAll()函数来实现

  1. Remove()函数

Remove()函数用于删除指定的文件或目录,如果出错,则返回*PathError类型的错误,其定义为

func Remove(name string) error {
}
  1. RemoveAll()函数

RemoveAll() 函数用于删除指定的文件或目录及它的所有下级对象。他会尝试删除所有内容,除非遇到错误并返回。如果参数path指定的对象不存在,则RemoveAll() 会返回nil,而不返回错误

func RemoveAll(path string) error {
	return removeAll(path)
}
// 创建一个名为demo.test 的文件
fout, err := os.Create("demo.test")
if err != nil {
	log.Panicln(err)
}
defer fout.Close()
// 创建一个名为fuck的目录,权限为777
err = os.Mkdir("fuck", 777)
if err != nil {
	log.Panicln(err)
}
// 将demo.test 文件移动到fuck目录下,并改名为demo.txt
err = os.Rename("demo.test", "fuck/demo.txt")
if err != nil {
	log.Panicln(err)
}
err = os.RemoveAll("fuck/demo.txt")
if err != nil {
	log.Panicln(err)
}
2.5、复制文件

在Go语言中,可以使用io包的Copy() 函数来实现文件复制功能,定义如下

func Copy(dst Writer, src Reader) (written int64, err error) {
	return copyBuffer(dst, src, nil)
}

其中,参数dst为源文件指针,参数src为目标文件指针。

	src, err := os.OpenFile("test.zip", os.O_CREATE|os.O_WRONLY, 777)
	if err != nil {
		log.Panicln(err)
	}
	defer src.Close()
	dst, err := os.OpenFile("test.zip.bak", os.O_CREATE|os.O_WRONLY, 777)
	if err != nil {
		log.Panicln(err)
	}
	defer dst.Close()
	result, err := io.Copy(dst, src)
	if err == nil {
		fmt.Println("复制成功,复制的字节数为:", result)
	}
	fmt.Println(err)




// 这里用exec

[root@newperiodical ~]# cat test.go 
package main
import (
    "log"
    "os"
    "fmt"
    "os/exec"
)

func main() {
                src, err := os.OpenFile("test.txt", os.O_CREATE|os.O_WRONLY, 777)
        if err != nil {
                log.Panicln(err)
        }
        dst, err := os.OpenFile("test.txt.bak", os.O_CREATE|os.O_WRONLY, 777)
        if err != nil {
                log.Panicln(err)
        }
        cmd := exec.Command("cp", "-rp", "test.txt", "test.txt.bak")
        cmd.Stdout = os.Stdout
        cmd.Stdin = os.Stdin
        cmd.Stderr = os.Stderr
        err = cmd.Run()
        if err != nil {
                log.Panicln(err)
        }
        fmt.Println(err)
        defer dst.Close()
        defer src.Close()
}

除此之外,还可以自己封装成一个函数:先通过使用os包中的os.Open()os.Create() 函数获取文件句柄(文件指针),然后通过文件指针的Read()Write()方法,按照字节读取和写入来实现复制文件的功能。

把复制文件封装成一个公共函数,以便在以后每次需要用到该功能时,直接调用封装好的函数。对于较大文件,可以自定义一个名为DoCopy()的函数,如下:

func DoCopy(srcFileName, dstFileName string) {
	srcFile, err := os.Open(srcFileName)
	if err != nil {
		log.Fatalf("源文件读取失败,err:%v\n", err)
	}
	defer func() {
		err = srcFile.Close()
		if err != nil {
			log.Fatalf("源文件关闭失败,err:%v\n", err)
		}
	}()
	distFile, err := os.Create(dstFileName)
	if err != nil {
		log.Fatalf("目标文件创建失败,err:%v\n", err)
	}
	defer func() {
		err := distFile.Close()
		if err != nil {
			log.Fatalf("目标文件关闭失败,err:%v\n", err)
		}
	}()
	// 指定长度的字节切片,每次最多读取指定长度
	var tmp = make([]byte, 1024*4)
	// 循环读取并写入
	for {
		n, err := srcFile.Read(tmp)
		n, _ = distFile.Write(tmp[:n])
		if err != nil {
			if err == io.EOF {
				return
			} else {
				log.Fatalf("复制过程发生错误,错误err:%v\n", err)
			}
		}
	}
}
func main() {
	DoCopy("test.txt", "shit.txt")
}

或者

func FileCopy(dstStr, srcStr string) error {
	dst, err := os.OpenFile(dstStr, os.O_CREATE|os.O_RDWR, 777)
	if err != nil {
		return err
	}
	defer dst.Close()
	src, err := os.Create(srcStr)
	if err != nil {
		return err
	}
	defer src.Close()
	reader := bufio.NewReader(dst)
	for {
		str, err := reader.ReadString('\n')
		if err == io.EOF {
			break
		}
		fmt.Print(str)
		n, err := src.WriteString(str)
		if err != nil {
			log.Panicln(err)
		}
		fmt.Printf("已更新%d\n", n)
	}

	return nil
}
func main() {

	err := FileCopy("test.txt", "fuck.txt")
	if err != nil {
		log.Panicln(err)
	}
}
2.6、修改文件权限
1、Linux中的文件权限
  1. linux中的文件权限有以下设定

    • 文件的权限类型一般包括读、写、执行(对应字母为r、w、x)
    • 权限的属组拥有者、群组、其他组这3种。每个文件都可以针对这3个属组(粒度),设置不同的r、w、x(读、写、执行)权限
    • 通常情况下,一个文件只能归属与一个用户和组。如果其他的用户想具有这个文件的权限,则可以将用户加入具备权限的群组。一个用户可以同时归属与多个组。
  2. 十位二进制表示法

-rwxrwxrwx(777)

以上权限表示所有用户(拥有者、所在群组的用户、其他组的用户)都有这个文件的读、写、执行权限。

**① **在十位二进制表示法中,第一位表示的是文件的类型,类型可以是下面几个中的一个

  • d:目录(directory)
  • -:文件(regular file)
  • s:套接字(socket)
  • p:管道文件(pipe)或命名管道文件(named pipe)
  • l:符号链接文件(symbolic link)
  • b:该文件是面向块的设备文件
  • c:该文件是面向字符的设备文件

②:在十位二进制表示法中,后9位每个位置的意义(代表某个属组的某个权限)都是固定的。如果将各个位置权限的有无用二进制数1和0来代替,则只读、只写、只执行权限可以用3位二进制数表示:

r--  => 100  4
-w-  => 010  2
--x  => 001  1
---  => 000  0

转换成八进制数,则为r=4,w=2,x=1 (这也就是在用数字设置权限时,为何4代表读,2代表写,1代表执行)

可以将所有的权限用二进制形式表现出来,并进一步转换成八进制数字

rwx = 111  = 7
rw- = 110  = 6
r-x = 101  = 5
-wx = 011  = 3
-w- = 010  = 2
--x = 001  = 1
--- = 000  = 0

由上可以看出,每个属组的所有的权限都可以用1位八进制数表示,每个数字都代表不同的权限。如最高的权限是7,则代表可读、可写、可执行。

2、修改文件权限

在Go语言中,可使用os.Chmod()方法来修改文件的权限,该方法是对操作系统权限控制的一种封装。

func Chmod(name string, mode FileMode) error { return chmod(name, mode) }

其中参数f为文件指针。如果出错,则返回底层错误类型*PathError。用Chmod()方法修改文件权限的实例如下:

	file, err := os.Create("chmod1.txt")
	if err != nil {
		log.Panicln(err)
	}
	info, err := os.Stat(file.Name())
	mode := info.Mode()
	fmt.Println(mode)
	os.Chmod(file.Name(), 0777)
	fileinfo, _ := os.Stat(file.Name())
	filemode := fileinfo.Mode()
	fmt.Println(filemode)
//输出
-r----x--x
-rwxrwxrwx