Skip to content

Dosya

Go dilinin dosya işleme için sağladığı standart kütüphaneler大致 olarak şunlardır:

  • os kütüphanesi, OS dosya sistemi etkileşiminin somut uygulamasından sorumludur
  • io kütüphanesi, IO okuma/yazma soyutlama katmanıdır
  • fs kütüphanesi, dosya sistemi soyutlama katmanıdır

Bu makalede Go dili ile temel dosya işlemenin nasıl yapılacağı anlatılacaktır.

Açma

Dosya açmanın iki yaygın yolu, os paketi tarafından sağlanan iki fonksiyonu kullanmaktır. Open fonksiyonu bir dosya işaretçisi ve bir hata döndürür.

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

İkincisi OpenFile daha ince taneli kontrol sağlayabilir. Open fonksiyonu OpenFile fonksiyonunun basit bir sarmalamasıdır.

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

Önce ilk kullanım yöntemini tanıtalım. Doğrudan ilgili dosya adını sağlayın. Kod şu şekildedir

go
func main() {
   file, err := os.Open("README.txt")
   fmt.Println(file, err)
}

Dosya arama yolu varsayılan olarak proje go.mod dosyasının bulunduğu yoldur. Proje altında README.txt dosyası olmadığı için doğal olarak bir hata döndürür.

<nil> open README.txt: The system cannot find the file specified.

IO hata türleri çok olduğundan, dosyanın var olup olmadığını manuel olarak belirlemeniz gerekir. Aynı şekilde os paketi bunun için kolaylık fonksiyonları sağlar. Değiştirilen kod şu şekildedir

go
func main() {
  file, err := os.Open("README.txt")
  if os.IsNotExist(err) {
    fmt.Println("Dosya mevcut değil")
  } else if err != nil {
    fmt.Println("Dosya erişim hatası")
  } else {
    fmt.Println("Dosya okuma başarılı", file)
  }
}

Tekrar çalıştırıldığında çıktı şu şekildedir

Dosya mevcut değil

Aslında ilk fonksiyon sadece salt okunur dosya okur, değiştirilemez

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

OpenFile fonksiyonu ile daha fazla detay kontrol edilebilir. Örneğin dosya tanımlayıcısı ve dosya izinleri değiştirilebilir. Dosya tanımlayıcısı hakkında, os paketi aşağıdaki sabitleri kullanıma sunar.

go
const (
   // Salt okunur, sadece yaz, oku-yaz üçünden biri belirtilmelidir
   O_RDONLY int = syscall.O_RDONLY // Dosyayı salt okunur modda aç
   O_WRONLY int = syscall.O_WRONLY // Dosyayı sadece yazma modunda aç
   O_RDWR   int = syscall.O_RDWR   // Dosyayı oku-yaz modunda aç
   // Kalan değerler davranışı kontrol eder
   O_APPEND int = syscall.O_APPEND // Dosya yazarken, veriyi dosya sonuna ekle
   O_CREATE int = syscall.O_CREAT  // Dosya mevcut değilse oluştur
   O_EXCL   int = syscall.O_EXCL   // O_CREATE ile birlikte kullanılır, dosya mevcut olmamalıdır
   O_SYNC   int = syscall.O_SYNC   // Dosyayı senkron IO modunda aç
   O_TRUNC  int = syscall.O_TRUNC  // Açıldığında yazılabilir dosyayı kes
)

Dosya izinleri için aşağıdaki sabitler sağlanır.

go
const (
   ModeDir        = fs.ModeDir        // d: Dizin
   ModeAppend     = fs.ModeAppend     // a: Sadece ekleme
   ModeExclusive  = fs.ModeExclusive  // l: Özel
   ModeTemporary  = fs.ModeTemporary  // T: Geçici dosya
   ModeSymlink    = fs.ModeSymlink    // L: Sembolik bağlantı
   ModeDevice     = fs.ModeDevice     // D: Aygıt dosyası
   ModeNamedPipe  = fs.ModeNamedPipe  // p: Adlı boru (FIFO)
   ModeSocket     = fs.ModeSocket     // S: Unix alan soketi
   ModeSetuid     = fs.ModeSetuid     // u: setuid
   ModeSetgid     = fs.ModeSetgid     // g: setgid
   ModeCharDevice = fs.ModeCharDevice // c: Unix karakter aygıtı, ModeDevice ayarlanmış olmalı
   ModeSticky     = fs.ModeSticky     // t: Yapışkan bit
   ModeIrregular  = fs.ModeIrregular  // ?: Düzensiz dosya

   // Tür bitleri maskesi. Normal dosyalar için hiçbir şey ayarlanmaz.
   ModeType = fs.ModeType

   ModePerm = fs.ModePerm // Unix izin bitleri, 0o777
)

