ChatGPT解决这个技术问题 Extra ChatGPT

用nodejs替换文件中的字符串

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

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

J
Jonathan Lin

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

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 支持多种语言,例如:越南语、中文...
如果您的字符串在您的文本中出现多次,它将仅替换它找到的第一个字符串。
A
Adam Reis

由于替换对我不起作用,我创建了一个简单的 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 工具)。爱它
出色的!这工作超级快速和容易!
绝对精彩的代码,以及启动的配件解释!
G
Gabriel

也许“替换”模块 (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 模式扩展为路径数组,然后对其进行迭代。
这很好,但已被放弃。如果您需要开箱即用的解决方案,请参阅 stackoverflow.com/a/31040890/1825390 了解维护包。
还有一个名为 node-replace 的维护版本;但是,查看代码库 this 和 replace-in-file 实际上 都没有替换文件中的文本,他们使用 readFile()writeFile() 就像接受的答案一样。
该库工作正常,但不支持 Typescript
T
Tony O'Hagan

您还可以使用 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 更好的 oneliner,但代码行数更丰富^^
导入第三方依赖不是最干净的解决方案。
这不会做多行。
S
SridharKritha

如果有人想为任务使用基于 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');

s
sanbor

您可以在使用流读取文件的同时处理文件。这就像使用缓冲区,但使用了更方便的 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 并保存最后一个块并与当前块连接,您是否可以避免该问题。
可能还应该通过将修改后的文件直接写入文件系统而不是创建一个大变量来改进这个片段,因为文件可能大于可用内存。
@JaakkoKarhu 我制作了一个 npm 包,将旧块保存在内存中,以防字符串跨越多个块。它称为 stream-replace-string。它不适用于正则表达式,但在查找字符串时它是一种有效的解决方案。
7
777

在 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 替换一行中的所有实例。删除它,只有每行的第一个匹配项将被替换。


h
hellatan

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

这不处理 regexFindPattern 匹配的文本被分成两个块的情况。
V
Vivek Molkar

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

我在做:

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

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

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

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

M
Matt

适用于 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
@FranciscoMateo 是的,但除了 1 或 2 个函数 promisifyAll 仍然非常有用。
N
Natdrip

这可能会帮助某人:

这与全局替换有点不同

从我们运行的终端
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,另一种用于编写可能有更好的方法来执行此操作,但它现在可以工作。

谢谢


p
pungggi

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

转换流是双工流,其中输出以某种方式从输入中计算出来。


E
Ezzabuzaid

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

当然,您可以改进阅读模板功能以作为流读取并逐行组合字节以提高效率


u
user9987636

通常,我使用 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