Skip to content

ไฟล์

ไลบรารีมาตรฐานที่ Go ให้สำหรับการจัดการไฟล์มีประมาณต่อไปนี้:

  • ไลบรารี os รับผิดชอบการใช้งานเฉพาะของการโต้ตอบระบบไฟล์ OS
  • ไลบรารี io เป็นเลเยอร์นามธรรมสำหรับการอ่านเขียน IO
  • ไลบรารี fs เป็นเลเยอร์นามธรรมสำหรับระบบไฟล์

บทความนี้จะอธิบายวิธีการจัดการไฟล์พื้นฐานในภาษา Go

การเปิด

สองวิธีที่พบบ่อยในการเปิดไฟล์คือการใช้สองฟังก์ชันที่จัดเตรียมโดยแพ็กเกจ os ฟังก์ชัน Open ส่งกลับพอยน์เตอร์ไฟล์หนึ่งตัวและข้อผิดพลาดหนึ่งตัว

go
func Open(name string) (*File, error)

ฟังก์ชันหลัง OpenFile สามารถให้การควบคุมที่ละเอียดมากขึ้น ฟังก์ชัน Open เป็นการห่อหุ้มอย่างง่ายสำหรับฟังก์ชัน OpenFile

go
func OpenFile(name string, flag int, perm FileMode) (*File, error)

ก่อนอื่นมาดูวิธีการใช้งาน第一种 ให้ชื่อไฟล์ที่ตรงกันก็พอ โค้ดมีดังนี้

go
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 ก็ได้จัดเตรียมฟังก์ชันความสะดวกสำหรับ此事 โค้ดที่แก้ไขมีดังนี้

go
func main() {
  file, err := os.Open("README.txt")
  if os.IsNotExist(err) {
    fmt.Println("ไฟล์ไม่มีอยู่")
  } else if err != nil {
    fmt.Println("การเข้าถึงไฟล์ผิดปกติ")
  } else {
    fmt.Println("การอ่านไฟล์สำเร็จ", file)
  }
}

รันอีกครั้งผลลัพธ์มีดังนี้

ไฟล์ไม่มีอยู่

จริงๆ แล้วฟังก์ชัน第一种อ่านไฟล์ได้แบบอ่านอย่างเดียวเท่านั้น ไม่สามารถแก้ไขได้

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

ผ่านฟังก์ชัน OpenFile สามารถควบคุมรายละเอียดมากขึ้นได้ เช่น ตัวอธิบายไฟล์และสิทธิ์ไฟล์ เกี่ยวกับตัวอธิบายไฟล์ แพ็กเกจ os จัดเตรียมค่าคงที่ต่อไปนี้สำหรับการใช้งาน

go
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  // ตัดไฟล์ที่เขียนได้เมื่อเปิด
)

เกี่ยวกับสิทธิ์ไฟล์มีค่าคงที่ต่อไปนี้

go
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 หมายถึงทุกคนสามารถอ่านเขียนไฟล์นี้ได้ และจะสร้างอัตโนมัติเมื่อไม่มีอยู่

go
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() เพื่อดำเนินการ ตัวอย่างโค้ดมีดังนี้

go
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

go
defer file.Close()

การอ่าน

หลังจากเปิดไฟล์สำเร็จแล้ว ก็สามารถดำเนินการอ่านได้ เกี่ยวกับการดำเนินการอ่านไฟล์ ประเภท *os.File จัดเตรียมเมธอดสาธารณะต่อไปนี้

go
// อ่านไฟล์เข้าไปในสไลซ์ไบต์ที่ส่งเข้ามา
func (f *File) Read(b []byte) (n int, err error)

// เทียบกับ第一种สามารถอ่านจากออฟเซ็ตที่ระบุได้
func (f *File) ReadAt(b []byte, off int64) (n int, err error)

ส่วนใหญ่第一种ใช้มากกว่า สำหรับ第一种เมธอด ต้องเขียนตรรกะเองสำหรับการขยายสไลซ์แบบไดนามิกเมื่ออ่าน โค้ดมีดังนี้

go
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
    }
  }
}

ตรรกะที่เหลือมีดังนี้

go
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

go
func ReadFile(name string) ([]byte, error)

ตัวอย่างการใช้งานมีดังนี้

go
func main() {
  bytes, err := os.ReadFile("README.txt")
  if err != nil {
    fmt.Println(err)
  } else {
    fmt.Println(string(bytes))
  }
}

ผลลัพธ์มีดังนี้

hello world!

io.ReadAll

go
func ReadAll(r Reader) ([]byte, error)

ตัวอย่างการใช้งานมีดังนี้

go
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 จัดเตรียมเมธอดต่อไปนี้สำหรับการเขียนข้อมูล

go
// เขียนสไลซ์ไบต์
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 เพื่อเขียนข้อมูล指定的

go
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 ดังนั้นเมื่อเขียนไฟล์จะเพิ่มข้อมูลต่อท้ายไฟล์ หลังจากดำเนินการเสร็จสิ้นเนื้อหาไฟล์มีดังนี้

txt
hello world!
hello world!
hello world!
hello world!
hello world!

การเขียนสไลซ์ไบต์ลงในไฟล์ก็เป็นการดำเนินการที่คล้ายกัน จะไม่กล่าวซ้ำ สำหรับ的操作เขียนไฟล์ไลบรารีมาตรฐานได้จัดเตรียมฟังก์ชันความสะดวก分别是 os.WriteFile กับ io.WriteString

