Skip to content

Archivos

Las bibliotecas estándar que proporciona Go para el procesamiento de archivos son principalmente las siguientes:

  • Biblioteca os, responsable de la implementación concreta de la interacción con el sistema de archivos del SO
  • Biblioteca io, capa de abstracción para lectura y escritura de IO
  • Biblioteca fs, capa de abstracción del sistema de archivos

Este artículo explicará cómo realizar el procesamiento básico de archivos con Go.

Abrir

Las dos formas comunes de abrir un archivo son usar las dos funciones proporcionadas por el paquete os. La función Open devuelve un puntero a archivo y un error:

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

La segunda función OpenFile puede proporcionar un control más granular. La función Open es simplemente una envoltura de la función OpenFile.

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

Primero presentemos el primer método de uso, simplemente proporcionando el nombre del archivo correspondiente, como sigue:

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

La ruta de búsqueda del archivo es por defecto la ruta donde se encuentra el archivo go.mod del proyecto. Como no hay un archivo README.txt en el proyecto, naturalmente devolverá un error.

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

Como hay muchos tipos de errores de IO, es necesario determinar manualmente si el archivo existe. El paquete os también proporciona funciones convenientes para esto. El código modificado es el siguiente:

go
func main() {
  file, err := os.Open("README.txt")
  if os.IsNotExist(err) {
    fmt.Println("El archivo no existe")
  } else if err != nil {
    fmt.Println("Error al acceder al archivo")
  } else {
    fmt.Println("Archivo leído exitosamente", file)
  }
}

Ejecutando nuevamente, la salida es:

El archivo no existe

De hecho, la primera función solo lee archivos de solo lectura, no se pueden modificar:

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

A través de la función OpenFile se pueden controlar más detalles, como modificar el descriptor de archivo y los permisos del archivo. Para los descriptores de archivo, el paquete os proporciona las siguientes constantes para su uso:

go
const (
   // Solo lectura, solo escritura, lectura y escritura: se debe especificar uno de los tres
   O_RDONLY int = syscall.O_RDONLY // Abrir archivo en modo solo lectura
   O_WRONLY int = syscall.O_WRONLY // Abrir archivo en modo solo escritura
   O_RDWR   int = syscall.O_RDWR   // Abrir archivo en modo lectura y escritura
   // Los valores restantes se utilizan para controlar el comportamiento
   O_APPEND int = syscall.O_APPEND // Al escribir en el archivo, agregar datos al final del archivo
   O_CREATE int = syscall.O_CREAT  // Crear archivo si no existe
   O_EXCL   int = syscall.O_EXCL   // Usar con O_CREATE, el archivo no debe existir
   O_SYNC   int = syscall.O_SYNC   // Abrir archivo en modo IO síncrono
   O_TRUNC  int = syscall.O_TRUNC  // Truncar archivo escribible al abrir
)

Para los permisos de archivo, se proporcionan las siguientes constantes:

go
const (
   ModeDir        = fs.ModeDir        // d: Directorio
   ModeAppend     = fs.ModeAppend     // a: Solo agregar
   ModeExclusive  = fs.ModeExclusive  // l: Exclusivo
   ModeTemporary  = fs.ModeTemporary  // T: Archivo temporal
   ModeSymlink    = fs.ModeSymlink    // L: Enlace simbólico
   ModeDevice     = fs.ModeDevice     // D: Archivo de dispositivo
   ModeNamedPipe  = fs.ModeNamedPipe  // p: Tubo con nombre (FIFO)
   ModeSocket     = fs.ModeSocket     // S: Socket de dominio Unix
   ModeSetuid     = fs.ModeSetuid     // u: setuid
   ModeSetgid     = fs.ModeSetgid     // g: setgid
   ModeCharDevice = fs.ModeCharDevice // c: Dispositivo de caracteres Unix,前提是 que ModeDevice esté configurado
   ModeSticky     = fs.ModeSticky     // t: Bit sticky
   ModeIrregular  = fs.ModeIrregular  // ?: Archivo irregular

   // Máscara de bits de tipo. Para archivos regulares, no se configura nada.
   ModeType = fs.ModeType

   ModePerm = fs.ModePerm // Bits de permiso Unix, 0o777
)

