Заменить строку в файле на nodejs

168

Я использую задачу 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);
  });
});
asgoth
источник
Конечно, но нужно ли мне читать файл, заменять текст, а затем снова записывать файл, или есть более простой способ, извините, я скорее из внешнего интерфейса.
Андреас Кёберле
Может быть, есть модуль узла, чтобы достигнуть этого, но я не знаю об этом. Добавил полный пример между прочим.
asgoth
4
@Zax: Спасибо, я удивлен, что этот «жук» мог так долго выжить;)
asgoth
1
извините, поскольку я знаю, что utf-8 поддерживает много языков, таких как: вьетнамский, китайский ...
vuhung3990
Если ваша строка появляется в тексте несколько раз, она заменит только первую найденную строку.
eltongonc
80

Поскольку замена не работала для меня, я создал простую замену пакета в файле 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) прежде, чем я когда-либо прочитал этот SO-ответ. люблю это
Рассел Чизхолм
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,
});
Slack Undertow
источник
Знаете ли вы, как можно фильтровать по расширению файла в путях? что-то вроде пути: ['path / to / your / file / *. js'] -> это не работает
Kalamarico
Вы можете использовать node-glob для расширения шаблонов glob до массива путей, а затем перебирать их.
RobW
3
Это хорошо, но было заброшено. См. Stackoverflow.com/a/31040890/1825390 для поддерживаемого пакета, если вы хотите готовое решение.
xavdid
1
Существует также поддерживаемая версия под названием node-replace ; однако, глядя на кодовую базу, ни this, ни replace-in-file фактически не заменяют текст в файле, они используют readFile()и так writeFile()же, как принятый ответ.
c1moore
26

Вы также можете использовать функцию sed, которая является частью ShellJS ...

 $ npm install [-g] shelljs


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

Посетите ShellJs.org для большего количества примеров.

Тони О'Хаган
источник
это, кажется, самое чистое решение :)
Еркен
1
shxпозволяет вам запускать из сценариев npm, ShellJs.org рекомендовал это. github.com/shelljs/shx
Джошуа Робинсон
Мне это тоже нравится Лучше, чем neliner, чем npm-модуль, но обзорные строки кода ^^
suther
Импорт сторонней зависимости - не самое чистое решение.
4:43
Это не будет делать мультилинии.
Чови
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');
sanbor
источник
3
Но ... что если кусок разбивает строку regexpFind? Разве намерение не проваливается тогда?
Яакко Карху
Это очень хороший момент. Интересно, bufferSizeможно ли избежать этой проблемы, установив длину, превышающую строку, которую вы заменяете, и сохранив последний блок и конкатенировав его с текущим.
Санбор
1
Вероятно, этот фрагмент также следует улучшить, записав измененный файл непосредственно в файловую систему, а не создав большую переменную, поскольку файл может быть больше доступной памяти.
Санбор
1

Я столкнулся с проблемами при замене небольшого заполнителя большой строкой кода.

Я делал:

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

Я понял, что проблема заключается в специальных шаблонах замены JavaScript, описанных здесь . Поскольку код, который я использовал в качестве замещающей строки, содержал в себе некоторое количество $, это приводило к неправильному выводу.

Мое решение состояло в том, чтобы использовать функцию замены функции, которая НЕ делает никакой специальной замены:

var replaced = original.replace('PLACEHOLDER', function() {
    return largeStringVar;
});
anderspitman
источник
1

ES2017 / 8 для узла 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
}

Обратите внимание, только для небольших файлов, так как они будут прочитаны в память.

Matt
источник
Нет необходимости 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`)

"Г" после финала "!" заставляет sed заменить все экземпляры в строке. Удалите его, и будет заменено только первое вхождение в строке.

777
источник
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 дуплексные потоки

Поток преобразования - это дуплексный поток, в котором выходные данные вычисляются некоторым образом из входных данных.

pungggi
источник
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' })

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

Ezzabuzaid
источник