Skip to content

4 Ficheros

Introducción

En Javascript no se puede trabajar con ficheros de forma nativa, pero gracias a Node JS y su módulo fs es posible poder trabajar con ficheros.

En Node JS se trabaja con ficheros de tres formas diferentes:

  • Haciendo uso de la programación síncrona.
  • Haciendo uso de la programación asíncrona con callbacks.
  • Haciendo uso de la programación asíncrona con await.
  • Haciendo uso de la programación asíncrona con promesas.

Algunos métodos del módulo tiene su versión síncrona y su versión asíncrona: readFile(), readFileSync().

Información de un fichero

El método statSync(path) devuelve información relativa de un fichero en un objeto de tipo Stat de forma síncrona, mientras que stat(path) devuelve la misma información pero de forma asíncrona. El atributo path es la ruta relativa o absoluta del fichero a comprobar.

Algunos métodos interesantes de la clase Stat:

  • isDirectory(): Comprueba si la ruta pertenece a un directorio.
  • isFile(): Comprueba si la ruta pertenece a un fichero.
  • isSymbolicLink(): Comprueba si es un enlace simbólico.
  • size: Devuelve el tamaño del fichero en bytes.

Info

Para conocer más métodos y atributos de la clase Stat: https://nodejs.org/api/fs.html#class-fsstats

const fs = require('node:fs') // a partir de Node 16, se recomienda poner node:

const stats = fs.statSync('../README.md')

console.log(
    stats.isFile(), // si es un fichero
    stats.isDirectory(), // si es un directorio
    stats.isSymbolicLink(), // si es un enlace simbólico
    stats.size // tamaño en bytes
)
const fs = require('node:fs') // a partir de Node 16, se recomienda poner node:

fs.stat('../README.md', (error, stat) => console.log(
    stat.isFile(), // si es un fichero
    stat.isDirectory(), // si es un directorio
    stat.isSymbolicLink(), // si es un enlace simbólico
    stat.size // tamaño en bytes
))

Leer un fichero

Para leer un fichero de forma síncrona existe el método readFileSync(path), donde path es la ruta relativa o absoluta del fichero a leer.

Danger

Si en lugar de indicar un fichero existente, si indica un fichero inexistente o un directorio, lanzará una excepción.

const fs = require('node:fs')

console.log('Leyendo el archivo en binario...')
let text = fs.readFileSync('./archivo.txt')
console.log(text)

El método devuelve la información del fichero en binario, para poder evitar esto se puede indicar un segundo parámetro con el tipo de codificación, como por ejemplo utf-8

const fs = require('node:fs')

console.log('Leyendo el archivo...')
let text = fs.readFileSync('./archivo2.txt', 'utf-8')
console.log(text)

Para poder leer una archivo de forma asíncrona se una el método readFile(path):

const fs = require('node:fs')

console.log('Leyendo el primer archivo...')
fs.readFile('./archivo.txt', 'utf-8', (err, text) => { // <---- ejecutas este callback
    console.log('primer texto:', text)
})

console.log('--> Hacer cosas mientras lee el archivo...')

console.log('Leyendo el segundo archivo...')
fs.readFile('./archivo2.txt', 'utf-8', (err, text) => {
    console.log('segundo texto:', text)
})

En la mayoría de módulos que trabajan de forma asíncrona existe una version con promesas. Para poder usarla es necesario importar el mismo módulo seguido de /promises.

const { readFile } = require('node:fs/promises')

async function init() {
    console.log('Leyendo el primer archivo...')
    const text = await readFile('./archivo.txt', 'utf-8')
    console.log('primer texto:', text)
    console.log('--> Hacer cosas mientras lee el archivo...')

    console.log('Leyendo el segundo archivo...')
    const secondText = await readFile('./archivo2.txt', 'utf-8')
    console.log('segundo texto:', secondText)
}

init()
// Esto sólo en los módulos nativos
// que no tienen promesas nativas

// const { promisify } = require('node:util')
// const readFilePromise = promisify(fs.readFile)

import { readFile } from 'node:fs/promises'

console.log('Leyendo el primer archivo...')
const text = await readFile('./archivo.txt', 'utf-8')
console.log('primer texto:', text)
console.log('--> Hacer cosas mientras lee el archivo...')

