Ersetzen Sie eine Zeichenfolge in einer Datei durch nodejs

166

Ich benutze die md5-Grunzaufgabe , um MD5-Dateinamen zu generieren. Jetzt möchte ich die Quellen in der HTML-Datei mit dem neuen Dateinamen im Rückruf der Aufgabe umbenennen. Ich frage mich, was der einfachste Weg ist, dies zu tun.

Andreas Köberle
quelle
1
Ich wünschte, es gäbe eine Kombination aus Umbenennung und Ersetzen in der Datei, die sowohl die Dateien umbenennt als auch alle Referenzen für diese Dateien sucht / ersetzt.
Brain2000

Antworten:

302

Sie könnten einen einfachen regulären Ausdruck verwenden:

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

So...

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
quelle
Sicher, aber muss ich die Datei lesen, den Text ersetzen und dann die Datei erneut schreiben, oder gibt es einen einfacheren Weg, sorry, ich bin eher ein Frontend-Typ.
Andreas Köberle
Vielleicht gibt es ein Knotenmodul, um dies zu erreichen, aber ich bin mir dessen nicht bewusst. Übrigens wurde ein vollständiges Beispiel hinzugefügt.
Asgoth
4
@ Zax: Danke, ich bin überrascht, dass dieser 'Bug' so lange überleben könnte;)
Asgoth
1
Entschuldigung, wie ich weiß, unterstützt utf-8 viele Sprachen wie: vietnamesisch, chinesisch ...
vuhung3990
Wenn Ihre Zeichenfolge in Ihrem Text mehrmals vorkommt, wird nur die erste gefundene Zeichenfolge ersetzt.
eltongonc
79

Da das Ersetzen bei mir nicht funktioniert hat, habe ich ein einfaches npm-Paket zum Ersetzen in der Datei erstellt , um Text in einer oder mehreren Dateien schnell zu ersetzen. Es basiert teilweise auf der Antwort von @ asgoth.

Bearbeiten (3. Oktober 2016) : Das Paket unterstützt jetzt Versprechen und Globs, und die Gebrauchsanweisungen wurden aktualisiert, um dies widerzuspiegeln.

Bearbeiten (16. März 2018) : Das Paket hat jetzt über 100.000 monatliche Downloads gesammelt und wurde um zusätzliche Funktionen sowie ein CLI-Tool erweitert.

Installieren:

npm install replace-in-file

Modul erforderlich

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

Geben Sie Ersatzoptionen an

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

Asynchroner Ersatz durch Versprechen:

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

Asynchroner Ersatz durch Rückruf:

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

Synchroner Austausch:

try {
  let changedFiles = replace.sync(options);
  console.log('Modified files:', changedFiles.join(', '));
}
catch (error) {
  console.error('Error occurred:', error);
}
Adam Reis
quelle
3
Tolles und einfach zu bedienendes schlüsselfertiges Modul. Benutzte es mit async / await und einem Glob über einem ziemlich großen Ordner und es war blitzschnell
Matt Fletcher
Wird es in der Lage sein, mit Dateigrößen größer als 256 MB zu arbeiten, da ich irgendwo gelesen habe, dass das String-Limit in Knoten js 256 MB
beträgt
Ich glaube, dass dies der Fall sein wird, aber es wird auch daran gearbeitet, den Streaming-Ersatz für größere Dateien zu implementieren.
Adam Reis
1
Schön, ich habe dieses Paket (für sein CLI-Tool) gefunden und verwendet, bevor ich diese SO-Antwort gelesen habe. Ich liebe es
Russell Chisholm
38

Vielleicht würde das "Ersetzen" -Modul ( www.npmjs.org/package/replace ) auch für Sie funktionieren. Sie müssten die Datei nicht lesen und dann schreiben.

Aus der Dokumentation übernommen:

// 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
quelle
Wissen Sie, wie in Pfaden nach Dateierweiterung gefiltert werden kann? so etwas wie Pfade: ['Pfad / zu / Ihre / Datei / *. js'] -> es funktioniert nicht
Kalamarico
Sie können Node-Glob verwenden, um Glob-Muster auf ein Array von Pfaden zu erweitern und diese dann zu durchlaufen.
RobW
3
Das ist schön, wurde aber aufgegeben. Unter stackoverflow.com/a/31040890/1825390 finden Sie ein gepflegtes Paket, wenn Sie eine sofort einsatzbereite Lösung wünschen.
Xavdid
1
Es gibt auch eine gepflegte Version namens Node-Replace . Betrachtet man jedoch die Codebasis, so ersetzen weder diese noch das Ersetzen in der Datei tatsächlich den Text in der Datei. Sie verwenden readFile()und writeFile()akzeptieren genau die akzeptierte Antwort.
c1moore
26