Aşağıda bir dosyayı oku-yaz modunda açan kod örneği bulunmaktadır. İzin 0666'dır, yani herkesin dosyayı okuyup yazabileceği anlamına gelir ve mevcut değilse otomatik olarak oluşturulur.

go
func main() {
  file, err := os.OpenFile("README.txt", os.O_RDWR|os.O_CREATE, 0666)
  if os.IsNotExist(err) {
    fmt.Println("Dosya mevcut değil")
  } else if err != nil {
    fmt.Println("Dosya erişim hatası")
  } else {
    fmt.Println("Dosya açma başarılı", file.Name())
    file.Close()
  }
}

Çıktı şu şekildedir

Dosya açma başarılı README.txt

Eğer dosyayı okumak istemiyor, sadece bazı bilgilerini almak istiyorsanız, os.Stat() fonksiyonunu kullanabilirsiniz. Kod örneği şu şekildedir

go
func main() {
  fileInfo, err := os.Stat("README.txt")
  if err != nil {
    fmt.Println(err)
  } else {
    fmt.Println(fmt.Sprintf("%+v", fileInfo))
  }
}

Çıktı şu şekildedir

&{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}

::: uyarı

Bir dosyayı açtıktan sonra her zaman dosyayı kapatmayı unutmayın. Genellikle kapatma işlemi defer ifadesine konur

go
defer file.Close()

:::

Okuma

Dosya başarıyla açıldıktan sonra, okuma işlemi yapılabilir. Dosya okuma işlemi için, *os.File türü aşağıdaki genel yöntemleri sağlar

go
// Dosyayı gelen byte slice içine oku
func (f *File) Read(b []byte) (n int, err error)

// İlkine göre belirli bir ofsetten okuyabilir
func (f *File) ReadAt(b []byte, off int64) (n int, err error)

Çoğu durumda ilk yöntem daha çok kullanılır. İlk yöntem için, okuma sırasında slice'ın dinamik olarak genişletilmesi için kendi mantığınızı yazmanız gerekir. Kod şu şekildedir

go
func ReadFile(file *os.File) ([]byte, error) {
  buffer := make([]byte, 0, 512)
  for {
    // Kapasite yetersiz olduğunda
    if len(buffer) == cap(buffer) {
      // Genişlet
      buffer = append(buffer, 0)[:len(buffer)]
    }
    // Dosyayı okumaya devam et
    offset, err := file.Read(buffer[len(buffer):cap(buffer)])
    // Yazılan veriyi slice'a dahil et
    buffer = buffer[:len(buffer)+offset]
    // Hata oluştuğunda
    if err != nil {
      if errors.Is(err, io.EOF) {
        err = nil
      }
      return buffer, err
    }
  }
}

Kalan mantık şu şekildedir

go
func main() {
   file, err := os.OpenFile("README.txt", os.O_RDWR|os.O_CREATE, 0666)
   if err != nil {
      fmt.Println("Dosya erişim hatası")
   } else {
      fmt.Println("Dosya açma başarılı", file.Name())
      bytes, err := ReadFile(file)
      if err != nil {
         fmt.Println("Dosya okuma hatası", err)
      } else {
         fmt.Println(string(bytes))
      }
      file.Close()
   }
}

Çıktı şu şekildedir

Dosya açma başarılı README.txt
hello world!

Bunun dışında, dosya okumak için iki kolaylık fonksiyonu kullanabilirsiniz. Bunlar sırasıyla os paketindeki ReadFile fonksiyonu ve io paketindeki ReadAll fonksiyonudur. os.ReadFile için sadece dosya yolunu sağlamanız yeterlidir. io.ReadAll için ise io.Reader türünün bir uygulamasını sağlamanız gerekir.

os.ReadFile

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

Kullanım örneği şu şekildedir

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

Çıktı şu şekildedir

hello world!

io.ReadAll

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

Kullanım örneği şu şekildedir

go
func main() {

   file, err := os.OpenFile("README.txt", os.O_RDWR|os.O_CREATE, 0666)
   if err != nil {
      fmt.Println("Dosya erişim hatası")
   } else {
      fmt.Println("Dosya açma başarılı", file.Name())
      bytes, err := io.ReadAll(file)
      if err != nil {
         fmt.Println(err)
      } else {
         fmt.Println(string(bytes))
      }
      file.Close()
   }
}
Dosya açma başarılı README.txt
hello world!

Yazma

os.File yapısı veri yazmak için aşağıdaki yöntemleri sağlar

