Nei titoli e nei testi troverete qualche rimando cinematografico (ebbene si, sono un cinefilo). Se non vi interessano fate finta di non vederli, già che non sono fondamentali per la comprensione dei post...

Di questo blog ho mandato avanti, fino a Settembre 2018, anche una versione in Spagnolo. Potete trovarla su El arte de la programación en C. Buona lettura.

Visualizzazione post con etichetta os.WriteFile. Mostra tutti i post
Visualizzazione post con etichetta os.WriteFile. Mostra tutti i post

venerdì 9 ottobre 2026

The FileCopy
come scrivere una funzione di File Copy in C (e Go) - pt.3

R.J.MacReady: Qualcuno in questo campo non è quello che sembra. Al momento potrebbe trattarsi di uno o due di noi. Entro la primavera, potremmo essere tutti noi.
Childs: Allora, come facciamo a sapere chi è umano? Se fossi un’imitazione, un’imitazione perfetta, come faresti a capire se fossi davvero io?

(...una premessa: questo post (parte 3 di 3) è un remake di un mio vecchio post. Ma, anche se tratta lo stesso argomento, amplia e perfeziona un po' il discorso è mi è sembrato il caso di riproporlo. Leggete e mi direte...)

Dove eravamo rimasti? Ah, si! Nell'ultimo articolo (l'avete letto vero?) avevo proposto la versione buffered della funzione cpFile(), dopo aver proposto (nel penultimo articolo) la versione unbuffered e quella "special" che funziona in kernel-space. E oggi, come promesso (e le promesse si mantengono, sempre) vi racconterò di come si può fare in Go la stessa cosa. Ma è doveroso, anche per questa terza puntata, fornire un altra immagine tratta da The Thing del Maestro John Carpenter, per ricordare che questo, come ben sapete, è un blog per programmatori cinefili...

...le copie dei file vengono meglio col fresco...

Ok, veniamo al dunque: ho scritto due versioni in Go della cpFile(), la prima si può accomunare ai tipi buffered/unbuffered già visti, mentre la seconda si può paragonare al tipo "special". Cominciamo con la versione "normal", che ho chiamato cpbase.go. Vai col codice!

// cpbase.go - un cp(1) con os.ReadFile() e os.WriteFile()
package main

import (
"os"
"fmt"
)

// funzione main
func main() {

// test argomenti
argc := len(os.Args)
if (argc != 3) {
// errore: numero errato di argomenti: esco
fmt.Printf("%s: numero errato di argomenti\n", os.Args[0])
fmt.Printf("uso: %s src dest [e.g.: %s prova.c prova.bak]\n", os.Args[0], os.Args[0])
os.Exit(1)
}

// eseguo la copia
n_copied, err := cpFile(os.Args[2], os.Args[1])
if err != nil {
// errore: copia fallita: esco
fmt.Printf("%s: error: %s\n", os.Args[0], err);
os.Exit(1)
}

// esco con Ok
fmt.Printf("%s: copiati %d byte\n", os.Args[0], n_copied);
os.Exit(0)
}

// cpFile() - copia il file <src> nel file <dest>
func cpFile(dest string, src string) (n_copied int, err error) {

// leggo tutto il contenuto dal file sorgente e lo metto in <data []byte>
// NOTA: non usare per file molto grandi: potrebbe non bastare la memoria
data, err := os.ReadFile(src)
if err != nil {
// errore: ritorno 0 e l'errore
return
}

// scrivo il contenuto sul file destinazione (specificando i permessi)
err = os.WriteFile(dest, data, 0644)
if err != nil {
// errore: ritorno 0 e l'errore
return
}

// set byte copiati
n_copied = len(data)

// ritorno il numero di byte copiati ed errore nullo
return
}

Come avrete sicuramente notato (e letto nei dettagliatissimi commenti) il main() è praticamente identico a quello dei programmi C visti precedentemente, mentre la funzione cpFile(), pur comportandosi esattamente nello stesso modo di quella in C, è alquanto diversa e, direi, molto semplificata: leggo il sorgente (prima operazione), scrivo sulla destinazione (seconda operazione) e ricavo il numero di byte copiati (terza operazione). Praticamente sono tre linee più i tradizionali test degli errori tipici del Go, tutto qua! Il Go conferma ancora una volta di essere un vero linguaggio di alto livello.

