ไฟล์
ไลบรารีมาตรฐานที่ Go ให้สำหรับการจัดการไฟล์มีประมาณต่อไปนี้:
- ไลบรารี
osรับผิดชอบการใช้งานเฉพาะของการโต้ตอบระบบไฟล์ OS - ไลบรารี
ioเป็นเลเยอร์นามธรรมสำหรับการอ่านเขียน IO - ไลบรารี
fsเป็นเลเยอร์นามธรรมสำหรับระบบไฟล์
บทความนี้จะอธิบายวิธีการจัดการไฟล์พื้นฐานในภาษา Go
การเปิด
สองวิธีที่พบบ่อยในการเปิดไฟล์คือการใช้สองฟังก์ชันที่จัดเตรียมโดยแพ็กเกจ os ฟังก์ชัน Open ส่งกลับพอยน์เตอร์ไฟล์หนึ่งตัวและข้อผิดพลาดหนึ่งตัว
func Open(name string) (*File, error)ฟังก์ชันหลัง OpenFile สามารถให้การควบคุมที่ละเอียดมากขึ้น ฟังก์ชัน Open เป็นการห่อหุ้มอย่างง่ายสำหรับฟังก์ชัน OpenFile
func OpenFile(name string, flag int, perm FileMode) (*File, error)ก่อนอื่นมาดูวิธีการใช้งาน第一种 ให้ชื่อไฟล์ที่ตรงกันก็พอ โค้ดมีดังนี้
func main() {
file, err := os.Open("README.txt")
fmt.Println(file, err)
}เส้นทางการค้นหาไฟล์เริ่มต้นเป็นที่ตั้งของไฟล์ go.mod ของโปรเจกต์ เนื่องจากโปรเจกต์ไม่มีไฟล์ README.txt จึงส่งกลับข้อผิดพลาดตามธรรมชาติ
<nil> open README.txt: The system cannot find the file specified.เนื่องจากประเภทข้อผิดพลาด IO มีมากมาย จึงต้องตัดสินด้วยตนเองว่าไฟล์มีอยู่หรือไม่ ในทำนองเดียวกันแพ็กเกจ os ก็ได้จัดเตรียมฟังก์ชันความสะดวกสำหรับ此事 โค้ดที่แก้ไขมีดังนี้
func main() {
file, err := os.Open("README.txt")
if os.IsNotExist(err) {
fmt.Println("ไฟล์ไม่มีอยู่")
} else if err != nil {
fmt.Println("การเข้าถึงไฟล์ผิดปกติ")
} else {
fmt.Println("การอ่านไฟล์สำเร็จ", file)
}
}รันอีกครั้งผลลัพธ์มีดังนี้
ไฟล์ไม่มีอยู่จริงๆ แล้วฟังก์ชัน第一种อ่านไฟล์ได้แบบอ่านอย่างเดียวเท่านั้น ไม่สามารถแก้ไขได้
func Open(name string) (*File, error) {
return OpenFile(name, O_RDONLY, 0)
}ผ่านฟังก์ชัน OpenFile สามารถควบคุมรายละเอียดมากขึ้นได้ เช่น ตัวอธิบายไฟล์และสิทธิ์ไฟล์ เกี่ยวกับตัวอธิบายไฟล์ แพ็กเกจ 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 // เปิดไฟล์ในโหมด IO แบบ synchronous
O_TRUNC int = syscall.O_TRUNC // ตัดไฟล์ที่เขียนได้เมื่อเปิด
)เกี่ยวกับสิทธิ์ไฟล์มีค่าคงที่ต่อไปนี้
const (
ModeDir = fs.ModeDir // d: ไดเรกทอรี
ModeAppend = fs.ModeAppend // a: เพิ่มได้เท่านั้น
ModeExclusive = fs.ModeExclusive // l: เฉพาะ
ModeTemporary = fs.ModeTemporary // T: ไฟล์ชั่วคราว
ModeSymlink = fs.ModeSymlink // L: symbolic link
ModeDevice = fs.ModeDevice // D: ไฟล์อุปกรณ์
ModeNamedPipe = fs.ModeNamedPipe // p: named pipe (FIFO)
ModeSocket = fs.ModeSocket // S: Unix domain socket
ModeSetuid = fs.ModeSetuid // u: setuid
ModeSetgid = fs.ModeSetgid // g: setgid
ModeCharDevice = fs.ModeCharDevice // c: Unix character device ต้องตั้งค่า ModeDevice ก่อน
ModeSticky = fs.ModeSticky // t: sticky bit
ModeIrregular = fs.ModeIrregular // ?: ไฟล์ไม่ปกติ
// มาสก์สำหรับบิตประเภท สำหรับไฟล์ปกติ จะไม่ตั้งค่าอะไร
ModeType = fs.ModeType
ModePerm = fs.ModePerm // บิตสิทธิ์ Unix, 0o777
)ด้านล่างเป็นตัวอย่างโค้ดที่เปิดไฟล์ในโหมดอ่านเขียน สิทธิ์เป็น 0666 หมายถึงทุกคนสามารถอ่านเขียนไฟล์นี้ได้ และจะสร้างอัตโนมัติเมื่อไม่มีอยู่
func main() {
file, err := os.OpenFile("README.txt", os.O_RDWR|os.O_CREATE, 0666)
if os.IsNotExist(err) {
fmt.Println("ไฟล์ไม่มีอยู่")
} else if err != nil {
fmt.Println("การเข้าถึงไฟล์ผิดปกติ")
} else {
fmt.Println("เปิดไฟล์สำเร็จ", file.Name())
file.Close()
}
}ผลลัพธ์มีดังนี้
เปิดไฟล์สำเร็จ README.txtหากต้องการเพียงรับข้อมูลบางอย่างของไฟล์นี้ ไม่ต้องการอ่านไฟล์นี้ สามารถใช้ฟังก์ชัน os.Stat() เพื่อดำเนินการ ตัวอย่างโค้ดมีดังนี้
func main() {
fileInfo, err := os.Stat("README.txt")
if err != nil {
fmt.Println(err)
} else {
fmt.Println(fmt.Sprintf("%+v", fileInfo))
}
}ผลลัพธ์มีดังนี้
&{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
หลังจากเปิดไฟล์หนึ่งแล้ว ต้องจำไว้เสมอว่าต้องปิดไฟล์นั้น โดยปกติการปิดจะ放在语句 defer
defer file.Close()การอ่าน
หลังจากเปิดไฟล์สำเร็จแล้ว ก็สามารถดำเนินการอ่านได้ เกี่ยวกับการดำเนินการอ่านไฟล์ ประเภท *os.File จัดเตรียมเมธอดสาธารณะต่อไปนี้
// อ่านไฟล์เข้าไปในสไลซ์ไบต์ที่ส่งเข้ามา
func (f *File) Read(b []byte) (n int, err error)
// เทียบกับ第一种สามารถอ่านจากออฟเซ็ตที่ระบุได้
func (f *File) ReadAt(b []byte, off int64) (n int, err error)ส่วนใหญ่第一种ใช้มากกว่า สำหรับ第一种เมธอด ต้องเขียนตรรกะเองสำหรับการขยายสไลซ์แบบไดนามิกเมื่ออ่าน โค้ดมีดังนี้
func ReadFile(file *os.File) ([]byte, error) {
buffer := make([]byte, 0, 512)
for {
// เมื่อความจุไม่เพียงพอ
if len(buffer) == cap(buffer) {
// ขยาย
buffer = append(buffer, 0)[:len(buffer)]
}
// อ่านไฟล์ต่อไป
offset, err := file.Read(buffer[len(buffer):cap(buffer)])
// รวมข้อมูลที่เขียนแล้วเข้าสไลซ์
buffer = buffer[:len(buffer)+offset]
// เมื่อเกิดข้อผิดพลาด
if err != nil {
if errors.Is(err, io.EOF) {
err = nil
}
return buffer, err
}
}
}ตรรกะที่เหลือมีดังนี้
func main() {
file, err := os.OpenFile("README.txt", os.O_RDWR|os.O_CREATE, 0666)
if err != nil {
fmt.Println("การเข้าถึงไฟล์ผิดปกติ")
} else {
fmt.Println("เปิดไฟล์สำเร็จ", file.Name())
bytes, err := ReadFile(file)
if err != nil {
fmt.Println("การอ่านไฟล์ผิดปกติ", err)
} else {
fmt.Println(string(bytes))
}
file.Close()
}
}ผลลัพธ์คือ
เปิดไฟล์สำเร็จ README.txt
hello world!นอกจากนี้ ยังสามารถใช้สองฟังก์ชันความสะดวกสำหรับการอ่านไฟล์分别是 ฟังก์ชัน ReadFile ภายใต้แพ็กเกจ os และฟังก์ชัน ReadAll ภายใต้แพ็กเกจ io สำหรับ os.ReadFile แล้ว只需ให้เส้นทางไฟล์ก็พอ สำหรับ io.ReadAll แล้ว ต้องให้การนำไปใช้ประเภท io.Reader หนึ่งตัว
os.ReadFile
func ReadFile(name string) ([]byte, error)ตัวอย่างการใช้งานมีดังนี้
func main() {
bytes, err := os.ReadFile("README.txt")
if err != nil {
fmt.Println(err)
} else {
fmt.Println(string(bytes))
}
}ผลลัพธ์มีดังนี้
hello world!io.ReadAll
func ReadAll(r Reader) ([]byte, error)ตัวอย่างการใช้งานมีดังนี้
func main() {
file, err := os.OpenFile("README.txt", os.O_RDWR|os.O_CREATE, 0666)
if err != nil {
fmt.Println("การเข้าถึงไฟล์ผิดปกติ")
} else {
fmt.Println("เปิดไฟล์สำเร็จ", file.Name())
bytes, err := io.ReadAll(file)
if err != nil {
fmt.Println(err)
} else {
fmt.Println(string(bytes))
}
file.Close()
}
}เปิดไฟล์สำเร็จ README.txt
hello world!การเขียน
สตรักต์ os.File จัดเตรียมเมธอดต่อไปนี้สำหรับการเขียนข้อมูล
// เขียนสไลซ์ไบต์
func (f *File) Write(b []byte) (n int, err error)
// เขียนสตริง
func (f *File) WriteString(s string) (n int, err error)
// เริ่มเขียนจากตำแหน่งที่ระบุ เมื่อเปิดในโหมด os.O_APPEND จะส่งกลับข้อผิดพลาด
func (f *File) WriteAt(b []byte, off int64) (n int, err error)หากต้องการเขียนข้อมูลลงในไฟล์หนึ่ง ต้องเปิดในโหมด O_WRONLY หรือ O_RDWR ไม่เช่นนั้นจะเขียนไฟล์ไม่สำเร็จ ด้านล่างเป็นตัวอย่างที่เปิดไฟล์ในโหมด os.O_RDWR|os.O_CREATE|os.O_APPEND|os.O_TRUNC และสิทธิ์เป็น 0666 เพื่อเขียนข้อมูล指定的
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("การเข้าถึงไฟล์ผิดปกติ")
} else {
fmt.Println("เปิดไฟล์สำเร็จ", 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())
}
}เนื่องจากเปิดไฟล์ในโหมด os.O_APPEND ดังนั้นเมื่อเขียนไฟล์จะเพิ่มข้อมูลต่อท้ายไฟล์ หลังจากดำเนินการเสร็จสิ้นเนื้อหาไฟล์มีดังนี้
hello world!
hello world!
hello world!
hello world!
hello world!การเขียนสไลซ์ไบต์ลงในไฟล์ก็เป็นการดำเนินการที่คล้ายกัน จะไม่กล่าวซ้ำ สำหรับ的操作เขียนไฟล์ไลบรารีมาตรฐานได้จัดเตรียมฟังก์ชันความสะดวก分别是 os.WriteFile กับ io.WriteString
os.WriteFile
func WriteFile(name string, data []byte, perm FileMode) errorตัวอย่างการใช้งานมีดังนี้
func main() {
err := os.WriteFile("README.txt", []byte("hello world!\n"), 0666)
if err != nil {
fmt.Println(err)
}
}ขณะนี้เนื้อหาไฟล์มีดังนี้
hello world!io.WriteString
func WriteString(w Writer, s string) (n int, err error)ตัวอย่างการใช้งานมีดังนี้
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("การเข้าถึงไฟล์ผิดปกติ")
} else {
fmt.Println("เปิดไฟล์สำเร็จ", 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!ฟังก์ชัน os.Create ใช้สำหรับสร้างไฟล์ โดยพื้นฐานแล้วเป็นการห่อหุ้ม OpenFile เช่นกัน
func Create(name string) (*File, error) {
return OpenFile(name, O_RDWR|O_CREATE|O_TRUNC, 0666)
}WARNING
เมื่อสร้างไฟล์หนึ่ง หากไดเรกทอรีพ่อของมันไม่มีอยู่ จะสร้างล้มเหลวและส่งกลับข้อผิดพลาด
การคัดลอก
สำหรับการคัดลอกไฟล์แล้ว ต้องเปิดสองไฟล์พร้อมกัน วิธี第一种คือการอ่านข้อมูลจากไฟล์ต้นฉบับ แล้วเขียนลงในไฟล์เป้าหมาย ตัวอย่างโค้ดมีดังนี้
func main() {
// อ่านข้อมูลจากไฟล์ต้นฉบับ
data, err := os.ReadFile("README.txt")
if err != nil {
fmt.Println(err)
return
}
// เขียนไฟล์เป้าหมาย
err = os.WriteFile("README(1).txt", data, 0666)
if err != nil {
fmt.Println(err)
} else {
fmt.Println("คัดลอกสำเร็จ")
}
}*os.File.ReadFrom
อีกวิธีหนึ่งคือการใช้เมธอด ReadFrom ที่ os.File จัดเตรียมไว้ เมื่อเปิดไฟล์ หนึ่งอ่านอย่างเดียว หนึ่งเขียนอย่างเดียว
func (f *File) ReadFrom(r io.Reader) (n int64, err error)ตัวอย่างการใช้งานมีดังนี้
func main() {
// เปิดไฟล์ต้นฉบับในโหมดอ่านอย่างเดียว
origin, err := os.OpenFile("README.txt", os.O_RDONLY, 0666)
if err != nil {
fmt.Println(err)
return
}
defer origin.Close()
// เปิดไฟล์สำเนาในโหมดเขียนอย่างเดียว
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()
// อ่านข้อมูลจากไฟล์ต้นฉบับ แล้วเขียนลงในไฟล์สำเนา
offset, err := target.ReadFrom(origin)
if err != nil {
fmt.Println(err)
return
}
fmt.Println("คัดลอกไฟล์สำเร็จ", offset)
}วิธีการคัดลอกนี้ต้องอ่านเนื้อหาทั้งหมดของไฟล์ต้นฉบับเข้าสู่หน่วยความจำก่อน แล้วจึงเขียนลงในไฟล์เป้าหมาย เมื่อไฟล์มีขนาดใหญ่มากไม่แนะนำให้ทำเช่นนี้
io.Copy
อีกวิธีหนึ่งคือการใช้ฟังก์ชัน io.Copy มันอ่านแล้วเขียนทีละอย่าง ก่อนอ่านเนื้อหาเข้าสู่บัฟเฟอร์ แล้วเขียนลงในไฟล์เป้าหมาย ขนาดบัฟเฟอร์เริ่มต้นคือ 32KB
func Copy(dst Writer, src Reader) (written int64, err error)ตัวอย่างการใช้งานมีดังนี้
func main() {
// เปิดไฟล์ต้นฉบับในโหมดอ่านอย่างเดียว
origin, err := os.OpenFile("README.txt", os.O_RDONLY, 0666)
if err != nil {
fmt.Println(err)
return
}
defer origin.Close()
// เปิดไฟล์สำเนาในโหมดเขียนอย่างเดียว
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()
// คัดลอก
written, err := io.Copy(target, origin)
if err != nil {
fmt.Println(err)
} else {
fmt.Println(written)
}
}คุณยังสามารถใช้ io.CopyBuffer เพื่อกำหนดขนาดบัฟเฟอร์ได้
การเปลี่ยนชื่อ
การเปลี่ยนชื่อสามารถเข้าใจได้ว่าเป็นการย้ายไฟล์ จะใช้ฟังก์ชัน Rename ภายใต้แพ็กเกจ os
func Rename(oldpath, newpath string) errorตัวอย่างมีดังนี้
func main() {
err := os.Rename("README.txt", "readme.txt")
if err != nil {
fmt.Println(err)
} else {
fmt.Println("เปลี่ยนชื่อสำเร็จ")
}
}ฟังก์ชันนี้มีผลเช่นเดียวกันสำหรับโฟลเดอร์
การลบ
操作การลบง่ายกว่า操作อื่นๆ มาก จะใช้เพียงสองฟังก์ชันภายใต้แพ็กเกจ os
// ลบไฟล์เดียวหรือไดเรกทอรีว่าง เมื่อไดเรกทอรีไม่ว่างจะส่งกลับข้อผิดพลาด
func Remove(name string) error
// ลบไฟล์และไดเรกทอรีทั้งหมดในไดเรกทอรีที่ระบุรวมถึงไดเรกทอรีย่อยและไฟล์ย่อย
func RemoveAll(path string) errorใช้งานง่ายมาก ด้านล่างเป็นตัวอย่างการลบไดเรกทอรี
func main() {
// ลบไฟล์และไดเรกทอรีย่อยทั้งหมดในไดเรกทอรีปัจจุบัน
err := os.RemoveAll(".")
if err != nil {
fmt.Println(err)
}else {
fmt.Println("ลบสำเร็จ")
}
}ด้านล่างเป็นตัวอย่างการลบไฟล์เดียว
func main() {
// ลบไฟล์และไดเรกทอรีย่อยทั้งหมดในไดเรกทอรีปัจจุบัน
err := os.Remove("README.txt")
if err != nil {
fmt.Println(err)
} else {
fmt.Println("ลบสำเร็จ")
}
}การ刷新
os.Sync เป็นฟังก์ชันที่ห่อหุ้มการเรียกใช้ระบบ Fsync ด้านล่าง ใช้สำหรับบังคับการเขียน IO ที่แคชไว้ในระบบปฏิบัติการลงดิสก์
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)
}
//刷盘
if err := create.Sync();err != nil {
return
}
}โฟลเดอร์
หลาย操作ของโฟลเดอร์คล้ายกับ操作ไฟล์
การอ่าน
สำหรับโฟลเดอร์แล้ว มีสองวิธีในการเปิด
os.ReadDir
วิธี第一种คือการใช้ฟังก์ชัน os.ReadDir
func ReadDir(name string) ([]DirEntry, error)func main() {
// ไดเรกทอรีปัจจุบัน
dir, err := os.ReadDir(".")
if err != nil {
fmt.Println(err)
} else {
for _, entry := range dir {
fmt.Println(entry.Name())
}
}
}*os.File.ReadDir
วิธีที่สองคือการใช้ฟังก์ชัน *os.File.ReadDir โดยพื้นฐานแล้ว os.ReadDir เป็นการห่อหุ้มอย่างง่ายสำหรับ *os.File.ReadDir
// เมื่อ n < 0 จะอ่านเนื้อหาทั้งหมดในโฟลเดอร์
func (f *File) ReadDir(n int) ([]DirEntry, error)func main() {
// ไดเรกทอรีปัจจุบัน
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())
}
}
}การสร้าง
操作สร้างโฟลเดอร์จะใช้สองฟังก์ชันภายใต้แพ็กเกจ os
// สร้างไดเรกทอรีชื่อที่ระบุด้วยสิทธิ์ที่ระบุ
func Mkdir(name string, perm FileMode) error
// เทียบกับ前者ฟังก์ชันนี้จะสร้างไดเรกทอรีพ่อที่จำเป็นทั้งหมด
func MkdirAll(path string, perm FileMode) errorตัวอย่างมีดังนี้
func main() {
err := os.Mkdir("src", 0666)
if err != nil {
fmt.Println(err)
} else {
fmt.Println("สร้างสำเร็จ")
}
}การคัดลอก
เราสามารถเขียนฟังก์ชันวนซ้ำทั้งโฟลเดอร์ได้ แต่ไลบรารีมาตรฐาน filepath ได้จัดเตรียมฟังก์ชันที่มีฟังก์ชันคล้ายกันไว้แล้ว ดังนั้น可以直接ใช้ ตัวอย่างโค้ดการคัดลอกโฟลเดอร์อย่างง่ายมีดังนี้
func CopyDir(src, dst string) error {
// ตรวจสอบสถานะของโฟลเดอร์ต้นทาง
_, 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
}
// คำนวณเส้นทางสัมพัทธ์
rel, err := filepath.Rel(src, path)
if err != nil {
return err
}
// ต่อเส้นทางเป้าหมาย
destpath := filepath.Join(dst, rel)
// สร้างโฟลเดอร์
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
}
// สร้างไฟล์
if info.Mode().IsRegular() {
srcfile, err := os.Open(path)
if err != nil {
return err
}
// ต้องจำไว้เสมอว่าปิดไฟล์
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()
// คัดลอกเนื้อหาไฟล์
if _, err := io.Copy(destfile, srcfile); err != nil {
return err
}
return nil
}
return nil
})
}filepath.Walk จะวนซ้ำทั้งโฟลเดอร์ ในระหว่างกระบวนการ เมื่อพบโฟลเดอร์ก็สร้างโฟลเดอร์ เมื่อพบไฟล์ก็สร้างไฟล์ใหม่และคัดลอก โค้ดเทียบกับการคัดลอกไฟล์มีมากหน่อยแต่ไม่ซับซ้อน