console.log('Leyendo el segundo archivo...')
const secondText = await readFile('./archivo2.txt', 'utf-8')
console.log('segundo texto:', secondText)

Como se puede observar con el CommonJS al usar await se ha tenido que envolver el código en una función. Esto se debe porque no existe el soporte con el await en funciones top-level, por lo que es necesario el uso de una función async que contenga el await. Esta es otra de las mejoras que se tiene con ES6, en el cual no es necesario realizar esa operación, ya que si existe soporte del await en funciones top-level.

El código de Common JS** se puede mejor haciendo uso del **IIFE (*Immediately Invoked function Expression)**.

const { readFile } = require('node:fs/promises')

    // IIFE - Immediately Invoked Function Expression
    ; (
        async () => {
            console.log('Leyendo el primer archivo...')
            const text = await readFile('./archivo.txt', 'utf-8')
            console.log('primer texto:', text)
            console.log('--> Hacer cosas mientras lee el archivo...')

            console.log('Leyendo el segundo archivo...')
            const secondText = await readFile('./archivo2.txt', 'utf-8')
            console.log('segundo texto:', secondText)
        }
    )()

Warning

Como se puede observar en el ejemplo, antes del uso de la función IIFE, se ante pone un ;. Esto es necesario y obligatorio en este caso, ya que si no sé usa el compilar entiende que forma parte de la función anterior, en este caso require, y por lo tanto dará error.

Se puede colocar antes del uso de la función tal y como aparece en el ejemplo:

const { readFile } = require('node:fs/promises')

;() {
    //...
}

O, también puede ir con la línea anterior:

const { readFile } = require('node:fs/promises');

() {
    //...
}

Los módulos que no traen de forma nativa las promesas se puede hacer uso del método promisify del módulo util que recibe el método asíncrono, el cual se desea usar con promesas.

const { promisify } = require('node:util')
const readFilePromise = promisify(fs.readFile)

Por otro lado, si no se quiere usar await, se puede seguir haciendo uso de promesas utilizando el método then:

const fs = require('node:fs/promises')

console.log('Leyendo el primer archivo...')
fs.readFile('./archivo.txt', 'utf-8')
  .then(text => {
    console.log('primer texto:', text)
  })

console.log('--> Hacer cosas mientras lee el archivo...')

console.log('Leyendo el segundo archivo...')
fs.readFile('./archivo2.txt', 'utf-8')
  .then(text => {
    console.log('segundo texto:', text)
  })

Por último, podemos unir la lectura de más de dos ficheros haciendo uso del método Promises.all(), de esta forma se ejecutaran de forma paralela:

Escribir ficheros

Para escribir ficheros de forma síncrona se utiliza el método writeFileSync(file, data, options) donde:

  • file es la ruta del fichero a escribir.
  • data es la información que se quiere añadir (puede ser una cadena o un buffer).
  • options puede ser una cadena o un objeto con las opciones a indicar, como el encoding, que por defecto es utf-8
const fs = require('node:fs')

const msg = "Esto es un fichero que se ha escrito con Node JS"

console.log("Escribiendo en el fichero")
fs.writeFileSync("archivo.txt", msg)

Si escribimos en el fichero más de una vez, lo que va a ocurrir es que el contenido del fichero va a ser sobrescrito y no añadido al final. Para poder solucionar este problema, hay que indicarle como opción a+ en el atributo flag:

const fs = require('node:fs')

const otherText = "\nSe esta añadiendo otro texto"

console.log("Añadiendo más texto")

fs.writeFileSync("archivo.txt", otherText, { flag: 'a+' })

La versión asíncrona para escribir archivos es writeFile(), además tiene su versión en fs/promises

const fs = require('node:fs')

console.log("Escribiendo en el fichero")

const msg = "Esta línea ha sido escrito con Node JS de forma asíncrona\n"
fs.writeFile("archivo.txt", msg, { flag: 'a+' }, console.log)
console.log("Terminado de escribir")
const { writeFile } = require('node:fs/promises')