Ma c'è un "ma". Avrete sicuramente visto la nota:

// NOTA: non usare per file molto grandi: potrebbe non bastare la memoria

Ecco, questa versione è adatta solo per file relativamente piccoli, perché il file, prima di essere copiato nella destinazione, viene caricato completamento nello slice di byte data (di tipo []byte) che è allocato nello heap: e l'heap ha una dimensione limitata, ragionevolmente proporzionale alla memoria fisica disponibile, quindi se il file è molto grande (chessoio, 100GB) avremo un problema di memoria. Quindi questa funzione è imperfetta: non funziona con qualsiasi file e quando non funziona genera un grave errore di memoria. Se proprio volete usarla, come minimo dovrete aggiungere un controllo preliminare sulla dimensione del file sorgente per ritornare, eventualmente, un errore del tipo "errore: il file è troppo grande".

E allora passiamo alla versione "special" quella che risolve il problema appena mostrato: vai col codice!

// cpspecial.go - un cp(1) con io.Copy()
package main

import (
"io"
"os"
"fmt"
)

// funzione main
func main() {

// test argomenti
argc := len(os.Args)
if (argc != 3) {
// errore: numero errato di argomenti: esco
fmt.Printf("%s: numero errato di argomenti\n", os.Args[0])
fmt.Printf("uso: %s src dest [e.g.: %s prova.c prova.bak]\n", os.Args[0], os.Args[0])
os.Exit(1)
}

// eseguo la copia
n_copied, err := cpFile(os.Args[2], os.Args[1])
if err != nil {
// errore: copia fallita: esco
fmt.Printf("%s: error: %s\n", os.Args[0], err);
os.Exit(1)
}

// esco con Ok
fmt.Printf("%s: copiati %d byte\n", os.Args[0], n_copied);
os.Exit(0)
}

// cpFile() - copia il file <src> nel file <dest>
func cpFile(dest string, src string) (n_copied int64, err error) {

// apro il file sorgente
fsrc, err := os.Open(src)
if err != nil {
// errore: ritorno 0 e l'errore
return
}

defer fsrc.Close() // chiusura differita

// apro il file destinazione
fdest, err := os.Create(dest)
if err != nil {
// errore: ritorno 0 e l'errore
return
}

defer fdest.Close() // chiusura differita

// copio da sorgente a destinazione usando io.Copy()
// NOTA: è il metodo più efficiente perché usa uno stream invece della memoria
n_copied, err = io.Copy(fdest, fsrc)
if err != nil {
// errore: ritorno 0 e l'errore
return
}

// ritorno il numero di byte copiati ed errore nullo
return
}

Interessante, no? Il main() è, ovviamente, invariato, mentre la funzione cpFile() è radicalmente diversa, pur mantenendo la grande compattezza di quella appena vista. Anche in questo caso sono tre operazioni: apro il file sorgente, apro (o creo, se necessario) il file destinazione e copio usando la funzione io.Copy() che genera uno stream di dati da un file all'altro e si ferma quando raggiunge l'EOF: Una cpFile()  pulita, compatta, super efficiente... e senza limitazioni sulla dimensione dei file! Mi piace.

E anche questa terza (e ultima) parte dell'articolo si può considerare conclusa. Il Go si dimostra, ancora una volta, una perfetta controparte ad alto livello del C (e non solo). Ma ricordate: tutti e due hanno ampie ragioni di essere: il C è, da sempre, il Re de linguaggi a basso livello multi-purpose (per kernel, firmware, librerie e funzioni di sistema, ecc.), mentre il Go è un fantastico linguaggio ad alto livello più adatto ad altri usi (microservizi, API Web, infrastrutture Cloud, ecc.).

E nel prossimo articolo... boh, ci devo ancora pensare. Restate sintonizzati!

Ciao, e al prossimo post!

P.S.

Dopo anni di tentennamenti ho, finalmente, deciso di aprire un cineblog! Se vi interessa lo trovate qui: Polvere di Cinema. Buona lettura!