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:
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.
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:
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:
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 existeDe hecho, la primera función solo lee archivos de solo lectura, no se pueden modificar:
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:
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:
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:
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.txtSi 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:
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:
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:
// 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:
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:
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
func ReadFile(name string) ([]byte, error)Ejemplo de uso:
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
func ReadAll(r Reader) ([]byte, error)Ejemplo de uso:
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:
// 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:
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:
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
func WriteFile(name string, data []byte, perm FileMode) errorEjemplo de uso:
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:
hello world!io.WriteString
func WriteString(w Writer, s string) (n int, err error)Ejemplo de uso:
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.
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:
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:
func (f *File) ReadFrom(r io.Reader) (n int64, err error)Ejemplo de uso:
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.
func Copy(dst Writer, src Reader) (written int64, err error)Ejemplo de uso:
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.
func Rename(oldpath, newpath string) errorEjemplo:
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:
// 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) errorSu uso es muy simple. A continuación se muestra un ejemplo de eliminación de directorio:
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:
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:
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:
func ReadDir(name string) ([]DirEntry, error)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.
// Cuando n < 0, lee todo el contenido de la carpeta
func (f *File) ReadDir(n int) ([]DirEntry, error)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:
// 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) errorEjemplo:
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:
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.
