Sostituisci una stringa in un file con nodejs


168

Uso l' attività grunt md5 per generare nomi di file MD5. Ora voglio rinominare le fonti nel file HTML con il nuovo nome file nel callback dell'attività. Mi chiedo quale sia il modo più semplice per farlo.


1
Vorrei che ci fosse una combinazione di renamer e di sostituzione nel file, che rinominasse entrambi i file e cercasse / sostituisse anche qualsiasi riferimento per quei file.
Brain2000,

Risposte:


303

Puoi usare regex semplice:

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

Così...

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

Certo, ma devo leggere il file, sostituire il testo e quindi scrivere di nuovo il file, o c'è un modo più semplice, mi dispiace, sono più un ragazzo frontend.
Andreas Köberle,

Forse c'è un modulo nodo per raggiungere questo obiettivo, ma non ne sono consapevole. Aggiunto un esempio completo tra l'altro.
asgoth

4
@Zax: Grazie, sono sorpreso che questo 'bug' possa sopravvivere così a lungo;)
asgoth

1
scusate come so utf-8 supporta molte lingue come: vietnamita, cinese ...
vuhung3990

Se l'aspetto della stringa più volte nel testo sostituirà solo la prima stringa trovata.
eltongonc

80

Dal momento che sostituire non funzionava per me, ho creato un semplice pacchetto npm repl -in-file per sostituire rapidamente il testo in uno o più file. È parzialmente basato sulla risposta di @ asgoth.

Modifica (3 ottobre 2016) : il pacchetto ora supporta promesse e globs e le istruzioni per l'uso sono state aggiornate per riflettere questo.

Modifica (16 marzo 2018) : il pacchetto ha accumulato oltre 100.000 download mensili ora ed è stato esteso con funzionalità aggiuntive e uno strumento CLI.

Installare:

npm install replace-in-file

Modulo richiesto

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

Specifica le opzioni di sostituzione

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

Sostituzione asincrona con promesse:

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

Sostituzione asincrona con callback:

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

Sostituzione sincrona:

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

3
Modulo chiavi in ​​mano eccezionale e facile da usare. L'ho usato con asincrono / wait e un glob su una cartella abbastanza grande ed è stato velocissimo
Matt Fletcher,

Sarà in grado di lavorare con dimensioni di file superiori a 256 Mb poiché ho letto da qualche parte che il limite di stringa nel nodo js è 256 Mb
Alien128

Credo che lo farà, ma ci sono anche lavori in corso per implementare la sostituzione dello streaming per file più grandi.
Adam Reis,

1
bello, ho trovato e usato questo pacchetto (per il suo strumento CLI) prima di leggere questa risposta SO. lo adoro
Russell Chisholm,

38

Forse anche il modulo "sostituisci" ( www.npmjs.org/package/replace ) funzionerebbe per te. Non richiederebbe di leggere e quindi scrivere il file.

Adattato dalla documentazione:

// 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,
});

Sai come filtrare per estensione di file nei percorsi? qualcosa come i percorsi: ['path / to / your / file / *. js'] -> non funziona
Kalamarico

È possibile utilizzare node-glob per espandere i pattern glob in una matrice di percorsi e quindi scorrere su di essi.
RobW

3
Questo è carino, ma è stato abbandonato. Consultare stackoverflow.com/a/31040890/1825390 per un pacchetto gestito se si desidera una soluzione pronta all'uso.
xavdid,

1
C'è anche una versione mantenuta chiamata node-replace ; tuttavia, osservando la base di codice né questo, né sostituire il file in realtà sostituiscono il testo nel file, usano readFile()e writeFile()proprio come la risposta accettata.
c1moore,

26

Puoi anche usare la funzione 'sed' che fa parte di ShellJS ...

 $ npm install [-g] shelljs


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

Visita ShellJs.org per altri esempi.


questa sembra essere la soluzione più pulita :)
Yerken

1
shxti permette di eseguire da script npm, ShellJs.org lo ha raccomandato. github.com/shelljs/shx
Joshua Robinson,

Mi piace anche questo Meglio un oneliner, di npm-module, ma linee di codice
surreali

L'importazione di una dipendenza di terze parti non è la soluzione più pulita.
quattro43

Questo non farà multiline.
amorevole

5

È possibile elaborare il file durante la lettura utilizzando gli stream. È come usare i buffer ma con un'API più comoda.

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');

3
Ma ... cosa succede se il blocco divide la stringa regexpFind? L'intenzione non fallisce allora?
Jaakko Karhu,

Questo è un ottimo punto. Mi chiedo se impostando un valore bufferSizepiù lungo della stringa che stai sostituendo e salvando l'ultimo pezzo e concatenandolo con quello attuale potresti evitare questo problema.
Sanbor,

1
Probabilmente questo frammento dovrebbe anche essere migliorato scrivendo il file modificato direttamente nel filesystem piuttosto che creare una grande variabile poiché il file potrebbe essere più grande della memoria disponibile.
Sanbor,

1

Ho riscontrato problemi durante la sostituzione di un segnaposto piccolo con una stringa di codice di grandi dimensioni.

Stavo facendo:

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

Ho capito che il problema erano gli schemi di sostituzione speciali di JavaScript, descritti qui . Poiché il codice che stavo usando come stringa di sostituzione ne conteneva un po $', stava rovinando l'output.

La mia soluzione era quella di utilizzare l'opzione di sostituzione della funzione, che NON esegue alcuna sostituzione speciale:

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

1

ES2017 / 8 per Nodo 7.6+ con un file di scrittura temporaneo per la sostituzione atomica.

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
}

Nota, solo per file di piccole dimensioni in quanto verranno letti in memoria.


Non è necessario bluebird, utilizzare native Promisee util.promisify .
Francisco Mateo,

1
@FranciscoMateo Vero, ma oltre 1 o 2 funzioni promisifyAll è ancora super utile.
Matt,

1

Su Linux o Mac, keep è semplice e basta usare sed con la shell. Non sono necessarie librerie esterne. Il seguente codice funziona su Linux.

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

La sintassi sed è leggermente diversa su Mac. Non posso provarlo in questo momento, ma credo che devi solo aggiungere una stringa vuota dopo "-i":

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

La "g" dopo la finale "!" fa sed sostituire tutte le istanze su una linea. Rimuoverlo e verrà sostituita solo la prima occorrenza per riga.


1

Espandendo la risposta di @ Sanbor, il modo più efficace per farlo è leggere il file originale come flusso, quindi anche eseguire lo streaming di ogni blocco in un nuovo file e infine sostituire il file originale con il nuovo file.

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

<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' })

sicuramente puoi migliorare la funzione del modello di lettura per leggere come stream e comporre i byte per riga per renderlo più efficiente

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.