back to top

Lavorare con i file e il modulo fs in Node.js

In questa lezione della nostra guida a Node.js vedremo come lavorare con file e directory. Per far ciò useremo il modulo nativo fs il quale fornisce un gran numero di metodi per eseguire diversi tipi di operazioni. Come già detto in altre lezioni, non potendo trattare in maniera approfondita tutti gli argomenti, vi rimando alla documentazione ufficiale per maggiori dettagli e informazioni.

Modulo fs e operazioni sincrone e asincrone

Tutti i metodi del modulo fs hanno due versioni: una asincrona e una sincrona. (Nel caso delle funzioni sincrone, al nome della funzione viene appesa la parola Sync. Un esempio è il metodo fs.readFileSync()) La prima richiede che venga passato come ultimo argomento una funzione callback che a sua volta riceve diversi argomenti a seconda dell’operazione che si effettua. Il primo argomento della funzione callback (errore) è solitamente usato per la gestione degli errori. Se l’operazione compiuta è avvenuta correttamente, tale argomento sarà sempre pari a null o undefined. Al contrario, per la versione sincrona, dovremo racchiudere la funzione invocata all’interno di un blocco try/catch. In quest’ultimo caso, l’esecuzione di un programma si blocca in attesa che la funzione invocata per le operazioni di I/O termini il suo lavoro. Invece, per quanto riguarda la versione asincrona, uno script può continuare ad eseguire altre operazioni, come abbiamo già visto quando abbiamo parlato della libreria libuv e dell’Event Loop.

// index.js

const { readFile, readFileSync } = require('fs')  

// versione asincrona
readFile(__filename, (errore, data) => {
  if (errore) {
    throw errore;
  }
  console.log('\n### Lettura file con readFile: ###\n');
  console.log(data.toString()) 
})

// versione sincrona
const buffer = readFileSync(__filename);
console.log('\n### Lettura file con readFileSync ###\n');
console.log(buffer.toString());

Nell’esempio appena visto, usiamo la funzione readFile() nelle sue due versioni sincrona e asincrona per leggere lo stesso file index.js, contenente il codice che abbiamo eseguito col comando node index.js.

Lavorare con i file

Iniziamo ad usare alcune funzioni che ci permettono di leggere, creare, copiare, appendere dei dati e cancellare un file. Tutti questi metodi presentano una segnatura simile che, come abbiamo appena detto, prevede una funzione callback come ultimo argomento. Negli esempi che seguono, se non specificato diversamente, useremo sempre un file index.js in cui inseriremo del codice Javascript da eseguire e un semplice file di testo testo.txt. Faremo inoltre riferimento alla versione asincrona dei metodi messi a disposizione dal modulo fs.

Leggere un file

Come abbiamo appena visto, possiamo leggere il contenuto di un file con l’ausilio della funzione fs.readFile(path[, options], callback). Il primo argomento rappresenta il percorso relativo o assoluto del file da leggere. Su Windows, Linux e MacOS, se quest’ultimo è una cartella, si verifica un errore. Tra le opzioni interessanti che possiamo usare, vi è la possibilità di specificare la codifica dei caratteri di un file di testo. Il terzo argomento è una funzione che, a sua volta, riceve come primo argomento un eventuale errore e come secondo argomento il contenuto del file. Nel caso non sia stata specificata una precisa codifica dei caratteri, la callback riceverà come argomento un oggetto di tipo Buffer.

// index.js

const fs = require('fs');

fs.readFile('testo.txt', (errore, data) => {
  if ( errore ) {
    throw errore;
  }
  console.log(data);
  // <Buffer 46 75 73 63 65 20 64 61 70 69 62 75 73>
})

Creare un file o modificarne il contenuto

In maniera analoga possiamo scrivere delle informazioni all’interno di un file con la funzione fs.writeFile(file, data[, options], callback). Vediamo un esempio. Supponiamo che il file testo.txt non esista all’interno della directory prima dell’esecuzione di node index.js

