Reemplace una cadena en un archivo con nodejs

167

Uso la tarea de gruñido md5 para generar nombres de archivo MD5. Ahora quiero cambiar el nombre de las fuentes en el archivo HTML con el nuevo nombre de archivo en la devolución de llamada de la tarea. Me pregunto cuál es la forma más fácil de hacer esto.

Andreas Köberle
fuente
1
Desearía que hubiera una combinación de cambio de nombre y reemplazo en archivo, que cambiaría el nombre de los archivos y buscaría / reemplazaría cualquier referencia para esos archivos también.
Brain2000

Respuestas:

303

Podrías usar expresiones regulares simples:

var result = fileAsString.replace(/string to be replaced/g, 'replacement');

Entonces...

var fs = require('fs')
fs.readFile(someFile, 'utf8', function (err,data) {
  if (err) {
    return console.log(err);
  }
  var result = data.replace(/string to be replaced/g, 'replacement');

  fs.writeFile(someFile, result, 'utf8', function (err) {
     if (err) return console.log(err);
  });
});
asgoth
fuente
Claro, pero ¿tengo que leer el archivo, reemplazar el texto y luego volver a escribir el archivo, o hay una manera más fácil, lo siento, soy más un tipo frontend.
Andreas Köberle
Tal vez haya un módulo de nodo para lograr esto, pero no lo sé. Se agregó un ejemplo completo por cierto.
asgoth
44
@Zax: Gracias, me sorprende que este 'error' pudiera sobrevivir tanto tiempo;)
asgoth
1
lo siento, sé que utf-8 admite muchos idiomas como: vietnamita, chino ...
vuhung3990
Si su cadena aparece varias veces en su texto, reemplazará solo la primera cadena que encuentre.
eltongonc
79

Como reemplazar no funcionaba para mí, he creado un paquete simple npm replace-in-file para reemplazar rápidamente el texto en uno o más archivos. Está parcialmente basado en la respuesta de @ asgoth.

Editar (3 de octubre de 2016) : el paquete ahora admite promesas y promesas, y las instrucciones de uso se han actualizado para reflejar esto.

Editar (16 de marzo de 2018) : el paquete ha acumulado más de 100k descargas mensuales ahora y se ha ampliado con características adicionales, así como una herramienta CLI.

Instalar en pc:

npm install replace-in-file

Requerir módulo

const replace = require('replace-in-file');

Especificar opciones de reemplazo

const options = {

  //Single file
  files: 'path/to/file',

  //Multiple files
  files: [
    'path/to/file',
    'path/to/other/file',
  ],

  //Glob(s) 
  files: [
    'path/to/files/*.html',
    'another/**/*.path',
  ],

  //Replacement to make (string or regex) 
  from: /Find me/g,
  to: 'Replacement',
};

Reemplazo asincrónico con promesas:

replace(options)
  .then(changedFiles => {
    console.log('Modified files:', changedFiles.join(', '));
  })
  .catch(error => {
    console.error('Error occurred:', error);
  });

Reemplazo asincrónico con devolución de llamada:

replace(options, (error, changedFiles) => {
  if (error) {
    return console.error('Error occurred:', error);
  }
  console.log('Modified files:', changedFiles.join(', '));
});

Reemplazo sincrónico:

try {
  let changedFiles = replace.sync(options);
  console.log('Modified files:', changedFiles.join(', '));
}
catch (error) {
  console.error('Error occurred:', error);
}
Adam Reis
fuente
3
Gran y fácil de usar módulo llave en mano. Lo usé con async / await y un globo sobre una carpeta bastante grande y fue muy rápido
Matt Fletcher
¿Será capaz de trabajar con los archivos de más de 256 Mb a continuación, ya que había leído en alguna parte que límite de la cadena en js nodo es de 256 Mb
Alien128
Creo que lo hará, pero también hay un trabajo en progreso para implementar el reemplazo de transmisión para archivos más grandes.
Adam Reis
1
bueno, encontré y usé este paquete (para su herramienta CLI) antes de leer esta respuesta SO. Me encanta
Russell Chisholm
38

