文件模式 Go语言的文件模式和 0777 权限常量定义在 os 包(事实上在io/fs包)中,如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 type FileMode uint32 const ( ModeDir FileMode = 1 << (32 - 1 - iota ) ModeAppend ModeExclusive ModeTemporary ModeSymlink ModeDevice ModeNamedPipe ModeSocket ModeSetuid ModeSetgid ModeCharDevice ModeSticky ModeType = ModeDir | ModeSymlink | ModeNamedPipe | ModeSocket | ModeDevice ModePerm FileMode = 0777 )
Go语言定义了 FileInfo 接口来描述文件信息,FileInfo 接口定义如下:
1 2 3 4 5 6 7 8 type FileInfo interface { Name() string Size() int64 Mode() FileMode ModTime() time.Time IsDir() bool Sys() interface {} }
func Stat(name string) (fi FileInfo, err error) 函数获取文件名name的 FileInfo 信息。如果出错,返回的错误值为*PathError类型。
func (f *File) Stat() (fi FileInfo, err error) 用于获取打开的文件的 FileInfo 信息。
func (f *File) Name() string 可直接返回已打开文件的文件名。
1 2 3 4 5 6 7 8 func main () { fi, _ := os.Stat("./src" ) fmt.Printf("src stat: %+v\n" , fi) } src stat: &{name:src FileAttributes:16 CreationTime:{LowDateTime:4178585970 HighDateTime:30952027 } LastAccessTime:{LowDateTime:440928839 HighDateTime:30952057 } LastWriteTime:{LowDateTime:1431422461 HighDateTime:30952056 } FileSizeHigh:0 FileSizeLow:0 Reserved0:0 filetype:0 Mutex:{state:0 sema:0 } path:D:\GoPram\src\demo\Learnfile\src vol:0 idxhi:0 idxlo:0 appendNameToPath:false }
文件操作失败的错误类型 Go语言为因路径问题导致的失败定义了 PathError 类型,PathError 记录一个错误,以及导致错误的路径。
1 2 3 4 5 6 7 type PathError struct { Op string Path string Err error } func (e *PathError) Error () string
对于链接操作和重命名操作定义了 LinkError 类型,LinkError 记录在 Link、Symlink、Rename 系统调用时出现的错误,以及导致错误的路径。
1 2 3 4 5 6 7 8 type LinkError struct { Op string Old string New string Err error } func (e *LinkError) Error () string
创建文件夹 使用 Mkdir(name string, perm FileMode) 函数根据指定的目录名 name 和权限 perm 创建一个目录,如果出错,会返回 *PathError 类型的错误。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 package mainimport ( "fmt" "log" "os" ) func main () { err := os.Mkdir("temp" , 0666 ) if err != nil { log.Println("创建目录 temp 失败, err:" , err.Error()) } else { fmt.Println("创建目录 temp 成功" ) } } PS D:\GoPram\src\demo\Learnfile> go run .\dir_file.go 创建目录 temp 成功 PS D:\GoPram\src\demo\Learnfile> ls Mode LastWriteTime Length Name ---- ------------- ------ ---- d----- 2022 /4 /7 16 :32 temp -a---- 2022 /4 /7 16 :32 242 dir_file.go -a---- 2022 /4 /7 16 :30 4081 os.go
使用 Mkdir(name string, perm FileInfo) 只能在当前工作目录下创建新目录,若需要递归创建多层目录需要使用 MkdirAll(path string, perm FileInfo) 函数。权限位perm会应用在每一个被本函数创建的目录上。如果path指定了一个已经存在的目录,MkdirAll不做任何操作并返回nil。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 func main () { err := os.MkdirAll("./src/susu/wei" , os.ModePerm) if err != nil { log.Println("创建目录 ./src/susu/wei 失败, err:" , err.Error()) } else { fmt.Println("创建目录 ./src/susu/wei 成功" ) } } PS D:\GoPram\src\demo\Learnfile> go run .\dir_file.go 创建目录 ./src/susu/wei 成功 PS D:\GoPram\src\demo\Learnfile> cd ./src/susu PS D:\GoPram\src\demo\Learnfile\src\susu> ls Mode LastWriteTime Length Name ---- ------------- ------ ---- d----- 2022 /4 /7 16 :46 wei
打开文件的flag 在操作文件之前先介绍下操作文件的模式(OpenFile flag)即指定以什么样的方式操作文件(如只读,只写,读写,追加写…)
Go语言在 os 包定义了如下部分flag常量
1 2 3 4 5 6 7 8 9 10 11 12 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_SYNC int = syscall.O_SYNC O_TRUNC int = syscall.O_TRUNC )
多个控制行为可通过按位或运算 | 来添加
1 2 OpenFile("suwei.go" , os.O_RDWR | O_APPEND, 0 )
创建文件 没有专门用于创建文件的函数,可以使用 OpenFile(name string, flag int, perm FileInfo) 函数,调用时传递 O_CREATE flag 打开一个不存在的 name 文件,便会以 perm 权限创建 name文件,并返回此文件的文件指针和nil,如果出错,会返回 *PathError 类型的错误。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 func main () { suwei, err := os.OpenFile("./src/susu/suwei.go" , os.O_RDONLY|os.O_CREATE, 0666 ) if err != nil { fmt.Println("创建./src/susu/suwei.go失败, err:" , err.Error()) } else { mesg, _ := suwei.Stat() fmt.Printf("%+v" , mesg) } } &{name:suwei.go FileAttributes:32 CreationTime:{LowDateTime:2659761341 HighDateTime:30952037 } LastAccessTime:{LowDateTime:2659761341 HighDateTime:30952037 } LastWriteTime:{LowDateTime:2659761341 HighDateTime:30952037 } FileSizeHigh:0 FileSizeLow:0 Reserved0:0 filetype:1 Mutex:{state:0 sema:0 } path: vol:3366446870 idxhi:589824 idxlo:4173 appendNameToPath:false }
多数时候创建文件都使用 0666(读写)权限,因此,Go语言封装了一个 Create(name string) 函数,用于简化创建 0666 权限的文件。func Create 的源码如下:
1 2 3 func Create (name string ) (*File, error) { return OpenFile(name, O_RDWR|O_CREATE|O_TRUNC, 0666 ) }
注:标志位中添加了 O_TRUNC 因此对于已经存在的文件,本函数会将其截断。
打开文件 上一节已经介绍了 func OpenFile 函数,可使用该函数打开文件。对于只使用只读模式打开文件Go语言封装了 Open(name string) 函数。 func Open 源码如下:
1 2 3 func Open (name string ) (*File, error) { return OpenFile(name, O_RDONLY, 0 ) }
关闭文件 文件使用完后应当关闭,习惯上把关闭资源的语句放在defer语句处理。关闭文件的函数申明 func (*os.File).Close() error
删除文件 使用 Remove(name string) 删除name指定的文件或目录。如果出错,会返回*PathError底层类型的错误。
1 2 3 4 5 6 7 8 9 func main () { err := os.Remove("./src/susu/suwei.go" ) if err != nil { log.Println("删除失败, err:" , err.Error()) } else { fmt.Println("删除成功" ) } }
使用 Remove 函数只能删除空目录,如果需要递归删除目录需要使用 RemoveAll(path string) 函数。如果path指定的对象不存在,RemoveAll会返回nil而不返回错误。
1 2 3 4 5 6 7 8 9 func main () { err := os.RemoveAll("./src/susu" ) if err != nil { log.Println("删除失败, err:" , err.Error()) } else { fmt.Println("删除成功" ) } }
重命名文件(移动文件) 使用 Rename(oldpath, newpath string) 对文件重命名或移动文件
1 2 3 4 5 6 7 8 9 func main () { err := os.Rename("./temp" , "./newTemp" ) if err != nil { fmt.Println("重命名失败" ) } else { fmt.Println("重命名成功" ) } }
从打开的文件中读取数据 从头读取 使用 func (f *File) Read(b []byte) (n int, err error) 方法从f中读取最多 len(b) 个字节并写入b中。它返回读取的字节数和可能遇到的任何错误。文件终止标志是读取0个字节且返回值err为io.EOF。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 func main () { srcFile, err := os.Open("./newTemp/su.txt" ) if err != nil { fmt.Println("打开文件失败, err:" , err.Error()) } defer srcFile.Close() dataT := make ([]byte , 1024 ) n, err := srcFile.Read(dataT) if err != nil { fmt.Println("文件读取失败" ) } else { fmt.Printf("文件已读取完, 共读取: %d个字节\n" , n) fmt.Println(string (dataT)) } } 文件已读取完, 共读取: 63 个字节 毫无疑问,我做的馅饼,是全天下,最好吃的。
注:当读取0个字节时返回值err才可能为io.EOF ,如果是文件字节数少于切片长度,一次性读完了,返回值err为nil,不为io.EOF ,因为n不等于0
循环读取大文件 注:每读取一次,下一次读/写的位置会向后偏移n个字节,n为本次读取到的字节数
基于此,对于读取大文件可以采用循环读取,直到返回值err为io.EOF 或其他错误时结束。代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 func main () { srcFile, err := os.Open("./newTemp/su.txt" ) if err != nil { fmt.Println("打开文件失败, err:" , err.Error()) } defer srcFile.Close() dataT := make ([]byte , 4 ) dst := make ([]byte , 100 ) for { n, err := srcFile.Read(dataT) if err != nil { if err == io.EOF { fi, _ := srcFile.Stat() fmt.Printf("文件%q读取完毕, 文件大小: %dB\n" , fi.Name(), fi.Size()) } else { fmt.Println("文件读取失败" ) } break } else { dst = append (dst, dataT[:n]...) } } fmt.Println(string (dst)) } 文件"su.txt" 读取完毕, 文件大小: 63 B 毫无疑问,我做的馅饼,是全天下,最好吃的。
从其他位置开始读取 使用ReadAt 使用 Read 方法只会从文件开始处读取文件,若不从文件开始处读取,可以使用 func ReadAt(b []byte, off int64) (n int, err error) 从指定位置 off 开始读取,即先从文件开始处偏移 off 个字节后再读取。当n<len(b)时,本方法总是会返回错误;如果是因为到达文件结尾,返回值err会是io.EOF。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 func main () { srcFile, err := os.Open("./newTemp/su.txt" ) if err != nil { fmt.Println("打开文件失败, err:" , err.Error()) } defer srcFile.Close() dataT := make ([]byte , 1024 ) n, err := srcFile.ReadAt(dataT, 15 ) if err == io.EOF { fmt.Printf("文件已读取完, 共读取: %d个字节\n" , n) fmt.Println(string (dataT)) } else { fmt.Println("文件读取失败" ) } } 文件已读取完, 共读取: 48 个字节 我做的馅饼,是全天下,最好吃的。
注:当n<len(b)时,本方法总是会返回错误 ,如果文件字节数少于切片长度,一次性读完了,返回值err为io.EOF,不为nil 。这和Read 方法有所区别。
使用Seek手动偏移 除了使用 ReadAt 方法外,还可以使用 func (f *File) Seek(offset int64, whence int) (ret int64, err error) 手动偏移后再读取,offset为偏移量,whence决定相对位置:0为相对文件开头,1为相对当前位置,2为相对文件结尾。它返回新的偏移量(相对开头)和可能的错误。os 包中定义了相应的常量。
1 2 3 4 5 const ( SEEK_SET int = 0 SEEK_CUR int = 1 SEEK_END int = 2 )
使用 Seek 改写 ReadAt
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 func main () { srcFile, err := os.Open("./newTemp/su.txt" ) if err != nil { fmt.Println("打开文件失败, err:" , err.Error()) } defer srcFile.Close() dataT := make ([]byte , 1024 ) p, _ := srcFile.Seek(15 , os.SEEK_SET) fmt.Println("新的偏移量:" , p) n, err := srcFile.Read(dataT) if err != nil { fmt.Println("文件读取失败" ) } else { fmt.Printf("文件已读取完, 共读取: %d个字节\n" , n) fmt.Println(string (dataT)) } } 新的偏移量: 15 文件已读取完, 共读取: 48 个字节 我做的馅饼,是全天下,最好吃的。
两个新增的读文件函数/方法
func ReadFile(name string) ([]byte, error) 1.16 引入
一次性读完文件,成功的调用返回值err == nil 而不是 err == EOF ,因为是一次性读完,所以不会将 EOF 视为要报告的错误。
func (f *File) ReadFrom(r io.Reader) (n int64, err error) 1.15 引入
接收一个 io.Reader 接口类型,将 r 中的数据 读入 到文件 f 中,返回读入的字节数和可能的错误。
往文件写
func (f *File) Write(b []byte) (n int, err error) 方法向文件中写入len(b)字节数据。它返回写入的字节数和可能遇到的任何错误。如果返回值n!=len(b),本方法会返回一个非nil的错误。
func (f *File) WriteString(s string) (ret int, err error) WriteString类似Write,但接受一个字符串参数。
func (f *File) WriteAt(b []byte, off int64) (n int, err error) WriteAt在指定的位置(相对于文件开始位置)写入len(b)字节数据。它返回写入的字节数和可能遇到的任何错误。如果返回值n!=len(b),本方法会返回一个非nil的错误。
func WriteFile(name string, data []byte, perm FileMode) error 1.16 引入
如果是往新文件(或者覆盖)写入数据,需要先新建文件,再写入。而本函数就是以上两个过程的封装。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 func main () { text := "suwei susu ddv." dstFile, err := os.Create("./src/suwei.txt" ) if err != nil { fmt.Println("创建文件失败" ) } defer dstFile.Close() n, err := dstFile.WriteString(text) if err != nil { fmt.Println("写文件出错, err:" , err.Error()) } else { fmt.Println("写文件成功,写入字节:" , n) } } PS D:\GoPram\src\demo\Learnfile> go run .\dir_file.go 写文件成功,写入字节: 15 PS D:\GoPram\src\demo\Learnfile> cat src/suwei.txt suwei susu ddv.
拷贝文件 由于 os 包没有提供直接拷贝文件的函数,所以需要自己将源文件的内容读出来,写入到目标文件来实现拷贝。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 func main () { srcFi, err := os.Open("./newTemp/su.txt" ) if err != nil { fmt.Println("打开文件失败, err:" , err.Error()) } defer srcFi.Close() dstFi, err := os.Create("./src/dest.txt" ) if err != nil { fmt.Println("创建文件失败, err:" , err.Error()) } defer dstFi.Close() transit := make ([]byte , 1024 *4 ) for { n, err := srcFi.Read(transit) if err != nil { if err == io.EOF { fmt.Println("拷贝完成" ) } else { fmt.Println("读取出错, err:" , err.Error()) } break } _, err = dstFi.Write(transit[:n]) if err != nil { fmt.Println("写入失败, err:" , err.Error()) } } } PS D:\GoPram\src\demo\Learnfile> go run .\dir_file.go 拷贝完成 PS D:\GoPram\src\demo\Learnfile> cat src/dest.txt 毫无疑问,我做的馅饼,是全天下,最好吃的。
使用 io.Copy 函数 因为 io 包下的 func Copy(dst Writer, src Reader) (written int64, err error) 函数实现了将 src 的数据拷贝到 dst 中,直到遇到EOF或第一个错误。返回拷贝的字节数和遇到的第一个错误。对成功的调用,返回值err为nil而非EOF,因为Copy定义为从src读取直到EOF,它不会将读取到EOF视为应报告的错误。如果src实现了WriterTo接口,本函数会调用src.WriteTo(dst)进行拷贝;否则如果dst实现了ReaderFrom接口,本函数会调用dst.ReadFrom(src)进行拷贝。
并且 *os.File 类型实现了 Reader、Writer 接口,因此可以使用 io.Copy 函数替换 for 循环的逻辑
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 func main () { srcFi, err := os.Open("./newTemp/su.txt" ) if err != nil { fmt.Println("打开文件失败, err:" , err.Error()) } defer srcFi.Close() dstFi, err := os.Create("./src/dest.txt" ) if err != nil { fmt.Println("创建文件失败, err:" , err.Error()) } defer dstFi.Close() if n, err := io.Copy(dstFi, srcFi); err != nil { fmt.Println("拷贝出错, err:" , err.Error()) } else { fmt.Printf("拷贝完成, 拷贝数据: %dB" , n) } } PS D:\GoPram\src\demo\Learnfile> go run .\dir_file.go 拷贝完成, 拷贝数据: 63 B
修改文件模式
func Chmod(name string, mode FileMode) error 将文件名为 name 的文件模式修改为 mode。如果name指定的文件是一个符号链接,它会修改该链接的目的地文件的mode。如果出错,会返回*PathError底层类型的错误。
func (f *File) Chmod(mode FileMode) error 用于修改已打开的文件。
1 2 3 4 5 6 7 8 9 10 11 12 13 func main () { err := os.Chmod("./src/dest.txt" , os.ModePerm) if err != nil { fmt.Println("修改文件模式失败" ) } else { fmt.Println("修改文件模式成功" ) } } PS D:\GoPram\src\demo\Learnfile> go run .\dir_file.go 修改文件模式成功
修改文件大小(截断文件)
func Truncate(name string, size int64) error 将name文件的大小修改为size个字节。如果该文件为一个符号链接,将修改链接指向的文件的大小。如果出错,会返回*PathError底层类型的错误。
func (f *File) Truncate(size int64) error 用于修改已打开的文件的大小。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 func main () { err := os.Truncate("./src/dest.txt" , 15 ) if err != nil { fmt.Println("修改大小失败" ) return } if fi, err := os.Open("./src/dest.txt" ); err != nil { fmt.Println("打开文件失败" ) } else { finfo, _ := fi.Stat() fmt.Printf("文件名: %s\t大小: %dB\n" , finfo.Name(), finfo.Size()) } } PS D:\GoPram\src\demo\Learnfile> go run .\dir_file.go 文件名: dest.txt 大小: 15 B
判断是否为目录 使用 func (m FileMode) IsDir() bool 判断当前的文件模式是否为一个目录。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 func main () { fi, err := os.Open("./newTemp" ) if err != nil { fmt.Println("打开文件失败" ) return } info, _ := fi.Stat() if info.Mode().IsDir() { fmt.Println("./newTemp 是目录" ) } else { fmt.Println("./newTemp 是普通文件" ) } } PS D:\GoPram\src\demo\Learnfile> go run .\dir_file.go ./newTemp 是目录
判断是否为普通文件 使用 func (m FileMode) IsRegular() bool 判断当前文件模式是否为普通文件,与 IsDir 相对应。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 func main () { fi, err := os.Open("./newTemp/su.txt" ) if err != nil { fmt.Println("打开文件失败" ) return } info, _ := fi.Stat() if info.Mode().IsRegular() { fmt.Println("./newTemp/su.txt 是文件" ) } else { fmt.Println("./newTemp/su.txt 是目录" ) } } PS D:\GoPram\src\demo\Learnfile> go run .\dir_file.go ./newTemp/su.txt 是文件
判断文件或目录是否存在
func IsExist(err error) bool 判断目录或文件是否存在。根据err错误判断是否为 ErrExist 常量。
func IsNotExist(err error) bool 与上一个相反。
错误类型常量定义在 oserror 包中:
1 2 3 4 5 6 7 8 9 10 11 package oserrorimport "errors" var ( ErrInvalid = errors.New("invalid argument" ) ErrPermission = errors.New("permission denied" ) ErrExist = errors.New("file already exists" ) ErrNotExist = errors.New("file does not exist" ) ErrClosed = errors.New("file already closed" ) )
判断两个文件是否一样 使用 func SameFile(fi1, fi2 FileInfo) bool 可以通过比较两个文件的 FileInfo 判断两个文件是否一致。
读取目录
func (f *File) Readdir(n int) (fi []FileInfo, err error)
Readdir读取目录f的内容,返回一个有n个成员的[]FileInfo,这些FileInfo是被Lstat返回的,采用目录顺序。对本函数的下一次调用会返回上一次调用剩余未读取的内容的信息。
如果n>0,Readdir函数会返回一个最多n个成员的切片。这时,如果Readdir返回一个空切片,它会返回一个非nil的错误说明原因。如果到达了目录f的结尾,返回值err会是io.EOF。
如果n<=0,Readdir函数返回目录中剩余所有文件对象的FileInfo构成的切片。此时,如果Readdir调用成功(读取所有内容直到结尾),它会返回该切片和nil的错误值。如果在到达结尾前遇到错误,会返回之前成功读取的FileInfo构成的切片和该错误。
func (f *File) ReadDir(n int) ([]DirEntry, error) 1.16 引入
和小写的 Readdir 几乎一致,不同的是只返回目录条目的切片,不会包含普通文件。
其中 DirEntry 定义在 fs 包:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 type DirEntry interface { Name() string IsDir() bool Type() FileMode Info() (FileInfo, error) }
func ReadDir(name string) ([]DirEntry, error) 1.16 引入
内部调用了 f.ReadDir(-1) ,省去了打开文件。
func (f *File) Readdirnames(n int) (names []string, err error)
Readdir读取目录f的内容,返回一个有n个成员的[]string,切片成员为目录中文件对象的名字,采用目录顺序。对本函数的下一次调用会返回上一次调用剩余未读取的内容的信息。
如果n>0,Readdir函数会返回一个最多n个成员的切片。这时,如果Readdir返回一个空切片,它会返回一个非nil的错误说明原因。如果到达了目录f的结尾,返回值err会是io.EOF。
如果n<=0,Readdir函数返回目录中剩余所有文件对象的名字构成的切片。此时,如果Readdir调用成功(读取所有内容直到结尾),它会返回该切片和nil的错误值。如果在到达结尾前遇到错误,会返回之前成功读取的名字构成的切片和该错误。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 func main () { file, err := os.Open("./src" ) if err != nil { fmt.Println("打开文件失败" ) return } defer file.Close() subfiles, err := file.Readdir(0 ) if err != nil { fmt.Println("读取目录出错" ) return } fmt.Println("./src" ) for _, f := range subfiles { var sizeStr string size := f.Size() if size > 1000 _000 { sizeStr = strconv.FormatFloat(float64 (size)/1000 _000, 'f' , 2 , 64 ) + "MB" } else if size > 1000 { sizeStr = strconv.FormatFloat(float64 (size)/1000 , 'f' , 2 , 64 ) + "KB" } else { sizeStr = strconv.FormatInt(size, 10 ) + "B" } fmt.Printf(" %s\t%s\tsize: %s\n" , f.Mode(), f.Name(), sizeStr) } } PS D:\GoPram\src\demo\Learnfile> go run .\dir_file.go ./src -rw-rw-rw- dest.txt size: 15 B -rw-rw-rw- suwei.txt size: 15 B -rw-rw-rw- sxr-dest.jpg size: 4.45 MB
完结:递归拷贝文件 之前实现的拷贝文件方法只能拷贝单个文件,对于拷贝整个目录是更为普遍的需要。下面实现一个递归拷贝文件的函数作为结尾。
要想递归拷贝,重点就是区分目录和文件,以使用不同的方式进行处理,可以使用 func (fs.FileMode).IsRegular() bool 方法判断是否为普通文件。对于普通文件我们直接拷贝;对于目录我们新建目录,再使用 func (*os.File).Readdir(n int) ([]fs.FileInfo, error) 方法读取该目录,递归处理读取到的每一个子条目。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 func Copy (srcPath, dstPath string ) error { srcF, err := os.Open(srcPath) if err != nil { return err } defer srcF.Close() info, err := srcF.Stat() if err != nil { return err } var currFile = dstPath + "/" + info.Name() if info.Mode().IsRegular() { dstf, err := os.Create(currFile) if err != nil { return err } defer dstf.Close() _, err = io.Copy(dstf, srcF) if err != nil { os.Remove(currFile) } return err } err = os.Mkdir(currFile, os.ModePerm) if err != nil { os.RemoveAll(currFile) return err } subFile, err := srcF.Readdir(-1 ) if err != nil { return err } for _, sub := range subFile { if err = Copy(srcPath+"/" +sub.Name(), currFile); err != nil { if err != nil { os.RemoveAll(currFile) } return err } } return nil }
使用方式如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 func main () { if err := Copy("./newTemp" , "./src" ); err != nil { fmt.Println("拷贝失败, err:" , err.Error()) } else { fmt.Println("拷贝完成" ) } if err := Copy("./newTemp/sxr.jpg" , "./" ); err != nil { fmt.Println("拷贝失败, err:" , err.Error()) } else { fmt.Println("拷贝完成" ) } } PS D:\GoPram\src\demo\Learnfile> go run .\copy .go 拷贝完成 拷贝完成