用nodejs替换文件中的字符串

Posted

技术标签:

【中文标题】用nodejs替换文件中的字符串【英文标题】:Replace a string in a file with nodejs 【发布时间】:2012-12-20 01:39:12 【问题描述】:

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

【问题讨论】:

我希望有一个重命名器和文件替换组合,它既可以重命名文件,又可以搜索/替换这些文件的任何引用。 @Brain2000 查看 gulp-rev-all at:github.com/smysnk/gulp-rev-all(它可以一步完成哈希版本控制...易于使用) 【参考方案1】:

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

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

【讨论】:

当然可以,但是我必须阅读文件替换文本然后再次写入文件,还是有更简单的方法,对不起,我更像是一个前端人员。 也许有一个节点模块可以实现这一点,但我不知道。顺便说一句,添加了一个完整的示例。 @Zax:谢谢,我很惊讶这个“虫子”能存活这么久;) 抱歉,我知道 utf-8 支持多种语言,例如:越南语、中文... 如果您的字符串在您的文本中出现多次,它将仅替换它找到的第一个字符串。【参考方案2】:

由于替换对我不起作用,我创建了一个简单的 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',
;

用 Promise 进行异步替换:

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

【讨论】:

伟大且易于使用的交钥匙模块。将它与 async/await 和一个 glob 一起使用在一个相当大的文件夹上,速度非常快 它是否能够处理大于 256 Mb 的文件,因为我在某处读到节点 js 中的字符串限制为 256 Mb 很好,在我阅读这个 SO 答案之前,我发现并使用了这个包(用于它的 CLI 工具)。喜欢它 太棒了!这非常快速和简单! 绝对精彩的代码,以及开机的配件说明!【参考方案3】:

也许“替换”模块 (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'] --> 它不起作用 您可以使用 node-glob 将 glob 模式扩展为路径数组,然后对其进行迭代。 这很好,但已被放弃。如果您需要开箱即用的解决方案,请参阅 ***.com/a/31040890/1825390 以获取维护包。 还有一个维护版本,叫做node-replace;但是,查看代码库 this 和 replace-in-file actually 都没有替换文件中的文本,他们使用 readFile()writeFile() 就像接受的答案一样。 该库运行良好,但不支持 Typescript【参考方案4】:

您还可以使用 ShellJS 中的“sed”函数...

 $ npm install [-g] shelljs


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

完整的文档...

ShellJS - sed() ShellJS

【讨论】:

这似乎是最干净的解决方案 :) shx 允许您从 npm 脚本运行,ShellJs.org 推荐它。 github.com/shelljs/shx 我也喜欢这个。比 npm-module 更好的单行器,但代码行数 ^^ 导入第三方依赖不是最干净的解决方案。 这不会做多行。【参考方案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');

【讨论】:

但是...如果块拆分了 regexpFind 字符串怎么办?那么意图不就失败了吗? 这是一个很好的观点。我想知道是否通过设置比您要替换的字符串更长的bufferSize 并保存最后一个块并与当前块连接可以避免这个问题。 可能这个 sn-p 也应该通过将修改后的文件直接写入文件系统而不是创建一个大变量来改进,因为文件可能大于可用内存。 @JaakkoKarhu 我制作了一个 npm 包,将旧块保存在内存中,以防字符串跨越多个块。它被称为stream-replace-string。它不适用于正则表达式,但在查找字符串时它是一种有效的解决方案。【参考方案6】:

在 Linux 或 Mac 上,keep 很简单,只需在 shell 中使用 sed。不需要外部库。以下代码适用于 Linux。

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

Mac 上的 sed 语法略有不同。我现在无法测试它,但我相信你只需要在“-i”之后添加一个空字符串:

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

最后的“!”之后的“g”使 sed 替换一行中的所有实例。删除它,只有每行的第一个匹配项将被替换。

【讨论】:

【参考方案7】:

扩展@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);
  
)()

【讨论】:

【参考方案8】:

如果有人想为任务使用基于 Promise 的“fs”模块。

const fs = require('fs').promises;

// Below statements must be wrapped inside the 'async' function:
const data = await fs.readFile(someFile, 'utf8');
const result = data.replace(/string to be replaced/g, 'replacement');
await fs.writeFile(someFile, result,'utf8');

【讨论】:

【参考方案9】:

我在用一大串代码替换一个小的占位符时遇到了问题。

我在做:

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

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

我的解决方案是使用函数替换选项,它不做任何特殊替换:

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

【讨论】:

【参考方案10】:

用于 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,使用原生Promise和util.promisify。 @FranciscoMateo 是的,但除了 1 或 2 个函数 promisifyAll 仍然非常有用。【参考方案11】:

这可能对某人有所帮助:

这与全局替换有点不同

从我们运行的终端node replace.js

替换.js:

function processFile(inputFile, repString = "../") 
var fs = require('fs'),
    readline = require('readline'),
    instream = fs.createReadStream(inputFile),
    outstream = new (require('stream'))(),
    rl = readline.createInterface(instream, outstream);
    formatted = '';   

const regex = /<xsl:include href="([^"]*)" \/>$/gm;

rl.on('line', function (line) 
    let url = '';
    let m;
    while ((m = regex.exec(line)) !== null) 
        // This is necessary to avoid infinite loops with zero-width matches
        if (m.index === regex.lastIndex) 
            regex.lastIndex++;
        
        
        url = m[1];
    

    let re = new RegExp('^.* <xsl:include href="(.*?)" \/>.*$', 'gm');

    formatted += line.replace(re, `\t<xsl:include href="$repString$url" />`);
    formatted += "\n";
);

rl.on('close', function (line) 
    fs.writeFile(inputFile, formatted, 'utf8', function (err) 
        if (err) return console.log(err);
    );

);



// path is relative to where your running the command from
processFile('build/some.xslt');

这就是它的作用。 我们有几个文件有 xml:includes

但是在开发过程中,我们需要向下移动一个级别的路径。

从这里

<xsl:include href="common/some.xslt" />

到这里

<xsl:include href="../common/some.xslt" />

所以我们最终运行了两种 regx 模式,一种用于获取 href,另一种用于编写 可能有更好的方法来做到这一点,但它现在有效。

谢谢

【讨论】:

【参考方案12】:

我会改用双工流。就像这里记录的nodejs doc duplex streams

Transform 流是一个 Duplex 流,其输出在 来自输入的某种方式。

【讨论】:

【参考方案13】:

&lt;p&gt;Please click in the following link to verify the account&lt;/p&gt;


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

当然你可以改进阅读模板功能以流形式读取并逐行组合字节以使其更高效

【讨论】:

【参考方案14】:

Nomaly,我使​​用tiny-replace-files 替换文件中的文本。这个 pkg 更小更轻... https://github.com/Rabbitzzc/tiny-replace-files

import  replaceStringInFilesSync  from 'tiny-replace-files'

const options = 
  files: 'src/targets/index.js',
  from: 'test-plugin',
  to: 'self-name',


# await
const result = replaceStringInFilesSync(options)
console.info(result)

【讨论】:

虽然此链接可能会回答问题,但最好在此处包含答案的基本部分并提供链接以供参考。如果链接页面发生更改,仅链接答案可能会失效。 - From Review

以上是关于用nodejs替换文件中的字符串的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 NodeJS 替换 PDF 文件中的字符串?

用来自不同文件的字符串替换文件中的字符串

怎么用C语言对文件操作用特定内容替换其中某些内容

用Python在给定目录及其子目录中递归替换文件中的字符串?

nodejs 匹配字符串问题

为啥用'\0'替换文件名中的无效字符?