A continuación se muestra un ejemplo de código para abrir un archivo en modo lectura y escritura, con permisos 0666, lo que significa que todos pueden leer y escribir en el archivo, y se creará automáticamente si no existe:

go
func main() {
  file, err := os.OpenFile("README.txt", os.O_RDWR|os.O_CREATE, 0666)
  if os.IsNotExist(err) {
    fmt.Println("El archivo no existe")
  } else if err != nil {
    fmt.Println("Error al acceder al archivo")
  } else {
    fmt.Println("Archivo abierto exitosamente", file.Name())
    file.Close()
  }
}

La salida es:

Archivo abierto exitosamente README.txt

Si solo deseas obtener información sobre el archivo sin leerlo, puedes usar la función os.Stat() para operar, como se muestra en el siguiente ejemplo:

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

La salida es:

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

Después de abrir un archivo, siempre recuerda cerrarlo. Generalmente, la operación de cierre se coloca en una declaración defer:

go
defer file.Close()

Leer

Cuando se ha abierto el archivo exitosamente, se puede proceder a la operación de lectura. Para la operación de lectura de archivos, el tipo *os.File proporciona los siguientes métodos públicos:

go
// Leer el archivo en el slice de bytes proporcionado
func (f *File) Read(b []byte) (n int, err error)

// En comparación con el primero, puede leer desde un desplazamiento específico
func (f *File) ReadAt(b []byte, off int64) (n int, err error)

En la mayoría de los casos, se usa más el primero. Para el primer método, es necesario escribir lógica personalizada para la expansión dinámica del slice durante la lectura, como sigue:

go
func ReadFile(file *os.File) ([]byte, error) {
  buffer := make([]byte, 0, 512)
  for {
    // Cuando la capacidad es insuficiente
    if len(buffer) == cap(buffer) {
      // Expandir
      buffer = append(buffer, 0)[:len(buffer)]
    }
    // Continuar leyendo el archivo
    offset, err := file.Read(buffer[len(buffer):cap(buffer)])
    // Incorporar los datos escritos en el slice
    buffer = buffer[:len(buffer)+offset]
    // Cuando ocurre un error
    if err != nil {
      if errors.Is(err, io.EOF) {
        err = nil
      }
      return buffer, err
    }
  }
}

La lógica restante es la siguiente:

go
func main() {
   file, err := os.OpenFile("README.txt", os.O_RDWR|os.O_CREATE, 0666)
   if err != nil {
      fmt.Println("Error al acceder al archivo")
   } else {
      fmt.Println("Archivo abierto exitosamente", file.Name())
      bytes, err := ReadFile(file)
      if err != nil {
         fmt.Println("Error al leer el archivo", err)
      } else {
         fmt.Println(string(bytes))
      }
      file.Close()
   }
}

La salida es:

Archivo abierto exitosamente README.txt
hello world!

Además, se pueden usar dos funciones convenientes para leer archivos: la función ReadFile del paquete os y la función ReadAll del paquete io. Para os.ReadFile, solo es necesario proporcionar la ruta del archivo. Para io.ReadAll, es necesario proporcionar una implementación del tipo io.Reader.

os.ReadFile

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

Ejemplo de uso:

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

La salida es:

hello world!

io.ReadAll

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

Ejemplo de uso:

go
func main() {

   file, err := os.OpenFile("README.txt", os.O_RDWR|os.O_CREATE, 0666)
   if err != nil {
      fmt.Println("Error al acceder al archivo")
   } else {
      fmt.Println("Archivo abierto exitosamente", file.Name())
      bytes, err := io.ReadAll(file)
      if err != nil {
         fmt.Println(err)
      } else {
         fmt.Println(string(bytes))
      }
      file.Close()
   }
}
Archivo abierto exitosamente README.txt
hello world!

Escribir

La estructura os.File proporciona los siguientes métodos para escribir datos:

go
// Escribir slice de bytes
func (f *File) Write(b []byte) (n int, err error)

// Escribir cadena
func (f *File) WriteString(s string) (n int, err error)

// Escribir desde una posición específica, devolverá error cuando se abre en modo os.O_APPEND
func (f *File) WriteAt(b []byte, off int64) (n int, err error)

Si deseas escribir datos en un archivo, debes abrirlo en modo O_WRONLY o O_RDWR, de lo contrario no se podrá escribir en el archivo. A continuación se muestra un ejemplo de abrir un archivo en modo os.O_RDWR|os.O_CREATE|os.O_APPEND|os.O_TRUNC con permisos 0666 y escribir datos:

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("Error al acceder al archivo")
  } else {
    fmt.Println("Archivo abierto exitosamente", 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())
  }
}

Como el archivo se abrió en modo os.O_APPEND, al escribir en el archivo, los datos se agregarán al final del archivo. Después de la ejecución, el contenido del archivo es el siguiente:

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

Escribir un slice de bytes en un archivo es una operación similar, no es necesario repetirlo. Para la operación de escritura en archivos, la biblioteca estándar también proporciona funciones convenientes: os.WriteFile e io.WriteString.

os.WriteFile

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

Ejemplo de uso:

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

En este punto, el contenido del archivo es:

txt
hello world!

io.WriteString

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

Ejemplo de uso:

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("Error al acceder al archivo")
   } else {
      fmt.Println("Archivo abierto exitosamente", 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!

La función os.Create se usa para crear archivos, esencialmente también es una envoltura de OpenFile.

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

WARNING

Al crear un archivo, si su directorio padre no existe, la creación fallará y devolverá un error.

Copiar

Para copiar un archivo, es necesario abrir dos archivos simultáneamente. El primer método es leer los datos del archivo original y luego escribirlos en el archivo de destino, como se muestra en el siguiente ejemplo:

go
func main() {
    // Leer datos del archivo original
  data, err := os.ReadFile("README.txt")
  if err != nil {
    fmt.Println(err)
    return
  }
    // Escribir en el archivo de destino
  err = os.WriteFile("README(1).txt", data, 0666)
  if err != nil {
    fmt.Println(err)
  } else {
    fmt.Println("Copiado exitosamente")
  }
}

*os.File.ReadFrom

Otro método es usar el método ReadFrom proporcionado por os.File. Al abrir el archivo, uno es de solo lectura y el otro es de solo escritura:

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

Ejemplo de uso:

go
func main() {
  // Abrir el archivo original en modo solo lectura
  origin, err := os.OpenFile("README.txt", os.O_RDONLY, 0666)
  if err != nil {
    fmt.Println(err)
    return
  }
  defer origin.Close()
  // Abrir el archivo de copia en modo solo escritura
  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()
  // Leer datos del archivo original y luego escribir en el archivo de copia
  offset, err := target.ReadFrom(origin)
  if err != nil {
    fmt.Println(err)
    return
  }
  fmt.Println("Archivo copiado exitosamente", offset)
}

Este método de copia requiere primero leer todo el contenido del archivo de origen en memoria y luego escribirlo en el archivo de destino. No se recomienda hacer esto cuando el archivo es particularmente grande.

io.Copy

Otro método es usar la función io.Copy, que lee y escribe simultáneamente, primero lee el contenido en un búfer y luego lo escribe en el archivo de destino. El tamaño predeterminado del búfer es de 32 KB.

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

Ejemplo de uso:

go
func main() {
  // Abrir el archivo original en modo solo lectura
  origin, err := os.OpenFile("README.txt", os.O_RDONLY, 0666)
  if err != nil {
    fmt.Println(err)
    return
  }
  defer origin.Close()
  // Abrir el archivo de copia en modo solo escritura
  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()
  // Copiar
  written, err := io.Copy(target, origin)
  if err != nil {
    fmt.Println(err)
  } else {
    fmt.Println(written)
  }
}

También puedes usar io.CopyBuffer para especificar el tamaño del búfer.

Renombrar

Renombrar también se puede entender como mover un archivo. Se usa la función Rename del paquete os.

go
func Rename(oldpath, newpath string) error

Ejemplo:

go
func main() {
  err := os.Rename("README.txt", "readme.txt")
  if err != nil {
    fmt.Println(err)
  } else {
    fmt.Println("Renombrado exitosamente")
  }
}

Esta función tiene el mismo efecto para carpetas.

Eliminar

La operación de eliminación es mucho más simple que otras operaciones, solo usa dos funciones del paquete os:

go
// Eliminar un solo archivo o directorio vacío, devolverá error cuando el directorio no esté vacío
func Remove(name string) error

// Eliminar todos los archivos y directorios del directorio especificado, incluidos subdirectorios y subarchivos
func RemoveAll(path string) error

Su uso es muy simple. A continuación se muestra un ejemplo de eliminación de directorio:

go
func main() {
  // Eliminar todos los archivos y subdirectorios del directorio actual
  err := os.RemoveAll(".")
  if err != nil {
    fmt.Println(err)
  }else {
    fmt.Println("Eliminación exitosa")
  }
}

A continuación se muestra un ejemplo de eliminación de un solo archivo:

go
func main() {
  // Eliminar todos los archivos y subdirectorios del directorio actual
  err := os.Remove("README.txt")
  if err != nil {
    fmt.Println(err)
  } else {
    fmt.Println("Eliminación exitosa")
  }
}

Sincronizar

La función os.Sync encapsula la llamada al sistema subyacente Fsync, utilizada para escribir la IO almacenada en caché del sistema operativo en el disco:

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

    // Sincronizar con disco
  if err := create.Sync();err != nil {
    return
  }
}

Carpetas

Muchas operaciones de carpetas son similares a las operaciones de archivos.

Leer

Para las carpetas, hay dos formas de abrirlas:

os.ReadDir

La primera forma es usar la función os.ReadDir:

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

*os.File.ReadDir

La segunda forma es usar la función *os.File.ReadDir. os.ReadDir es esencialmente solo una envoltura simple de *os.File.ReadDir.

go
// Cuando n < 0, lee todo el contenido de la carpeta
func (f *File) ReadDir(n int) ([]DirEntry, error)
go
func main() {
   // Directorio actual
   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())
      }
   }
}

