Zastąp ciąg w pliku nodejs


167

Używam zadania md5 grunt do generowania nazw plików MD5. Teraz chcę zmienić nazwy źródeł w pliku HTML na nową nazwę pliku w wywołaniu zwrotnym zadania. Zastanawiam się, jak to zrobić najłatwiej.


1
Chciałbym, żeby istniała kombinacja zmiany nazwy i zamiany w pliku, która zarówno zmieniłaby nazwy plików, jak i wyszukałaby / zastąpiła wszelkie odniesienia do tych plików.
— Brain2000

Odpowiedzi:


303

Możesz użyć prostego wyrażenia regularnego:

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

Więc...

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

Jasne, ale czy muszę przeczytać plik, zamienić tekst, a następnie napisać plik ponownie, czy jest łatwiejszy sposób, przepraszam, jestem bardziej facetem od frontendu.
— Andreas Köberle

Może istnieje moduł węzłowy, który to osiągnie, ale nie jestem tego świadomy. Przy okazji dodano pełny przykład.
— asgoth

4
@Zax: Dzięki, jestem zaskoczony, że ten 'błąd' mógł przetrwać tak długo;)
— asgoth

1
sorry as i know utf-8 obsługuje wiele języków, takich jak: vietnamese, chinese ...
— vuhung3990

Jeśli twój ciąg pojawia się wiele razy w twoim tekście, zastąpi tylko pierwszy znaleziony ciąg.
— eltongonc

79

Ponieważ funkcja zastępowania nie działała, utworzyłem prosty pakiet npm do zamiany w pliku, aby szybko zastąpić tekst w jednym lub wielu plikach. Jest częściowo oparty na odpowiedzi @ asgoth.

Edycja (3 października 2016 r.) : Pakiet obsługuje teraz obietnice i elementy globalne, a instrukcje użytkowania zostały zaktualizowane, aby to odzwierciedlić.

Edycja (16 marca 2018 r.) : Pakiet zgromadził teraz ponad 100 tys. Miesięcznych pobrań i został rozszerzony o dodatkowe funkcje, a także narzędzie CLI.

Zainstalować:

npm install replace-in-file

Wymagaj modułu

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

Określ opcje wymiany

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

Asynchroniczna wymiana z obietnicami:

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

Asynchroniczna zamiana z wywołaniem zwrotnym:

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

Synchroniczna wymiana:

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

3
Świetny i łatwy w obsłudze moduł pod klucz. Użyłem go z async / await i globem na dość dużym folderze i był błyskawiczny
— Matt Fletcher

Czy będzie w stanie pracować z rozmiarami plików większymi niż 256 Mb, ponieważ gdzieś przeczytałem, że limit ciągów w węźle js wynosi 256 MB
— Alien128

Wierzę, że tak, ale trwają również prace nad wdrożeniem zastępowania przesyłania strumieniowego dla większych plików.
— Adam Reis,

1
fajnie, znalazłem i użyłem tego pakietu (jako narzędzia CLI), zanim przeczytałem tę odpowiedź. uwielbiam to
— Russell Chisholm

38

Być może moduł „replace” ( www.npmjs.org/package/replace ) również zadziała. Nie wymagałoby to czytania, a następnie zapisywania pliku.

Na podstawie dokumentacji:

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

Czy wiesz, jak można filtrować według rozszerzenia pliku w ścieżkach? coś takiego jak ścieżki: ['ścieżka / do / twojego / pliku / *. js'] -> to nie działa
— Kalamarico

Możesz użyć node-glob, aby rozszerzyć wzorce glob do tablicy ścieżek, a następnie iterować po nich.
— RobW

3
To miłe, ale zostało porzucone. Zobacz stackoverflow.com/a/31040890/1825390, aby uzyskać pakiet, który jest obsługiwany, jeśli chcesz mieć gotowe rozwiązanie.
— xavdid