; (async () => {
    console.log("Escribiendo en el fichero")

    const msg = "Esta línea ha sido escrito con Node JS de forma asíncrona con await y CommonJS\n"
    await writeFile("archivo.txt", msg, { flag: 'a+' })
    console.log("Terminado de escribir")
})()
import { writeFile } from 'node:fs/promises'

console.log("Escribiendo en el fichero")

const msg = "Esta línea ha sido escrito con Node JS de forma asíncrona con await y ES6\n"
await writeFile("archivo.txt", msg, { flag: 'a+' })
console.log("Terminado de escribir")
const { writeFile } = require('node:fs/promises')

console.log("Escribiendo en el fichero")

const msg = "Esta línea ha sido escrito con Node JS de forma asíncrona con promesas\n"
writeFile("archivo.txt", msg, { flag: 'a+' }).then(() => {
    console.log("Terminado de escribir")
})
Ejercicio 1

Realiza una aplicación llamada cpFile.js la cual pueda recibir como parámetros dos rutas de dos ficheros. El objetivo de la aplicación es copiar el contenido del primer fichero en el segundo fichero. Si el segundo fichero ya existe, sobrescribirá el fichero.

Si recibe menos de 2 ficheros, se saldrá de la aplicación mostrando un mensaje de error.

Listar ficheros

Para poder mostrar todos los ficheros de un directorio se puede usar el método readdir() del módulos fs:

const { readdir } = require('node:fs')
const process = require('node:process')

readdir('../', (err, files) => {
    if (err) {
        console.error('Ha habido un error:', err)
        process.exit(1)
    }

    files.forEach(console.log)
})

Info

Para conocer más método que trabajando con ficheros visita https://nodejs.org/api/fs.html

Módulo Path

El módulo Path es un módulo que se trabaja con rutas de formas sencilla. Es módulo incluido entre los módulos nativos de Node JS por lo que no necesita instalación.

Depende del sistema operativo en el que ejecutemos nuestro programa javascript las rutas son separadas de forma diferente. Por ejemplo en Windows se usa el separador \ mientras que en Linux /. Este módulo contiene un atributo sep que obtiene el separador de la ruta.

No se recomienda crear una ruta directamente, ya que esto puede suponer un error dependiendo del entorno donde se ejecute, por lo que se recomienda usar esta librería con sus métodos y atributos. Por ejemplo:

const path = require('node:path') // Se importa path
const sep = path.sep // Se obtiene el separador del OS

console.log(`files${sep}archivo.txt`)

En ese ejemplo puede mostrar files/archivo.txt si estamos trabajando en Linux o files\archivos.txt si estamos trabajando en Windows.

El ejemplo anterior se puede optimizar haciendo uso del método join() que recibe todas las partes de la ruta y devuelve una cadena con la ruta unida junto con el separador:

const path = require('node:path') // Se importa path

console.log(path.join('files', 'archivo.txt'))

Con el método basename() podemos obtener el nombre base del archivo. Por ejemplo, si nosotros tenemos la ruta de files/archivo.txt, devolverá archivos.txt. También recibe un segundo parámetro para obtener el nombre sin la extensión, debiéndose pasar la extensión a eliminar:

const path = require('node:path') // Se importa path

const myPath = path.join('files', 'archivo.txt')

console.log(path.basename(myPath)) // archivo.txt
console.log(path.basename(myPath), '.txt') // archivo

Si lo que se desea obtener es la extensión del archivo con el métodos extname() se puede visualizar:

const path = require('node:path') // Se importa path

const myPath = path.join('files', 'archivo.txt')
console.log(path.extname(myPath)) // .txt

Este método devolverá una cadena vacía si la extensión del archivo no es válida, por ejemplo archivo., en este caso no devolverá nada.

Otros métodos a tener en cuenta:

  • isAbsolute(): es un método que comprueba si la ruta es absoluta.
  • parse(path): retorna un objeto con información referente al path (string), como su raíz, el nombre del fichero, la extensión y el nombre base.
  • relative(from, to): obtiene la ruta relative que hay que invocar para llegar desde from hasta to.
Ejercicio 2

Realiza una aplicación, llamado ls.js, que liste los ficheros y directorios de un directorio indicado como parámetro.