Quizás el módulo "reemplazar" ( www.npmjs.org/package/replace ) también funcione para usted. No requeriría que leyeras y luego escribieras el archivo.

Adaptado de la documentación:

// install:

npm install replace 

// require:

var replace = require("replace");

// use:

replace({
    regex: "string to be replaced",
    replacement: "replacement string",
    paths: ['path/to/your/file'],
    recursive: true,
    silent: true,
});
Slack Undertow
fuente
¿Sabes cómo se puede filtrar por extensión de archivo en las rutas? algo así como rutas: ['ruta / a / tu / archivo / *. js'] -> no funciona
Kalamarico
Puede usar node-glob para expandir los patrones de glob a una variedad de rutas y luego iterar sobre ellas.
RobW
3
Esto es bueno, pero ha sido abandonado. Consulte stackoverflow.com/a/31040890/1825390 para obtener un paquete actualizado si desea una solución lista para usar.
xavdid
1
También hay una versión mantenida llamada node-replace ; sin embargo, al mirar la base del código, ni esto ni el reemplazo en el archivo realmente reemplazan el texto en el archivo, usan readFile()y writeFile()al igual que la respuesta aceptada.
c1moore
26

También puede usar la función 'sed' que es parte de ShellJS ...

 $ npm install [-g] shelljs


 require('shelljs/global');
 sed('-i', 'search_pattern', 'replace_pattern', file);

Visite ShellJs.org para más ejemplos.

Tony O'Hagan
fuente
esta parece ser la solución más limpia :)
Yerken
1
shxle permite ejecutar desde scripts npm, ShellJs.org lo recomendó. github.com/shelljs/shx
Joshua Robinson
Me gusta esto también. Mejor un oneliner que el módulo npm, pero líneas de código
topográficas
Importar una dependencia de terceros no es la solución más limpia.
four43
Esto no hará multilíneas.
chovy
5

Puede procesar el archivo mientras se lee utilizando secuencias. Es como usar buffers pero con una API más conveniente.