Sie können auch die 'sed'-Funktion verwenden, die Teil von ShellJS ist ...

 $ npm install [-g] shelljs


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

Besuchen Sie ShellJs.org für weitere Beispiele.

Tony O'Hagan
quelle
Dies scheint die sauberste Lösung zu sein :)
Yerken
1
shxMit ShellJs.org können Sie npm-Skripte ausführen. github.com/shelljs/shx
Joshua Robinson
Ich mag das auch. Besser ein Oneliner als ein npm-Modul, aber mehrere Codezeilen ^^
suther
Das Importieren einer Abhängigkeit von Drittanbietern ist nicht die sauberste Lösung.
Four43
Dies macht keine Multilines.
Chovy
5

Sie können die Datei beim Lesen mithilfe von Streams verarbeiten. Es ist wie bei der Verwendung von Puffern, jedoch mit einer bequemeren API.

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
quelle
3
Aber ... was ist, wenn der Block den regexpFind-String teilt? Scheitert die Absicht dann nicht?
Jaakko Karhu
Das ist ein sehr guter Punkt. Ich frage mich, ob bufferSizeSie dieses Problem vermeiden könnten, indem Sie eine längere Zeichenfolge als die Zeichenfolge festlegen, die Sie ersetzen, den letzten Block speichern und mit der aktuellen verketten.
Sanbor
1
Wahrscheinlich sollte dieses Snippet auch verbessert werden, indem die geänderte Datei direkt in das Dateisystem geschrieben wird, anstatt eine große Variable zu erstellen, da die Datei möglicherweise größer als der verfügbare Speicher ist.
Sanbor
1

Beim Ersetzen eines kleinen Platzhalters durch eine große Codefolge sind Probleme aufgetreten.

Ich habe getan:

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

Ich fand heraus, dass das Problem die hier beschriebenen speziellen Ersatzmuster von JavaScript waren . Da der Code, den ich als Ersetzungszeichenfolge verwendete, einige $enthielt, hat er die Ausgabe durcheinander gebracht.

Meine Lösung bestand darin, die Option zum Ersetzen von Funktionen zu verwenden, die KEINEN speziellen Austausch durchführt:

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

ES2017 / 8 für Knoten 7.6+ mit einer temporären Schreibdatei für den atomaren Ersatz.

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
}

Beachten Sie, nur für kleinere Dateien, da diese in den Speicher eingelesen werden.

Matt
quelle
Keine Notwendigkeit für bluebird, verwenden Sie native Promiseund util.promisify .
Francisco Mateo
1
@FranciscoMateo Stimmt, aber über 1 oder 2 Funktionen hinaus ist promisifyAll immer noch super nützlich.
Matt
1

Unter Linux oder Mac ist keep einfach und verwendet sed nur mit der Shell. Keine externen Bibliotheken erforderlich. Der folgende Code funktioniert unter Linux.

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

Die sed-Syntax ist auf dem Mac etwas anders. Ich kann es momentan nicht testen, aber ich glaube, Sie müssen nur eine leere Zeichenfolge nach dem "-i" hinzufügen:

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

Das "g" nach dem Finale "!" Lässt sed alle Instanzen in einer Zeile ersetzen. Entfernen Sie es, und nur das erste Vorkommen pro Zeile wird ersetzt.

777
quelle
1

Wenn Sie die Antwort von @ Sanbor erweitern, können Sie dies am effizientesten tun, indem Sie die Originaldatei als Stream lesen und dann jeden Block in eine neue Datei streamen und zuletzt die Originaldatei durch die neue Datei ersetzen.

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 Seele
quelle
0

Ich würde stattdessen einen Duplex-Stream verwenden. wie hier dokumentiert nodejs doc Duplex-Streams

Ein Transformationsstrom ist ein Duplexstrom, bei dem die Ausgabe auf irgendeine Weise aus der Eingabe berechnet wird.

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

Natürlich können Sie die Lesevorlagenfunktion verbessern, um sie als Stream zu lesen, und die Bytes zeilenweise zusammensetzen, um sie effizienter zu gestalten

Ezzabuzaid
quelle