go
// Byte slice yaz
func (f *File) Write(b []byte) (n int, err error)

// String yaz
func (f *File) WriteString(s string) (n int, err error)

// Belirli bir konumdan yazmaya başla. os.O_APPEND modunda açıldığında hata döndürür
func (f *File) WriteAt(b []byte, off int64) (n int, err error)

Bir dosyaya veri yazmak istiyorsanız, O_WRONLY veya O_RDWR modunda açmalısınız. Aksi takdirde dosyaya başarıyla yazamazsınız. Aşağıda os.O_RDWR|os.O_CREATE|os.O_APPEND|os.O_TRUNC modunda dosya açan ve 0666 izni ile belirtilen veri yazan bir örnek bulunmaktadır

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("Dosya erişim hatası")
  } else {
    fmt.Println("Dosya açma başarılı", 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())
  }
}

Dosya os.O_APPEND modunda açıldığından, dosyaya yazılırken veri dosyanın sonuna eklenir. İşlem tamamlandıktan sonra dosya içeriği şu şekildedir

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

Dosyaya byte slice yazmak da benzer bir işlemdir, tekrar anlatmayacağız. Dosya yazma işlemleri için standart kütüphane kolaylık fonksiyonları sağlar. Bunlar sırasıyla os.WriteFile ve io.WriteString

os.WriteFile

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

Kullanım örneği şu şekildedir

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

Bu durumda dosya içeriği şu şekildedir

txt
hello world!

io.WriteString

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

Kullanım örneği şu şekildedir

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("Dosya erişim hatası")
   } else {
      fmt.Println("Dosya açma başarılı", 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 fonksiyonu dosya oluşturmak için kullanılır. Temelde OpenFile'ın bir sarmalamasıdır.

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

::: uyarı

Bir dosya oluştururken, üst dizini mevcut değilse, oluşturma başarısız olur ve hata döndürür.

:::

Kopyalama

Dosya kopyalama için, iki dosyayı aynı anda açmanız gerekir. İlk yöntem, orijinal dosyadaki veriyi okuyup hedef dosyaya yazmaktır. Kod örneği şu şekildedir

go
func main() {
    // Orijinal dosyadan veri oku
  data, err := os.ReadFile("README.txt")
  if err != nil {
    fmt.Println(err)
    return
  }
    // Hedef dosyaya yaz
  err = os.WriteFile("README(1).txt", data, 0666)
  if err != nil {
    fmt.Println(err)
  } else {
    fmt.Println("Kopyalama başarılı")
  }
}

*os.File.ReadFrom

Diğer yöntem os.File tarafından sağlanan ReadFrom yöntemini kullanmaktır. Dosyayı açarken, biri salt okunur, diğeri sadece yazma.

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

Kullanım örneği şu şekildedir

go
func main() {
  // Orijinal dosyayı salt okunur olarak aç
  origin, err := os.OpenFile("README.txt", os.O_RDONLY, 0666)
  if err != nil {
    fmt.Println(err)
    return
  }
  defer origin.Close()
  // Kopya dosyasını sadece yazma olarak aç
  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()
  // Orijinal dosyadan veri oku, sonra kopya dosyaya yaz
  offset, err := target.ReadFrom(origin)
  if err != nil {
    fmt.Println(err)
    return
  }
  fmt.Println("Dosya kopyalama başarılı", offset)
}

Bu kopyalama yöntemi önce kaynak dosyanın tüm içeriğini belleğe okur, sonra hedef dosyaya yazar. Dosya çok büyük olduğunda bu önerilmez.

io.Copy

Diğer yöntem io.Copy fonksiyonunu kullanmaktır. Bu bir yandan okurken diğer yandan yazar. Önce içeriği arabelleğe okur, sonra hedef dosyaya yazar. Varsayılan arabellek boyutu 32KB'dır.

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

Kullanım örneği şu şekildedir

go
func main() {
  // Orijinal dosyayı salt okunur olarak aç
  origin, err := os.OpenFile("README.txt", os.O_RDONLY, 0666)
  if err != nil {
    fmt.Println(err)
    return
  }
  defer origin.Close()
  // Kopya dosyasını sadece yazma olarak aç
  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()
  // Kopyala
  written, err := io.Copy(target, origin)
  if err != nil {
    fmt.Println(err)
  } else {
    fmt.Println(written)
  }
}

Arabellek boyutunu belirtmek için io.CopyBuffer kullanabilirsiniz.

Yeniden Adlandırma

Yeniden adlandırma aynı zamanda dosya taşıma olarak da anlaşılabilir. os paketindeki Rename fonksiyonunu kullanır.

go
func Rename(oldpath, newpath string) error

Örnek şu şekildedir

go
func main() {
  err := os.Rename("README.txt", "readme.txt")
  if err != nil {
    fmt.Println(err)
  } else {
    fmt.Println("Yeniden adlandırma başarılı")
  }
}

Bu fonksiyon klasörler için de aynı etkiye sahiptir.

Silme

Silme işlemi diğer işlemlere göre çok daha basittir. Sadece os paketindeki iki fonksiyonu kullanır

go
// Tek bir dosya veya boş dizini sil. Dizin boş değilse hata döndürür
func Remove(name string) error

// Belirtilen dizindeki tüm dosya ve dizinleri alt dizinler ve alt dosyalar dahil sil
func RemoveAll(path string) error

Kullanımı çok basittir. Aşağıda dizin silme örneği bulunmaktadır

go
func main() {
  // Mevcut dizindeki tüm dosya ve alt dizinleri sil
  err := os.RemoveAll(".")
  if err != nil {
    fmt.Println(err)
  }else {
    fmt.Println("Silme başarılı")
  }
}

Aşağıda tek bir dosya silme örneği bulunmaktadır

go
func main() {
  // Mevcut dizindeki tüm dosya ve alt dizinleri sil
  err := os.Remove("README.txt")
  if err != nil {
    fmt.Println(err)
  } else {
    fmt.Println("Silme başarılı")
  }
}

Yenileme

os.Sync fonksiyonu alt sistem çağrısı Fsync'yi sarmalar. İşletim sistemindeki önbelleğe alınmış IO'nun diske yazılmasını sağlar

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

    // Diske yaz
  if err := create.Sync();err != nil {
    return
  }
}

Klasör

Klasörün birçok işlemi dosya işlemlerine benzer

Okuma

Klasör için, açmanın iki yolu vardır

os.ReadDir

İlk yol os.ReadDir fonksiyonunu kullanmaktır

go
func ReadDir(name string) ([]DirEntry, error)
go
func main() {
   // Mevcut dizin
   dir, err := os.ReadDir(".")
   if err != nil {
      fmt.Println(err)
   } else {
      for _, entry := range dir {
         fmt.Println(entry.Name())
      }
   }
}

*os.File.ReadDir

İkinci yol *os.File.ReadDir fonksiyonunu kullanmaktır. os.ReadDir temelde *os.File.ReadDir'ın basit bir sarmalamasıdır.

go
// n < 0 olduğunda, klasördeki tüm içeriği oku
func (f *File) ReadDir(n int) ([]DirEntry, error)
go
func main() {
   // Mevcut dizin
   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())
      }
   }
}

