Замініть рядок у файлі на nodejs


167

Я використовую md5 grunt завдання для створення файлів MD5. Тепер я хочу перейменувати джерела у файлі HTML з новим іменем файлу у зворотному дзвінку завдання. Цікаво, який найпростіший спосіб зробити це.


1
Я хотів би, щоб було перейменування та комбінація файлів, що перейменували б файли, а також шукати / замінювати будь-які посилання на ці файли.
Brain2000

Відповіді:


303

Ви можете використовувати простий регулярний вираз:

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

Так...

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

Звичайно, але чи потрібно мені прочитати файл, який замінює текст, а потім знову записати файл, чи є простіший спосіб, вибачте, я більше фронтмен.
Andreas Köberle

Можливо, є модуль вузла для досягнення цього, але я цього не знаю. Додано повний приклад btw.
asgoth

4
@Zax: Дякую, я здивований, що ця помилка могла витримати так довго;)
1313

1
вибачте, як я знаю, що utf-8 підтримує багато мов, таких як: в'єтнамська, китайська ...
vuhung3990,

Якщо ваш рядок відображається кілька разів у вашому тексті, він замінить лише першу, яку він знайде.
eltongonc

79

Оскільки заміни не працює для мене, я створив простий пакет NPM заміну на файл , щоб швидко замінити текст в одному або декількох файлах. Частково це ґрунтується на відповіді @ asgoth.

Редагувати (3 жовтня 2016 р.) : Пакет тепер підтримує обіцянки та глобус, і інструкції з використання були оновлені, щоб відобразити це.

Редагувати (16 березня 2018 р.) . Зараз пакет набрав понад 100 тис. Завантажень щомісяця та був розширений додатковими функціями, а також інструментом CLI.

Встановити:

npm install replace-in-file

Потрібен модуль

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

Вкажіть варіанти заміни

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

Асинхронна заміна обіцянками:

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

Асинхронна заміна з зворотним зв'язком:

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

Синхронна заміна:

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

3
Чудовий і простий у користуванні модуль під ключ. Використовували його з async / await та глобусом над досить великою папкою, і це було блискавично
Метт Флетчер

Чи вдасться працювати з розмірами файлів більше 256 Мб, оскільки я десь читав, що обмеження рядка у вузлі js становить 256 Мб
Alien128

Я вважаю, що це буде, але зараз триває робота з впровадження потокової заміни на більші файли.
Адам Рейс

1
приємно, я знайшов та використав цей пакет (для його інструменту CLI), перш ніж я коли-небудь прочитав цю відповідь. люблю це
Рассел Чизгольм

38

Можливо, модуль "Замінити" ( www.npmjs.org/package/replace ) також буде працювати для вас. Це не зажадає, щоб ви прочитали та потім записали файл.

Адаптовано з документації:

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

Чи знаєте ви, як можна фільтрувати за розширенням файлів у контурах? щось на зразок шляхів: ['шлях / до / ваш / файл / *. js'] -> це не працює
Kalamarico

Ви можете використовувати node-glob для розширення шаблонів глобусу до масиву шляхів, а потім перебирати їх.
RobW

3
Це добре, але відмовились. Дивіться stackoverflow.com/a/31040890/1825390 щодо пакету, який підтримується, якщо ви хочете вийти з коробки.
xavdid

1
Існує також підтримувана версія під назвою node-substitute ; однак, дивлячись на базу коду, ні це, ні файл, що замінюється, фактично не замінюють текст у файлі, вони використовують readFile()і так writeFile()само, як прийняту відповідь.
c1moore

26

Ви також можете використовувати функцію "sed", що є частиною ShellJS ...

 $ npm install [-g] shelljs


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

Відвідайте ShellJs.org для отримання додаткових прикладів.


це здається найчистішим рішенням :)
Yerken

1
shxдозволяє запускати сценарії npm, рекомендував ShellJs.org. github.com/shelljs/shx
Джошуа Робінсон

Мені це теж подобається. Краще онлінер, ніж npm-модуль, але оглядові рядки коду ^^
суть

Імпорт сторонньої залежності не є найчистішим рішенням.
чотири43

Мультилінії це не робитиме.
chovy

5

Ви можете обробити файл під час читання, використовуючи потоки. Це так само, як використовувати буфери, але з більш зручним 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
Але ... що робити, якщо фрагмент розбиває рядок regexpFind? Тоді намір не провалюється?
Jaakko Karhu

Це дуже вдалий момент. Цікаво, якщо встановити bufferSizeдовше, ніж рядок, який ви замінюєте та збережете останній фрагмент та з'єднавшись із поточним, ви могли б уникнути цієї проблеми.
санбор

1
Можливо, цей фрагмент слід також покращити, записавши модифікований файл безпосередньо у файлову систему, а не створюючи велику змінну, оскільки файл може бути більшим, ніж наявна пам'ять.
sanbor

1

У мене виникли проблеми під час заміни невеликого заповнювача на великий рядок коду.

Я робив:

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

Я зрозумів, що проблема полягає в особливих моделях заміни JavaScript, описаних тут . Оскільки код, який я використовував як рядок, що заміняв, мав $у ньому деякі , він псував вихід.

Моє рішення полягало у використанні опції заміни функції, яка НЕ ​​робить будь-якої спеціальної заміни:

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

1

ES2017 / 8 для Node 7.6+ з тимчасовим файлом запису для заміни атомів.

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
}

Зауважте, лише для невеликих файлів, оскільки вони будуть читатися в пам'яті.


Не потрібно bluebird, користуйтеся рідною Promiseта util.promisify .
Франциско Матео

1
@FranciscoMateo Правда, але понад 1 або 2 функції promisifyAll все ще надзвичайно корисно.
Метт

1

На Linux або Mac зберігати просто і просто використовувати sed з оболонкою. Не потрібні зовнішні бібліотеки. Наступний код працює в Linux.

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

Синтаксис sed дещо відрізняється на Mac. Я не можу перевірити його зараз, але я вважаю, що вам просто потрібно додати порожній рядок після "-i":

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

"G" після фіналу "!" змушує sed замінювати всі екземпляри лінії. Видаліть його, і буде замінено лише перше виникнення в рядку.


1

Розгортаючись на відповідь @ Sanbor, найефективніший спосіб зробити це - прочитати оригінальний файл як потік, а потім також передати кожен фрагмент у новий файл, а потім остаточно замінити оригінальний файл на новий.

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

Я б замість цього використовував дуплексний потік. як задокументовані тут nodejs doc дуплексні потоки

Трансформаційний потік - це дуплексний потік, де вихід обчислюється якимось чином із входу.


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

Ви точно можете вдосконалити функцію шаблону читання, щоб читати як потік, і складати байти за рядком, щоб зробити його більш ефективним

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.