1
Istnieje również utrzymywana wersja zwana zastępowaniem węzłów ; Jednak patrząc na podstawę kodu, ani to, ani zamiana w pliku, nie zastępują tekstu w pliku, używają readFile()i writeFile()tak jak zaakceptowana odpowiedź.
— c1moore

26

Możesz także użyć funkcji 'sed', która jest częścią ShellJS ...

 $ npm install [-g] shelljs


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

Wizyta ShellJs.org więcej przykładów.


to wydaje się być najczystszym rozwiązaniem :)
— Yerken

1
shxpozwala na uruchamianie ze skryptów npm, zaleca ShellJs.org. github.com/shelljs/shx
— Joshua Robinson

Ja też to lubię. Lepszy oneliner niż moduł npm, ale kilka wierszy kodu ^^
— suther

Importowanie zależności innej firmy nie jest najczystszym rozwiązaniem.
— cztery43

To nie będzie działać na wielu liniach.
— chovy

5

Możesz przetwarzać plik podczas odczytu za pomocą strumieni. To tak, jak używanie buforów, ale z wygodniejszym 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');

3
Ale ... co się stanie, jeśli fragment podzieli ciąg wyrażenia regularnego Znajdź? Czy wtedy zamiar nie zawodzi?
— Jaakko Karhu

To bardzo dobra uwaga. Zastanawiam się, czy ustawiając bufferSizedłuższy niż ciąg, który zastępujesz, zapisując ostatni fragment i łącząc z bieżącym, możesz uniknąć tego problemu.
— sanbor

1
Prawdopodobnie ten fragment powinien również zostać ulepszony poprzez zapisanie zmodyfikowanego pliku bezpośrednio do systemu plików zamiast tworzenia dużej zmiennej, ponieważ plik może być większy niż dostępna pamięć.
— sanbor

1

Napotkałem problemy podczas zastępowania małego symbolu zastępczego dużym ciągiem kodu.

Ja robiłem:

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

Odkryłem, że problem dotyczy specjalnych wzorców zastępowania JavaScript, opisanych tutaj . Ponieważ kod, którego użyłem jako zastępujący ciąg, zawierał trochę $w sobie, zakłócał wynik.

Moim rozwiązaniem było użycie opcji zamiany funkcji, która NIE WYKONUJE żadnej specjalnej zamiany:

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

1

ES2017 / 8 dla węzła 7.6+ z tymczasowym plikiem zapisu do zastąpienia atomowego.

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
}

Uwaga, tylko dla małych plików, ponieważ zostaną one odczytane do pamięci.


Nie ma potrzeby bluebird, użyj natywnego Promisei util.promisify .
— Francisco Mateo

1
@FranciscoMateo True, ale poza 1 lub 2 funkcjami promisifyAll jest nadal bardzo przydatne.
— Matt

1

W systemie Linux lub Mac Keep jest prosty i po prostu używaj seda z powłoką. Nie są wymagane żadne biblioteki zewnętrzne. Poniższy kod działa w systemie Linux.

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

Składnia seda jest trochę inna na Macu. Nie mogę tego teraz przetestować, ale wydaje mi się, że wystarczy dodać pusty ciąg po „-i”:

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

Litera „g” po ostatnim „!” sprawia, że ​​sed zastępuje wszystkie instancje w linii. Usuń go, a tylko pierwsze wystąpienie w wierszu zostanie zastąpione.


1

Rozwijając odpowiedź @ Sanbor, najskuteczniejszym sposobem na to jest odczytanie oryginalnego pliku jako strumienia, a następnie strumieniowanie każdej porcji do nowego pliku, a następnie zastąpienie oryginalnego pliku nowym plikiem.

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

Zamiast tego użyłbym strumienia dupleksowego. jak udokumentowano tutaj nodejs doc dupleksowe strumienie

Strumień Transform to strumień Duplex, w którym dane wyjściowe są w jakiś sposób obliczane na podstawie danych wejściowych.


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

z pewnością możesz ulepszyć funkcję szablonu odczytu, aby czytać jako strumień i komponować bajty po linii, aby była bardziej wydajna

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.