Crear

La operación de crear carpetas usa dos funciones del paquete os:

go
// Crear directorio con el nombre especificado y los permisos especificados
func Mkdir(name string, perm FileMode) error

// En comparación con la anterior, esta función creará todos los directorios padres necesarios
func MkdirAll(path string, perm FileMode) error

Ejemplo:

go
func main() {
  err := os.Mkdir("src", 0666)
  if err != nil {
    fmt.Println(err)
  } else {
    fmt.Println("Creación exitosa")
  }
}

Copiar

Podemos escribir nuestra propia función para recorrer recursivamente toda la carpeta, pero la biblioteca estándar filepath ya proporciona funciones de funcionalidad similar, por lo que se pueden usar directamente. A continuación se muestra un ejemplo simple de código para copiar una carpeta:

go
func CopyDir(src, dst string) error {
    // Verificar el estado de la carpeta de origen
  _, 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
    }

        // Calcular ruta relativa
    rel, err := filepath.Rel(src, path)
    if err != nil {
      return err
    }

        // Concatenar ruta de destino
    destpath := filepath.Join(dst, rel)

        // Crear carpeta
    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
    }

        // Crear archivo
    if info.Mode().IsRegular() {
      srcfile, err := os.Open(path)
      if err != nil {
        return err
      }
            // Asegúrate de cerrar el archivo
      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()

            // Copiar contenido del archivo
      if _, err := io.Copy(destfile, srcfile); err != nil {
        return err
      }
      return nil
    }

    return nil
  })
}

filepath.Walk recorrerá recursivamente toda la carpeta. Durante el proceso, crea carpetas cuando encuentra carpetas, y crea nuevos archivos y copia cuando encuentra archivos. El código es un poco más extenso que copiar archivos, pero no es complejo.

Golang editado por www.golangdev.cn