const fs = require('fs');

const testo = "Semplice messaggio di prova."

const opzioni = {
  mode: 0o600,
}

fs.writeFile('testo.txt', testo,  opzioni, (errore) => {
  if ( errore ) {
    throw errore;
  }
  console.log('Funzione writeFile() eseguita correttamente!');
})

In questo esempio abbiamo creato un nuovo file all’interno del quale abbiamo inserito un messaggio di testo. Se il file non esiste all’interno della directory, viene creato. In caso contrario viene sovrascritto. Con l’oggetto opzioni abbiamo indicato quali devono essere i permessi del file testo.txt. (Garantiamo il privilegio di lettura e scrittura solo al proprietario del file)

-rw-r--r--   user  group  274B  index.js
-rw-------   user  group   30B  testo.txt

All’interno dell’oggetto opzioni, è anche possibile specificare un campo flag pari ad ‘a’ se si vogliono appendere i dati al contenuto di un file già esistente invece di sovrascriverlo. In alternativa esiste l’apposita funzione fs.apppendFile().

Copiare un file

A partire dalla versione v8.5.0, è possibile usare il metodo fs.copyFile(origine, destinazione[, flags], callback) per eseguire la copia di un file. È possibile specificare come terzo (flags) argomento un valore intero. L’unica opzione supportata è fs.constants.COPYFILE_EXCL che provoca il fallimento dell’operazione di copia nel caso il file di destinazione sia già presente.

// solo nel caso in cui abbiate installato Node v8.5.0

const fs = require('fs');
const { COPYFILE_EXCL } = fs.constants;

const origine = 'testo.txt';
const destinazione = 'copia_testo.txt';

// Usando il flag COPYFILE_EXCL, l'operazione fallisce se il file di destinazione esiste già.
fs.copyFile(origine, destinazione, COPYFILE_EXCL, (errore) => {
  if ( errore ) throw errore;

  console.log('Copia eseguita con succcesso');
});

Se eseguiamo node index.js una volta, visualizzeremo il messaggio di avvenuta copia del file. La seconda volta visualizzeremo invece un messaggio di errore come il seguente.

Error: 

EEXIST: file already exists, copyfile 'testo.txt' -> 'copia_testo.txt'

Cancellare un file

Vediamo ora come cancellare un file attraverso un altro semplicissimo esempio. Questa volta useremo la funzione fs.unlink(percorso, callback). La funzione callback riceve come unico argomento un possibile errore.

var fs = require("fs");

// rimuove il file testo.txt
fs.unlink('testo.txt', function(errore) {
  if (errore) throw errore;

  console.log('File rimosso con successo');
});

Alla prima esecuzione, verrà mostrato il messaggio che conferma l’avvenuta cancellazione del file. Se proviamo a eseguire nuovamente lo stesso script, otterremo un messaggio di errore, dal momento che il file non è più presente all’interno della directory corrente.

Lavorare con le directory

Vediamo rapidamente alcune utili funzioni per lavorare con le directory. Per prima cosa vedremo come creare una nuova directory.

Creare un nuova directory

Per creare una nuova cartella, possiamo usare il metodo fs.mkdir(percorso [, mode], callback). Col primo argomento indichiamo il nome o il percorso della directory che deve essere creata. Il secondo argomento può essere usato per settare i permessi della cartella. Infine, come già visto con gli altri metodi, l’ultimo argomento è una funzione callback che, a sua volta, riceverà come unico argomento un’eventuale errore. In questo caso vogliamo creare una directory dir all’interno della cartella corrente.

const fs = require('fs')  

fs.mkdir('./dir', (errore) => {
  if ( errore ) {
    throw errore;
  }
  console.log('Nuova directory creata con successo!');
})

La funzione fallisce se una o più directory lungo il percorso specificato non esistono.

const fs = require('fs')  

