用nodejs替换文件中的字符串


166

我使用md5 grunt任务生成MD5文件名。现在,我想使用任务回调中的新文件名重命名HTML文件中的源。我想知道什么是最简单的方法。


1
我希望有一个重命名器和在文件中替换的组合,既可以重命名文件,也可以搜索/替换这些文件的任何引用。
Brain2000

Answers:


302

您可以使用简单的正则表达式:

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

可以,但是我必须先阅读文件替换文本,然后再次写入文件,还是有更简单的方法,对不起,我更像是一个前端人员。
AndreasKöberle'13

也许有一个节点模块可以实现这一点,但我不知道。添加了一个完整的示例顺便说一句。
asgoth

4
@Zax:谢谢,我很惊讶这个“ bug”能生存这么长时间;)
13年

1
抱歉,我知道utf-8支持许多语言,例如:
越南语

如果您的字符串在文本中出现多次,它将仅替换找到的第一个字符串。
eltongonc

79

由于replace对我不起作用,因此我创建了一个简单的npm包replace-in-file来快速替换一个或多个文件中的文本。它部分基于@asgoth的答案。

编辑(2016年10月3日):该软件包现在支持Promise和Glob,并且使用说明已更新以反映这一点。

编辑(2018年3月16日):该软件包现已累积每月超过10万次下载,并已扩展了其他功能和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
方便易用的交钥匙模块。与异步/的await并在相当大的文件夹中的水珠使用它,它是快如闪电
马特·弗莱彻

它会能够处理大于256 Mb的文件,因为我在某处读取到节点js中的字符串限制为256 Mb
Alien128 '19

我相信会的,但是正在进行针对较大文件的流替换的工作。
亚当·里斯

1
很好,在我读完SO答案之前,我找到并使用了此软件包(用于其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,
});

您知道如何在路径中按文件扩展名过滤吗?类似路径:['path / to / your / file / *。js']->无效
Kalamarico

您可以使用node-glob将glob模式扩展到路径数组,然后对其进行迭代。
RobW

3
这很好,但已被放弃。如果您需要开箱即用的解决方案,请参见stackoverflow.com/a/31040890/1825390以获取维护的软件包。
xavdid

1
还有一个称为node-replace的维护版本;但是,从代码库的角度来看,无论是此文件还是替换文件,实际上都不会替换文件中的文本,它们使用的方式readFile()writeFile()接受的答案相同。
c1moore

26

您还可以使用ShellJS中的“ sed”功能...

 $ npm install [-g] shelljs


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

访问ShellJs.org了解更多示例。


这似乎是最干净的解决方案:)
Yerken '16

1
shxShellJs.org建议您使用npm脚本运行它。github.com/shelljs/shx
Joshua Robinson

我也喜欢 比npm-module更好的oneliner,但代码行^^的其余行
suther

导入第三方依赖项不是最干净的解决方案。
4: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比您要替换的字符串长的字符串,并保存最后一个块并与当前块连接,您是否可以避免该问题。
桑博'18

1
可能还应通过将修改后的文件直接写入文件系统而不是创建一个大变量来改善此片段,因为该文件可能大于可用内存。
桑博尔'18

1

用大量代码替换较小的占位符时遇到问题。

我在做:

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

我发现问题出在这里,描述 JavaScript的特殊替换模式。由于我用作替换字符串的代码中包含一些代码$,因此它弄乱了输出。

我的解决方案是使用功能替换选项,该选项不做任何特殊替换:

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

1

适用于Node 7.6+的ES2017 / 8,带有用于原子替换的临时写入文件。

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使用本机Promiseutil.promisify
Francisco Mateo

1
@FranciscoMateo是的,但是超过1或2个功能的promisifyAll仍然非常有用。
马特

1

在Linux或Mac上,keep很简单,只需将sed与shell配合使用即可。无需外部库。以下代码在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

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

确保可以改进读取模板功能以流形式读取并逐行编写字节以使其更高效

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.