web-dev-qa-db-fra.com

Remplacer une chaîne dans un fichier avec nodejs

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

114
Andreas Köberle

Vous pouvez utiliser une expression rationnelle simple:

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);
  });
});
213
asgoth

Comme le remplacement ne fonctionnait pas pour moi, j'ai créé un simple paquet npm replace-in-file pour remplacer rapidement le texte d'un ou de plusieurs fichiers. C'est partiellement 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 maintenant accumulé plus de 100 000 téléchargements par mois et a été étendu à des fonctionnalités supplémentaires ainsi qu'à un outil de ligne de commande.

Installer:

npm install replace-in-file

Requiert un module

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);
}
45
Adam Reis

Peut-être que le module "remplacer" ( www.npmjs.org/package/replace ) conviendrait également. 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,
});
29
Slack Undertow

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.

24
Tony O'Hagan

Vous pouvez traiter le fichier en cours de lecture à l'aide de 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');
5
sanbor

ES2017/8 pour le noeud 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 fichiers de petite taille car ils seront lus en mémoire. 

1
Matt

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 dû aux modèles de remplacement spéciaux de JavaScript, décrits ici . Étant donné que le code que j’utilisais comme chaîne de remplacement contenait un $, il perturbait la sortie.

Ma solution a été d'utiliser l'option de remplacement de fonction, qui ne fait aucun remplacement spécial:

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

Sous Linux ou Mac, keep est simple et il suffit d’utiliser 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 l'instant, mais je crois qu'il 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 occurrences d'une ligne. Supprimez-le et seule la première occurrence par ligne sera remplacée.

0
777

En développant la réponse de @ Sanbor, le moyen le plus efficace consiste à lire le fichier d'origine en tant que flux, à transférer ensuite chaque bloc dans un nouveau fichier, puis à remplacer le fichier d'origine par le nouveau.

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

Je voudrais utiliser un flux duplex à la place. comme documenté ici streamj doc doc de nodejs

Un flux de transformation est un flux duplex dans lequel la sortie est calculée à quelque distance de l'entrée.

0
pungggi