Замініть рядок у файлі на 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.