os.WriteFile

go
func WriteFile(name string, data []byte, perm FileMode) error

ตัวอย่างการใช้งานมีดังนี้

go
func main() {
  err := os.WriteFile("README.txt", []byte("hello world!\n"), 0666)
  if err != nil {
    fmt.Println(err)
  }
}

ขณะนี้เนื้อหาไฟล์มีดังนี้

txt
hello world!

io.WriteString

go
func WriteString(w Writer, s string) (n int, err error)

ตัวอย่างการใช้งานมีดังนี้

go
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 เช่นกัน

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

WARNING

เมื่อสร้างไฟล์หนึ่ง หากไดเรกทอรีพ่อของมันไม่มีอยู่ จะสร้างล้มเหลวและส่งกลับข้อผิดพลาด

การคัดลอก

สำหรับการคัดลอกไฟล์แล้ว ต้องเปิดสองไฟล์พร้อมกัน วิธี第一种คือการอ่านข้อมูลจากไฟล์ต้นฉบับ แล้วเขียนลงในไฟล์เป้าหมาย ตัวอย่างโค้ดมีดังนี้

go
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 จัดเตรียมไว้ เมื่อเปิดไฟล์ หนึ่งอ่านอย่างเดียว หนึ่งเขียนอย่างเดียว

go
func (f *File) ReadFrom(r io.Reader) (n int64, err error)

ตัวอย่างการใช้งานมีดังนี้

go
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

go
func Copy(dst Writer, src Reader) (written int64, err error)

ตัวอย่างการใช้งานมีดังนี้

go
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

go
func Rename(oldpath, newpath string) error

ตัวอย่างมีดังนี้

go
func main() {
  err := os.Rename("README.txt", "readme.txt")
  if err != nil {
    fmt.Println(err)
  } else {
    fmt.Println("เปลี่ยนชื่อสำเร็จ")
  }
}

ฟังก์ชันนี้มีผลเช่นเดียวกันสำหรับโฟลเดอร์

การลบ

操作การลบง่ายกว่า操作อื่นๆ มาก จะใช้เพียงสองฟังก์ชันภายใต้แพ็กเกจ os

go
// ลบไฟล์เดียวหรือไดเรกทอรีว่าง เมื่อไดเรกทอรีไม่ว่างจะส่งกลับข้อผิดพลาด
func Remove(name string) error

// ลบไฟล์และไดเรกทอรีทั้งหมดในไดเรกทอรีที่ระบุรวมถึงไดเรกทอรีย่อยและไฟล์ย่อย
func RemoveAll(path string) error

ใช้งานง่ายมาก ด้านล่างเป็นตัวอย่างการลบไดเรกทอรี

go
func main() {
  // ลบไฟล์และไดเรกทอรีย่อยทั้งหมดในไดเรกทอรีปัจจุบัน
  err := os.RemoveAll(".")
  if err != nil {
    fmt.Println(err)
  }else {
    fmt.Println("ลบสำเร็จ")
  }
}

ด้านล่างเป็นตัวอย่างการลบไฟล์เดียว

go
func main() {
  // ลบไฟล์และไดเรกทอรีย่อยทั้งหมดในไดเรกทอรีปัจจุบัน
  err := os.Remove("README.txt")
  if err != nil {
    fmt.Println(err)
  } else {
    fmt.Println("ลบสำเร็จ")
  }
}

การ刷新

os.Sync เป็นฟังก์ชันที่ห่อหุ้มการเรียกใช้ระบบ Fsync ด้านล่าง ใช้สำหรับบังคับการเขียน IO ที่แคชไว้ในระบบปฏิบัติการลงดิสก์

go
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

go
func ReadDir(name string) ([]DirEntry, error)
go
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

go
// เมื่อ n < 0 จะอ่านเนื้อหาทั้งหมดในโฟลเดอร์
func (f *File) ReadDir(n int) ([]DirEntry, error)
go
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

go
// สร้างไดเรกทอรีชื่อที่ระบุด้วยสิทธิ์ที่ระบุ
func Mkdir(name string, perm FileMode) error

// เทียบกับ前者ฟังก์ชันนี้จะสร้างไดเรกทอรีพ่อที่จำเป็นทั้งหมด
func MkdirAll(path string, perm FileMode) error

ตัวอย่างมีดังนี้

go
func main() {
  err := os.Mkdir("src", 0666)
  if err != nil {
    fmt.Println(err)
  } else {
    fmt.Println("สร้างสำเร็จ")
  }
}

การคัดลอก

เราสามารถเขียนฟังก์ชันวนซ้ำทั้งโฟลเดอร์ได้ แต่ไลบรารีมาตรฐาน filepath ได้จัดเตรียมฟังก์ชันที่มีฟังก์ชันคล้ายกันไว้แล้ว ดังนั้น可以直接ใช้ ตัวอย่างโค้ดการคัดลอกโฟลเดอร์อย่างง่ายมีดังนี้

go
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 จะวนซ้ำทั้งโฟลเดอร์ ในระหว่างกระบวนการ เมื่อพบโฟลเดอร์ก็สร้างโฟลเดอร์ เมื่อพบไฟล์ก็สร้างไฟล์ใหม่และคัดลอก โค้ดเทียบกับการคัดลอกไฟล์มีมากหน่อยแต่ไม่ซับซ้อน

Golang by www.golangdev.cn edit