File
Các thư viện chuẩn mà Go cung cấp để xử lý file chủ yếu gồm mấy loại sau
- Thư viện
osphụ trách triển khai cụ thể tương tác với hệ thống file của OS - Thư viện
iolớp trừu tượng cho đọc ghi IO - Thư viện
fslớp trừu tượng cho hệ thống file
Bài viết này sẽ giải thích cách xử lý file cơ bản trong Go.
Mở
Hai cách mở file phổ biến là sử dụng hai hàm do gói os cung cấp hàm Open trả về một con trỏ file và một lỗi
func Open(name string) (*File, error)Hàm OpenFile sau có thể cung cấp kiểm soát chi tiết hơn hàm Open là một đóng gói đơn giản của hàm OpenFile.
func OpenFile(name string, flag int, perm FileMode) (*File, error)Trước tiên giới thiệu cách sử dụng đầu tiên chỉ cần cung cấp tên file tương ứng mã như sau
func main() {
file, err := os.Open("README.txt")
fmt.Println(file, err)
}Đường dẫn tìm kiếm file mặc định là đường dẫn nơi file go.mod của dự án nằm do dự án không có file README.txt nên tự nhiên sẽ trả về một lỗi.
<nil> open README.txt: The system cannot find the file specified.Vì loại lỗi IO có nhiều nên cần thủ công判断 file có tồn tại không tương tự gói os cũng cung cấp hàm tiện lợi cho việc này mã sau khi sửa đổi như sau
func main() {
file, err := os.Open("README.txt")
if os.IsNotExist(err) {
fmt.Println("File không tồn tại")
} else if err != nil {
fmt.Println("Truy cập file bất thường")
} else {
fmt.Println("Đọc file thành công", file)
}
}Chạy lại kết quả xuất như sau
File không tồn tạiThực tế hàm đọc file đầu tiên chỉ đọc được file chỉ đọc không thể sửa đổi
func Open(name string) (*File, error) {
return OpenFile(name, O_RDONLY, 0)
}Thông qua hàm OpenFile có thể kiểm soát nhiều chi tiết hơn ví dụ như sửa đổi mô tả file và quyền file về mô tả file gói os cung cấp các hằng số sau để sử dụng.
const (
// Chỉ đọc chỉ ghi đọc ghi ba loại phải chỉ định một
O_RDONLY int = syscall.O_RDONLY // Mở file ở chế độ chỉ đọc
O_WRONLY int = syscall.O_WRONLY // Mở file ở chế độ chỉ ghi
O_RDWR int = syscall.O_RDWR // Mở file ở chế độ đọc ghi
// Các giá trị còn lại dùng để kiểm soát hành vi
O_APPEND int = syscall.O_APPEND // Khi ghi file thêm dữ liệu vào cuối file
O_CREATE int = syscall.O_CREAT // Tạo file nếu file không tồn tại
O_EXCL int = syscall.O_EXCL // Sử dụng cùng với O_CREATE file phải không tồn tại
O_SYNC int = syscall.O_SYNC // Mở file ở chế độ IO đồng bộ
O_TRUNC int = syscall.O_TRUNC // Cắt file có thể ghi khi mở
)Về quyền file thì cung cấp các hằng số sau.
const (
ModeDir = fs.ModeDir // d: thư mục
ModeAppend = fs.ModeAppend // a: chỉ có thể thêm
ModeExclusive = fs.ModeExclusive // l: chuyên dụng
ModeTemporary = fs.ModeTemporary // T: file tạm thời
ModeSymlink = fs.ModeSymlink // L: liên kết biểu tượng
ModeDevice = fs.ModeDevice // D: file thiết bị
ModeNamedPipe = fs.ModeNamedPipe // p: ống dẫn có tên (FIFO)
ModeSocket = fs.ModeSocket // S: Unix domain socket
ModeSetuid = fs.ModeSetuid // u: setuid
ModeSetgid = fs.ModeSetgid // g: setgid
ModeCharDevice = fs.ModeCharDevice // c: thiết bị ký tự Unix với điều kiện đã đặt ModeDevice
ModeSticky = fs.ModeSticky // t: sticky bit
ModeIrregular = fs.ModeIrregular // ?: file không theo quy tắc
// Mặt nạ bit loại. Đối với file thông thường không đặt gì.
ModeType = fs.ModeType
ModePerm = fs.ModePerm // Bit quyền Unix 0o777
)Dưới đây là một ví dụ mã mở file ở chế độ đọc ghi quyền là 0666 biểu thị mọi người đều có thể đọc ghi file và sẽ tự động tạo khi không tồn tại.
func main() {
file, err := os.OpenFile("README.txt", os.O_RDWR|os.O_CREATE, 0666)
if os.IsNotExist(err) {
fmt.Println("File không tồn tại")
} else if err != nil {
fmt.Println("Truy cập file bất thường")
} else {
fmt.Println("Mở file thành công", file.Name())
file.Close()
}
}Kết quả xuất như sau
Mở file thành công README.txtNếu chỉ muốn lấy một số thông tin của file mà không muốn đọc file có thể sử dụng hàm os.Stat() để thực hiện thao tác ví dụ mã như sau
func main() {
fileInfo, err := os.Stat("README.txt")
if err != nil {
fmt.Println(err)
} else {
fmt.Println(fmt.Sprintf("%+v", fileInfo))
}
}Kết quả xuất như sau
&{name:README.txt FileAttributes:32 CreationTime:{LowDateTime:3603459389 HighDateTime:31016791} LastAccessTime:{LowDateTime:3603459389 HighDateTime:31016791} LastWriteTime:{LowDateTime:3603459389 HighDateTime:31016791} FileSizeHigh
:0 FileSizeLow:0 Reserved0:0 filetype:0 Mutex:{state:0 sema:0} path:README.txt vol:0 idxhi:0 idxlo:0 appendNameToPath:false}WARNING
Sau khi mở một file luôn nhớ đóng file đó thông thường thao tác đóng sẽ được đặt trong câu lệnh defer
defer file.Close()Đọc
Khi mở file thành công có thể thực hiện thao tác đọc về thao tác đọc file loại *os.File cung cấp các phương thức công khai sau
// Đọc file vào slice byte được truyền vào
func (f *File) Read(b []byte) (n int, err error)
// So với loại đầu tiên có thể đọc từ độ lệch chỉ định
func (f *File) ReadAt(b []byte, off int64) (n int, err error)Trong hầu hết các trường hợp loại đầu tiên được sử dụng nhiều hơn. Đối với phương thức đầu tiên cần tự viết logic để mở rộng slice động khi đọc mã như sau
func ReadFile(file *os.File) ([]byte, error) {
buffer := make([]byte, 0, 512)
for {
// Khi dung lượng không đủ
if len(buffer) == cap(buffer) {
// Mở rộng
buffer = append(buffer, 0)[:len(buffer)]
}
// Tiếp tục đọc file
offset, err := file.Read(buffer[len(buffer):cap(buffer)])
// Đưa dữ liệu đã ghi vào slice
buffer = buffer[:len(buffer)+offset]
// Khi xảy ra lỗi
if err != nil {
if errors.Is(err, io.EOF) {
err = nil
}
return buffer, err
}
}
}Logic còn lại như sau
func main() {
file, err := os.OpenFile("README.txt", os.O_RDWR|os.O_CREATE, 0666)
if err != nil {
fmt.Println("Truy cập file bất thường")
} else {
fmt.Println("Mở file thành công", file.Name())
bytes, err := ReadFile(file)
if err != nil {
fmt.Println("Đọc file bất thường", err)
} else {
fmt.Println(string(bytes))
}
file.Close()
}
}Kết quả xuất là
Mở file thành công README.txt
hello world!Ngoài ra còn có thể sử dụng hai hàm tiện lợi để đọc file lần lượt là hàm ReadFile trong gói os và hàm ReadAll trong gói io. Đối với os.ReadFile chỉ cần cung cấp đường dẫn file là được còn đối với io.ReadAll cần cung cấp một triển khai loại io.Reader
os.ReadFile
func ReadFile(name string) ([]byte, error)Ví dụ sử dụng như sau
func main() {
bytes, err := os.ReadFile("README.txt")
if err != nil {
fmt.Println(err)
} else {
fmt.Println(string(bytes))
}
}Kết quả xuất như sau
hello world!io.ReadAll
func ReadAll(r Reader) ([]byte, error)Ví dụ sử dụng như sau
func main() {
file, err := os.OpenFile("README.txt", os.O_RDWR|os.O_CREATE, 0666)
if err != nil {
fmt.Println("Truy cập file bất thường")
} else {
fmt.Println("Mở file thành công", file.Name())
bytes, err := io.ReadAll(file)
if err != nil {
fmt.Println(err)
} else {
fmt.Println(string(bytes))
}
file.Close()
}
}Mở file thành công README.txt
hello world!Ghi
Struct os.File cung cấp các phương thức sau để ghi dữ liệu
// Ghi slice byte
func (f *File) Write(b []byte) (n int, err error)
// Ghi chuỗi
func (f *File) WriteString(s string) (n int, err error)
// Bắt đầu ghi từ vị trí chỉ định khi mở ở chế độ os.O_APPEND sẽ trả về lỗi
func (f *File) WriteAt(b []byte, off int64) (n int, err error)Nếu muốn ghi dữ liệu vào file thì phải mở ở chế độ O_WRONLY hoặc O_RDWR nếu không sẽ không thể ghi file thành công. Dưới đây là một ví dụ mở file ở chế độ os.O_RDWR|os.O_CREATE|os.O_APPEND|os.O_TRUNC và quyền là 0666 để ghi dữ liệu chỉ định
func main() {
file, err := os.OpenFile("README.txt", os.O_RDWR|os.O_CREATE|os.O_APPEND|os.O_TRUNC, 0666)
if err != nil {
fmt.Println("Truy cập file bất thường")
} else {
fmt.Println("Mở file thành công", file.Name())
for i := 0; i < 5; i++ {
offset, err := file.WriteString("hello world!\n")
if err != nil {
fmt.Println(offset, err)
}
}
fmt.Println(file.Close())
}
}Do mở file ở chế độ os.O_APPEND nên khi ghi file dữ liệu sẽ được thêm vào cuối file sau khi thực hiện xong nội dung file như sau
hello world!
hello world!
hello world!
hello world!
hello world!Việc ghi slice byte vào file cũng là thao tác tương tự không nhắc lại nữa. Đối với thao tác ghi file thư viện chuẩn cũng cung cấp hàm tiện lợi lần lượt là os.WriteFile và io.WriteString
os.WriteFile
func WriteFile(name string, data []byte, perm FileMode) errorVí dụ sử dụng như sau
func main() {
err := os.WriteFile("README.txt", []byte("hello world!\n"), 0666)
if err != nil {
fmt.Println(err)
}
}Lúc này nội dung file như sau
hello world!io.WriteString
func WriteString(w Writer, s string) (n int, err error)Ví dụ sử dụng như sau
func main() {
file, err := os.OpenFile("README.txt", os.O_RDWR|os.O_CREATE|os.O_APPEND|os.O_TRUNC, 0666)
if err != nil {
fmt.Println("Truy cập file bất thường")
} else {
fmt.Println("Mở file thành công", file.Name())
for i := 0; i < 5; i++ {
offset, err := io.WriteString(file, "hello world!\n")
if err != nil {
fmt.Println(offset, err)
}
}
fmt.Println(file.Close())
}
}hello world!
hello world!
hello world!
hello world!
hello world!Hàm os.Create dùng để tạo file về bản chất cũng là đóng gói của OpenFile.
func Create(name string) (*File, error) {
return OpenFile(name, O_RDWR|O_CREATE|O_TRUNC, 0666)
}WARNING
Khi tạo một file nếu thư mục cha của nó không tồn tại sẽ tạo thất bại và trả về lỗi.
Sao chép
Đối với việc sao chép file cần mở hai file đồng thời cách thứ nhất là đọc dữ liệu từ file gốc sau đó ghi vào file đích ví dụ mã như sau
func main() {
// Đọc dữ liệu từ file gốc
data, err := os.ReadFile("README.txt")
if err != nil {
fmt.Println(err)
return
}
// Ghi vào file đích
err = os.WriteFile("README(1).txt", data, 0666)
if err != nil {
fmt.Println(err)
} else {
fmt.Println("Sao chép thành công")
}
}*os.File.ReadFrom
Cách khác là sử dụng phương thức ReadFrom do os.File cung cấp khi mở file một chỉ đọc một chỉ ghi.
func (f *File) ReadFrom(r io.Reader) (n int64, err error)Ví dụ sử dụng như sau
func main() {
// Mở file gốc ở chế độ chỉ đọc
origin, err := os.OpenFile("README.txt", os.O_RDONLY, 0666)
if err != nil {
fmt.Println(err)
return
}
defer origin.Close()
// Mở file sao chép ở chế độ chỉ ghi
target, err := os.OpenFile("README(1).txt", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
if err != nil {
fmt.Println(err)
return
}
defer target.Close()
// Đọc dữ liệu từ file gốc sau đó ghi vào file sao chép
offset, err := target.ReadFrom(origin)
if err != nil {
fmt.Println(err)
return
}
fmt.Println("Sao chép file thành công", offset)
}Cách sao chép này cần đọc toàn bộ nội dung file nguồn vào bộ nhớ trước rồi mới ghi vào file đích khi file đặc biệt lớn không nên làm như vậy.
io.Copy
Cách khác là sử dụng hàm io.Copy nó vừa đọc vừa ghi trước tiên đọc nội dung vào bộ đệm sau đó ghi vào file đích kích thước bộ đệm mặc định là 32KB.
func Copy(dst Writer, src Reader) (written int64, err error)Ví dụ sử dụng như sau
func main() {
// Mở file gốc ở chế độ chỉ đọc
origin, err := os.OpenFile("README.txt", os.O_RDONLY, 0666)
if err != nil {
fmt.Println(err)
return
}
defer origin.Close()
// Mở file sao chép ở chế độ chỉ ghi
target, err := os.OpenFile("README(1).txt", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
if err != nil {
fmt.Println(err)
return
}
defer target.Close()
// Sao chép
written, err := io.Copy(target, origin)
if err != nil {
fmt.Println(err)
} else {
fmt.Println(written)
}
}Bạn cũng có thể sử dụng io.CopyBuffer để chỉ định kích thước bộ đệm.
Đổi tên
Đổi tên cũng có thể hiểu là di chuyển file sẽ sử dụng hàm Rename trong gói os.
func Rename(oldpath, newpath string) errorVí dụ như sau
func main() {
err := os.Rename("README.txt", "readme.txt")
if err != nil {
fmt.Println(err)
} else {
fmt.Println("Đổi tên thành công")
}
}Hàm này cũng có hiệu quả tương tự đối với thư mục.
Xóa
Thao tác xóa đơn giản hơn nhiều so với các thao tác khác chỉ sử dụng hai hàm trong gói os
// Xóa một file đơn hoặc thư mục rỗng khi thư mục không rỗng sẽ trả về lỗi
func Remove(name string) error
// Xóa tất cả file và thư mục trong thư mục chỉ định bao gồm thư mục con và file con
func RemoveAll(path string) errorSử dụng rất đơn giản dưới đây là ví dụ xóa thư mục
func main() {
// Xóa tất cả file và thư mục con trong thư mục hiện tại
err := os.RemoveAll(".")
if err != nil {
fmt.Println(err)
}else {
fmt.Println("Xóa thành công")
}
}Dưới đây là ví dụ xóa một file đơn
func main() {
// Xóa tất cả file và thư mục con trong thư mục hiện tại
err := os.Remove("README.txt")
if err != nil {
fmt.Println(err)
} else {
fmt.Println("Xóa thành công")
}
}Làm mới
Hàm os.Sync này đóng gói gọi hệ thống Fsync ở cấp dưới dùng để thực hiện việc ghi IO được đệm trong hệ điều hành vào đĩa
func main() {
create, err := os.Create("test.txt")
if err != nil {
panic(err)
}
defer create.Close()
_, err = create.Write([]byte("hello"))
if err != nil {
panic(err)
}
// Ghi đĩa
if err := create.Sync();err != nil {
return
}
}Thư mục
Nhiều thao tác của thư mục tương tự như thao tác file
Đọc
Đối với thư mục có hai cách mở
os.ReadDir
Cách thứ nhất là sử dụng hàm os.ReadDir
func ReadDir(name string) ([]DirEntry, error)func main() {
// Thư mục hiện tại
dir, err := os.ReadDir(".")
if err != nil {
fmt.Println(err)
} else {
for _, entry := range dir {
fmt.Println(entry.Name())
}
}
}*os.File.ReadDir
Cách thứ hai là sử dụng hàm *os.File.ReadDir về bản chất os.ReadDir chỉ là một đóng gói đơn giản của *os.File.ReadDir.
// Khi n < 0 thì đọc tất cả nội dung trong thư mục
func (f *File) ReadDir(n int) ([]DirEntry, error)func main() {
// Thư mục hiện tại
dir, err := os.Open(".")
if err != nil {
fmt.Println(err)
}
defer dir.Close()
dirs, err := dir.ReadDir(-1)
if err != nil {
fmt.Println(err)
} else {
for _, entry := range dirs {
fmt.Println(entry.Name())
}
}
}Tạo
Thao tác tạo thư mục sẽ sử dụng hai hàm trong gói os
// Tạo thư mục có tên chỉ định với quyền chỉ định
func Mkdir(name string, perm FileMode) error
// So với hàm trước thì hàm này sẽ tạo tất cả thư mục cha cần thiết
func MkdirAll(path string, perm FileMode) errorVí dụ như sau
func main() {
err := os.Mkdir("src", 0666)
if err != nil {
fmt.Println(err)
} else {
fmt.Println("Tạo thành công")
}
}Sao chép
Chúng ta có thể tự viết hàm để duyệt đệ quy toàn bộ thư mục nhưng thư viện chuẩn filepath đã cung cấp hàm có chức năng tương tự nên có thể sử dụng trực tiếp dưới đây là một ví dụ mã sao chép thư mục đơn giản.
func CopyDir(src, dst string) error {
// Kiểm tra trạng thái thư mục nguồn
_, err := os.Stat(src)
if err != nil {
return err
}
return filepath.Walk(src, func(path string, info fs.FileInfo, err error) error {
if err != nil {
return err
}
// Tính đường dẫn tương đối
rel, err := filepath.Rel(src, path)
if err != nil {
return err
}
// Nối đường dẫn đích
destpath := filepath.Join(dst, rel)
// Tạo thư mục
var dirpath string
var mode os.FileMode = 0755
if info.IsDir() {
dirpath = destpath
mode = info.Mode()
} else if info.Mode().IsRegular() {
dirpath = filepath.Dir(destpath)
}
if err := os.MkdirAll(dirpath, mode); err != nil {
return err
}
// Tạo file
if info.Mode().IsRegular() {
srcfile, err := os.Open(path)
if err != nil {
return err
}
// Nhớ đóng file
defer srcfile.Close()
destfile, err := os.OpenFile(destpath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, info.Mode())
if err != nil {
return err
}
defer destfile.Close()
// Sao chép nội dung file
if _, err := io.Copy(destfile, srcfile); err != nil {
return err
}
return nil
}
return nil
})
}filepath.Walk sẽ duyệt đệ quy toàn bộ thư mục trong quá trình gặp thư mục thì tạo thư mục gặp file thì tạo file mới và sao chép mã so với sao chép file có nhiều hơn nhưng không phức tạp.
