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.