Remplacer une chaîne dans un fichier par nodejs

168

J'utilise la tâche grunt md5 pour générer des noms de fichiers MD5. Maintenant, je veux renommer les sources dans le fichier HTML avec le nouveau nom de fichier dans le rappel de la tâche. Je me demande quelle est la manière la plus simple de procéder.

Andreas Köberle
la source
1
J'aurais aimé qu'il y ait une combinaison de renommage et de remplacement dans le fichier, qui renomme à la fois les fichiers et recherche / remplace toute référence pour ces fichiers.
Brain2000

Réponses:

303

Vous pouvez utiliser une simple regex:

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

Alors...

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
la source
Bien sûr, mais dois-je lire le fichier, remplacer le texte, puis réécrire le fichier, ou y a-t-il un moyen plus simple, désolé, je suis plus un gars du frontend.
Andreas Köberle
Il existe peut-être un module de nœud pour y parvenir, mais je n'en suis pas conscient. Ajout d'un exemple complet btw.
asgoth
4
@Zax: Merci, je suis surpris que ce `` bug '' puisse survivre si longtemps;)
asgoth
1
désolé car je sais que utf-8 prend en charge de nombreuses langues telles que: vietnamien, chinois ...
vuhung3990
Si votre chaîne apparaît plusieurs fois dans votre texte, elle ne remplacera que la première chaîne trouvée.
eltongonc
80

Comme replace ne fonctionnait pas pour moi, j'ai créé un simple package npm replace-in-file pour remplacer rapidement le texte dans un ou plusieurs fichiers. C'est en partie basé sur la réponse de @ asgoth.

Edit (3 octobre 2016) : Le package prend désormais en charge les promesses et les globs, et les instructions d'utilisation ont été mises à jour pour refléter cela.

Edit (16 mars 2018) : Le package a accumulé plus de 100000 téléchargements mensuels maintenant et a été étendu avec des fonctionnalités supplémentaires ainsi qu'un outil CLI.

Installer:

npm install replace-in-file

Module requis

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

Spécifier les options de remplacement

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',
};

Remplacement asynchrone avec promesses:

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

Remplacement asynchrone avec rappel:

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

Remplacement synchrone:

try {
  let changedFiles = replace.sync(options);
  console.log('Modified files:', changedFiles.join(', '));
}
catch (error) {
  console.error('Error occurred:', error);
}
Adam Reis
la source
3
Module clé en main génial et facile à utiliser. Je l'ai utilisé avec async / await et un glob sur un dossier assez volumineux et c'était rapide comme l'éclair
Matt Fletcher
Sera-t-il capable de fonctionner avec des tailles de fichier supérieures à 256 Mo puisque j'ai lu quelque part que la limite de chaîne dans le nœud js est de 256 Mo
Alien128
Je pense que ce sera le cas, mais il y a aussi un travail en cours pour implémenter le remplacement du streaming pour les fichiers plus volumineux.
Adam Reis
1
sympa, j'ai trouvé et utilisé ce package (pour son outil CLI) avant de lire cette réponse SO. love it
Russell Chisholm
38

Peut-être que le module «replace» ( www.npmjs.org/package/replace ) fonctionnerait également pour vous. Cela ne vous obligerait pas à lire puis à écrire le fichier.

Adapté de la documentation:

// 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
la source
Savez-vous comment filtrer par extension de fichier dans les chemins? quelque chose comme des chemins: ['chemin / vers / votre / fichier / *. js'] -> cela ne fonctionne pas
Kalamarico
Vous pouvez utiliser node-glob pour étendre les modèles glob à un tableau de chemins, puis les parcourir.
RobW
3
C'est bien, mais a été abandonné. Voir stackoverflow.com/a/31040890/1825390 pour un package maintenu si vous voulez une solution prête à l'emploi.
xavdid
1
Il existe également une version maintenue appelée node-replace ; cependant, en regardant la base de code ni this ni replace-in-file ne remplacent réellement le texte dans le fichier, ils utilisent readFile()et writeFile()tout comme la réponse acceptée.
c1moore
26

Vous pouvez également utiliser la fonction 'sed' qui fait partie de ShellJS ...

 $ npm install [-g] shelljs


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

Visitez ShellJs.org pour plus d'exemples.

Tony O'Hagan
la source
cela semble être la solution la plus propre :)
Yerken
1
shxvous permet d'exécuter à partir de scripts npm, ShellJs.org l'a recommandé. github.com/shelljs/shx
Joshua Robinson
J'aime ça aussi. Mieux vaut un oneliner, que npm-module, mais surveral lines of code ^^
suther
L'importation d'une dépendance tierce n'est pas la solution la plus propre.
four43
Cela ne fera pas de multilignes.
chovy
5

Vous pouvez traiter le fichier pendant sa lecture en utilisant des flux. C'est comme utiliser des tampons mais avec une API plus pratique.

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
la source
3
Mais ... et si le bloc divise la chaîne regexpFind? L'intention n'échoue-t-elle pas alors?
Jaakko Karhu
C'est un très bon point. Je me demande si en définissant une bufferSizechaîne plus longue que la chaîne que vous remplacez et en sauvegardant le dernier morceau et en concaténant avec la chaîne actuelle, vous pourriez éviter ce problème.
sanbor
1
Cet extrait de code devrait probablement également être amélioré en écrivant le fichier modifié directement dans le système de fichiers plutôt qu'en créant une grande variable car le fichier peut être plus volumineux que la mémoire disponible.
sanbor
1

J'ai rencontré des problèmes lors du remplacement d'un petit espace réservé par une grande chaîne de code.

Je faisais:

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

J'ai compris que le problème était lié aux modèles de remplacement spéciaux de JavaScript, décrits ici . Puisque le code que j'utilisais comme chaîne de remplacement en contenait $, cela gâchait la sortie.

Ma solution était d'utiliser l'option de remplacement de fonction, qui ne fait PAS de remplacement spécial:

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

ES2017 / 8 pour Node 7.6+ avec un fichier d'écriture temporaire pour le remplacement atomique.

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
}

Remarque, uniquement pour les petits fichiers car ils seront lus en mémoire.

Mat
la source
Pas besoin bluebird, utilisez native Promiseet util.promisify .
Francisco Mateo
1
@FranciscoMateo Vrai, mais au-delà de 1 ou 2 fonctions promisifyAll est toujours super utile.
Matt
1

Sur Linux ou Mac, keep est simple et utilisez simplement sed avec le shell. Aucune bibliothèque externe requise. Le code suivant fonctionne sous Linux.

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

La syntaxe sed est un peu différente sur Mac. Je ne peux pas le tester pour le moment, mais je pense qu'il vous suffit d'ajouter une chaîne vide après le "-i":

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

Le "g" après la finale "!" fait que sed remplace toutes les instances sur une ligne. Supprimez-le, et seule la première occurrence par ligne sera remplacée.

777
la source
1

En développant la réponse de @ Sanbor, le moyen le plus efficace de le faire est de lire le fichier d'origine en tant que flux, puis de diffuser également chaque morceau dans un nouveau fichier, puis de remplacer enfin le fichier d'origine par le nouveau fichier.

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);
  }
})()
sudo soul
la source
0

J'utiliserais plutôt un flux duplex. comme documenté ici nodejs doc duplex streams

Un flux de transformation est un flux duplex où la sortie est calculée d'une manière ou d'une autre à partir de l'entrée.

Pungggi
la source
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' })

à coup sûr, vous pouvez améliorer la fonction de modèle de lecture pour lire en tant que flux et composer les octets par ligne pour la rendre plus efficace

Ezzabuzaid
la source