Oluşturma

Klasör oluşturma işlemi os paketindeki iki fonksiyonu kullanır

go
// Belirtilen izinle belirtilen adlı dizini oluştur
func Mkdir(name string, perm FileMode) error

// İlkine göre bu fonksiyon tüm gerekli üst dizinleri oluşturur
func MkdirAll(path string, perm FileMode) error

Örnek şu şekildedir

go
func main() {
  err := os.Mkdir("src", 0666)
  if err != nil {
    fmt.Println(err)
  } else {
    fmt.Println("Oluşturma başarılı")
  }
}

Kopyalama

Tüm klasörü yinelemeli olarak gezinebiliriz. Ancak filepath standart kütüphanesi benzer işlevsellikte fonksiyonlar sağlar. Bu nedenle doğrudan kullanılabilir. Basit bir klasör kopyalama kod örneği şu şekildedir.

go
func CopyDir(src, dst string) error {
    // Kaynak klasörün durumunu kontrol et
  _, 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
    }

        // Göreli yolu hesapla
    rel, err := filepath.Rel(src, path)
    if err != nil {
      return err
    }

        // Hedef yolu birleştir
    destpath := filepath.Join(dst, rel)

        // Klasör oluştur
    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
    }

        // Dosya oluştur
    if info.Mode().IsRegular() {
      srcfile, err := os.Open(path)
      if err != nil {
        return err
      }
            // Dosyayı kapatmayı unutmayın
      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()

            // Dosya içeriğini kopyala
      if _, err := io.Copy(destfile, srcfile); err != nil {
        return err
      }
      return nil
    }

    return nil
  })
}

filepath.Walk tüm klasörü yinelemeli olarak gezinir. Süreçte, klasörle karşılaşıldığında klasör oluşturur, dosyayla karşılaşıldığında yeni dosya oluşturur ve kopyalar. Kod dosya kopyalamaya göre biraz daha fazladır ancak karmaşık değildir.

Golang by www.golangdev.cn edit