fs.mkdir('./directoryNonEsistente/dir', (errore) => {
  if ( errore ) {
    throw errore;
  }
  console.log('Nuova directory creata con successo!');
})

In questo caso riceveremo un messaggio di errore come il seguente.

Error: ENOENT: no such file or directory, mkdir './directoryNonEsistente/dir'

Per risolvere questo tipo di situazioni, potremmo realizzare una funzione apposita oppure usare il package mkdirp disponibile su npmjs.com

Leggere il contenuto di una directory

Per leggere il contenuto di una directory usiamo invece fs.readdir(percorsoDellaCartella [, options], callback). La funzione callback riceve in questo caso due argomenti (errore, files), in cui ‘files’ è un array contenente i nomi dei file presenti nella directory a eccezione di ‘.’ e ‘..’. Nell’esempio che segue, leggiamo il contenuto della cartella ‘dir’ presente all’interno della directory corrente che avrà quindi la seguente struttura.

.
|-- dir
|   |-- dir1
|   |   |-- file_3.txt
|   |-- file1.txt
|   |-- file2.txt
|-- index.js
const fs = require("fs");

fs.readdir("dir", (errore, files) => { 
  if (errore) { 
    throw errore;
  } 

  console.dir(files);
});

Otterremo come risultato il seguente array.

[ 'dir1', 'file1.txt', 'file2.txt' ]

Come potete notare, la funzione readdir si limita a leggere il contenuto della directory passata come argomento, ma non legge in maniera ricorsiva il contenuto di eventuali sottocartelle.

Rimuovere una directory

Se una directory è vuota, possiamo eliminarla con la funzione fs.rmdir(percorso, callback). In caso contenga almeno un file riceveremo però un messaggio di errore. Riprendendo l’esempio appena visto, se proviamo a eliminare la cartella ‘dir’, la funzione fs.rmdir() fallisce.

const fs = require("fs");

fs.rmdir('./dir', (errore) => {
  if (errore) {
    throw errore;
  }

  console.log('Directory rimossa con successo');
})

Verrà mostrato il seguente messaggio d’errore.

Error: ENOTEMPTY: directory not empty, rmdir './dir'

Esistono vari package su npm per rimuovere delle cartelle che contengono dei file al loro interno. Fra questi il più interessante è rimraf che è l’equivalente del comando ‘rm -rf’ per Node.js. Facendo riferimento agli esempi visti in precedenza, all’interno della cartella contenente la directory ‘dir’ e il file index.js lanciamo il comando npm init -y per inizializzare il progetto. Eseguiamo poi il comando npm install rimraf –save che aggiungerà il package all’elenco delle dipendenze.

const fs = require("fs");
const rimraf = require('rimraf');

rimraf('./dir', (errore) => {
  if (errore) {
    throw errore;
  }
  console.log('Cartella, file e sottocartelle cancellate!');
})

Il metodo fs.stat()

Per concludere, vi segnalo un metodo molto utile, ovvero fs.stat(). (Sono presenti anche delle varianti come fs.lstat(). Per maggiori dettagli potete consultare la documentazione)

Il metodo fs.stat(percorso, callback) permette di ottenere varie informazioni in merito a un file. La callback riceve due argomenti (errore, stats). L’oggetto stats è un’istanza di fs.Stats e presenta vari metodi interessanti come stats.isFile() e stats.isDirectory() che restituiscono un valore booleano. L’oggetto stats contiene anche dati come la dimensione, l’ultimo accesso o l’istante di modifica di un file.

var fs = require("fs");

fs.stat('./dir', (errore, stats) => {
  if (errore) {
    throw errore;
  }
  console.log(stats.isDirectory()); // true
})

Conclusioni

Nella prossima lezione vedremo come sia possibile ottimizzare le operazioni sui file e non solo grazie agli Stream. Illustreremo alcuni esempi in cui useremo gli Stream anche attraverso il meccanismo delle Pipe.

Pubblicitร