var fs = require('fs');
function searchReplaceFile(regexpFind, replace, cssFileName) {
    var file = fs.createReadStream(cssFileName, 'utf8');
    var newCss = '';

    file.on('data', function (chunk) {
        newCss += chunk.toString().replace(regexpFind, replace);
    });

    file.on('end', function () {
        fs.writeFile(cssFileName, newCss, function(err) {
            if (err) {
                return console.log(err);
            } else {
                console.log('Updated!');
            }
    });
});

searchReplaceFile(/foo/g, 'bar', 'file.txt');
sanbor
fuente
3
Pero ... ¿qué pasa si el fragmento divide la cadena regexpFind? ¿No falla la intención entonces?
Jaakko Karhu
Ese es un muy buen punto. Me pregunto si al establecer una bufferSizecadena más larga que la que está reemplazando y guardar el último fragmento y concatenar con el actual podría evitar ese problema.
sanbor
1
Probablemente este fragmento también debería mejorarse escribiendo el archivo modificado directamente en el sistema de archivos en lugar de crear una gran variable ya que el archivo podría ser más grande que la memoria disponible.
sanbor
1

Me encontré con problemas al reemplazar un pequeño marcador de posición con una gran cadena de código.

Estaba haciendo:

var replaced = original.replace('PLACEHOLDER', largeStringVar);

Descubrí que el problema eran los patrones de reemplazo especiales de JavaScript, descritos aquí . Dado que el código que estaba usando como la cadena de reemplazo tenía algunos$ , estaba estropeando la salida.

Mi solución fue usar la opción de reemplazo de funciones, que NO hace ningún reemplazo especial:

var replaced = original.replace('PLACEHOLDER', function() {
    return largeStringVar;
});
Anderspitman
fuente
1

ES2017 / 8 para el Nodo 7.6+ con un archivo de escritura temporal para reemplazo atómico.

const Promise = require('bluebird')
const fs = Promise.promisifyAll(require('fs'))

async function replaceRegexInFile(file, search, replace){
  let contents = await fs.readFileAsync(file, 'utf8')
  let replaced_contents = contents.replace(search, replace)
  let tmpfile = `${file}.jstmpreplace`
  await fs.writeFileAsync(tmpfile, replaced_contents, 'utf8')
  await fs.renameAsync(tmpfile, file)
  return true
}

Tenga en cuenta que solo para archivos pequeños, ya que se leerán en la memoria.

Mate
fuente
No es necesario bluebird, use native Promisey util.promisify .
Francisco Mateo
1
@FranciscoMateo True, pero más allá de 1 o 2 funciones promisifyAll sigue siendo súper útil.
Matt
1

En Linux o Mac, keep es simple y solo usa sed con el shell. No se requieren bibliotecas externas. El siguiente código funciona en Linux.

const shell = require('child_process').execSync
shell(`sed -i "s!oldString!newString!g" ./yourFile.js`)

La sintaxis de sed es un poco diferente en Mac. No puedo probarlo en este momento, pero creo que solo necesita agregar una cadena vacía después de "-i":

const shell = require('child_process').execSync
shell(`sed -i "" "s!oldString!newString!g" ./yourFile.js`)

La "g" después de la final "!" hace que sed reemplace todas las instancias en una línea. Elimínelo y solo se reemplazará la primera aparición por línea.

777
fuente
1

Ampliando la respuesta de @ Sanbor, la forma más eficiente de hacerlo es leer el archivo original como una secuencia, y luego también transmitir cada fragmento en un nuevo archivo, y finalmente reemplazar el archivo original con el nuevo archivo.

async function findAndReplaceFile(regexFindPattern, replaceValue, originalFile) {
  const updatedFile = `${originalFile}.updated`;

  return new Promise((resolve, reject) => {
    const readStream = fs.createReadStream(originalFile, { encoding: 'utf8', autoClose: true });
    const writeStream = fs.createWriteStream(updatedFile, { encoding: 'utf8', autoClose: true });

    // For each chunk, do the find & replace, and write it to the new file stream
    readStream.on('data', (chunk) => {
      chunk = chunk.toString().replace(regexFindPattern, replaceValue);
      writeStream.write(chunk);
    });

    // Once we've finished reading the original file...
    readStream.on('end', () => {
      writeStream.end(); // emits 'finish' event, executes below statement
    });

    // Replace the original file with the updated file
    writeStream.on('finish', async () => {
      try {
        await _renameFile(originalFile, updatedFile);
        resolve();
      } catch (error) {
        reject(`Error: Error renaming ${originalFile} to ${updatedFile} => ${error.message}`);
      }
    });

    readStream.on('error', (error) => reject(`Error: Error reading ${originalFile} => ${error.message}`));
    writeStream.on('error', (error) => reject(`Error: Error writing to ${updatedFile} => ${error.message}`));
  });
}

async function _renameFile(oldPath, newPath) {
  return new Promise((resolve, reject) => {
    fs.rename(oldPath, newPath, (error) => {
      if (error) {
        reject(error);
      } else {
        resolve();
      }
    });
  });
}

// Testing it...
(async () => {
  try {
    await findAndReplaceFile(/"some regex"/g, "someReplaceValue", "someFilePath");
  } catch(error) {
    console.log(error);
  }
})()
alma sudo
fuente
0

En su lugar, usaría una transmisión dúplex. como se documenta aquí las corrientes dúplex doc de nodejs

Un flujo de transformación es un flujo dúplex donde la salida se calcula de alguna manera desde la entrada.

pungggi
fuente
0

<p>Please click in the following {{link}} to verify the account</p>


function renderHTML(templatePath: string, object) {
    const template = fileSystem.readFileSync(path.join(Application.staticDirectory, templatePath + '.html'), 'utf8');
    return template.match(/\{{(.*?)\}}/ig).reduce((acc, binding) => {
        const property = binding.substring(2, binding.length - 2);
        return `${acc}${template.replace(/\{{(.*?)\}}/, object[property])}`;
    }, '');
}
renderHTML(templateName, { link: 'SomeLink' })

seguro que puede mejorar la función de plantilla de lectura para leer como flujo y componer los bytes por línea para que sea más eficiente

Ezzabuzaid
fuente