Copiar, renombrar y borrar archivos en Go

Todo esto está en el paquete os y son llamadas de una línea. Lo único que conviene entender bien son dos parejas que se confunden: Remove frente a RemoveAll, y copiar cargando el archivo en memoria frente a copiarlo por trozos.

Copiar

Si el archivo es pequeño, dos líneas:

b, err := os.ReadFile("original.txt")
if err != nil {
	return err
}
err = os.WriteFile("copia.txt", b, 0644)

Pero eso carga el archivo entero en memoria. Con uno grande hay que copiarlo por trozos, y io.Copy lo hace solo:

func copiar(origen, destino string) error {
	o, err := os.Open(origen)
	if err != nil {
		return fmt.Errorf("abriendo %s: %w", origen, err)
	}
	defer o.Close()

	d, err := os.Create(destino)
	if err != nil {
		return fmt.Errorf("creando %s: %w", destino, err)
	}
	defer d.Close()

	if _, err := io.Copy(d, o); err != nil {
		return fmt.Errorf("copiando a %s: %w", destino, err)
	}
	return d.Sync()
}

io.Copy va leyendo y escribiendo en bloques, así que la memoria que usa no depende del tamaño del archivo. El Sync() del final fuerza el volcado a disco, que es lo que quieres si lo siguiente que va a pasar es apagar la máquina o mover el archivo.

Go no tiene una función «copiar archivo». No es un olvido: copiar bien significa decidir qué pasa con los permisos, las fechas, los enlaces y los archivos dispersos, y esas decisiones no son universales. Por eso te da las piezas y tú montas la que necesitas.

Renombrar y mover

os.Rename("copia.txt", "renombrado.txt")        // cambiar el nombre
os.Rename("copia2.txt", "respaldo/copia2.txt")  // moverlo de carpeta

Es la misma función: mover es renombrar con otra ruta. Y es una operación atómica… mientras no cruces de sistema de archivos. Si el origen y el destino están en discos distintos, Rename falla y hay que copiar y borrar a mano.

Borrar

fmt.Println(os.Remove("renombrado.txt"))
fmt.Println(os.Remove("fantasma.txt"))
fmt.Println(errors.Is(os.Remove("fantasma.txt"), fs.ErrNotExist))
salida<nil>
remove fantasma.txt: no such file or directory
true

Borrar algo que no existe sí es un error en Go, al contrario que en otros lenguajes. Si en tu caso eso da igual, se distingue con errors.Is(err, fs.ErrNotExist) en vez de ignorar el error entero.

Remove o RemoveAll

fmt.Println(os.Remove("respaldo"))      // una carpeta con cosas dentro
fmt.Println(os.RemoveAll("respaldo"))
fmt.Println(os.RemoveAll("respaldo"))   // y otra vez, ya borrada
salidaremove respaldo: directory not empty
<nil>
<nil>
Las dos diferencias, y las dos importan. RemoveAll borra la carpeta con todo lo que tenga dentro, recursivamente y sin preguntar. Y además no se queja si no existe: devuelve nil. Eso lo hace cómodo para limpiar, y peligroso si la ruta viene de una variable que podría estar vacía.

¿Existe?

if _, err := os.Stat(ruta); err == nil {
	// existe
} else if errors.Is(err, fs.ErrNotExist) {
	// no existe
} else {
	// existe pero algo falla: permisos, por ejemplo
}
salidaoriginal.txt   existe=true
fantasma.txt   existe=false

Fíjate en la tercera rama. «No pude mirarlo» no es lo mismo que «no está», y confundirlos es el origen de bastantes errores raros.

Y muchas veces no hace falta preguntar. Si lo siguiente que vas a hacer es abrir el archivo, ábrelo directamente y mira el error: entre la comprobación y la apertura puede cambiar todo, y te ahorras una llamada al sistema.

Resumen

Para Usa
Copiar algo pequeño os.ReadFile + os.WriteFile
Copiar algo grande io.Copy entre dos *os.File
Renombrar o mover os.Rename
Borrar un archivo o una carpeta vacía os.Remove
Borrar una carpeta con contenido os.RemoveAll, con cuidado
Saber si existe os.Stat + errors.Is(err, fs.ErrNotExist)

Todo el código se compiló y ejecutó con Go 1.25 antes de publicarla; las salidas y los mensajes de error están copiados de esa ejecución. Documentación oficial: paquete os.