Thay thế một chuỗi trong một tệp bằng nodejs


166

Tôi sử dụng tác vụ md5 grunt để tạo tên tệp MD5. Bây giờ tôi muốn đổi tên các nguồn trong tệp HTML bằng tên tệp mới trong hàm gọi lại của tác vụ. Tôi tự hỏi cách dễ nhất để làm điều này là gì.


1
Tôi ước có một sự kết hợp giữa renamer và thay thế trong tập tin, cả hai sẽ đổi tên các tập tin và tìm kiếm / thay thế bất kỳ tham chiếu nào cho các tập tin đó.
Brain2000

Câu trả lời:


302

Bạn có thể sử dụng regex đơn giản:

var result = fileAsString.replace(/string to be replaced/g, 'replacement');

Vì thế...

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

Chắc chắn, nhưng tôi phải đọc tệp thay thế văn bản và sau đó viết lại tệp, hoặc có một cách dễ dàng hơn, xin lỗi tôi là một người ở phía trước.
Andreas Köberle

Có thể có một mô-đun nút để đạt được điều này, nhưng tôi không biết về nó. Đã thêm một ví dụ đầy đủ btw.
vào

4
@Zax: Cảm ơn, tôi ngạc nhiên vì 'lỗi' này có thể tồn tại quá lâu;)
vào

1
xin lỗi vì tôi biết utf-8 hỗ trợ nhiều ngôn ngữ như: tiếng việt, tiếng trung ...
vuhung3990

Nếu chuỗi của bạn xuất hiện nhiều lần trong văn bản của bạn, nó sẽ chỉ thay thế chuỗi đầu tiên mà nó tìm thấy.
eltongonc

79

Vì thay thế không hoạt động đối với tôi, tôi đã tạo một gói npm đơn giản thay thế trong tệp để nhanh chóng thay thế văn bản trong một hoặc nhiều tệp. Đó là một phần dựa trên câu trả lời của @ asgoth.

Chỉnh sửa (3 tháng 10 năm 2016) : Gói hiện hỗ trợ các lời hứa và các thông tin, và các hướng dẫn sử dụng đã được cập nhật để phản ánh điều này.

Chỉnh sửa (16 tháng 3 năm 2018) : Gói đã tích lũy được hơn 100 nghìn lượt tải xuống hàng tháng và đã được mở rộng với các tính năng bổ sung cũng như công cụ CLI.

Tải về:

npm install replace-in-file

Yêu cầu mô-đun

const replace = require('replace-in-file');

Chỉ định tùy chọn thay thế

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',
};

Thay thế không đồng bộ bằng các lời hứa:

replace(options)
  .then(changedFiles => {
    console.log('Modified files:', changedFiles.join(', '));
  })
  .catch(error => {
    console.error('Error occurred:', error);
  });

Thay thế không đồng bộ với gọi lại:

replace(options, (error, changedFiles) => {
  if (error) {
    return console.error('Error occurred:', error);
  }
  console.log('Modified files:', changedFiles.join(', '));
});

Thay thế đồng bộ:

try {
  let changedFiles = replace.sync(options);
  console.log('Modified files:', changedFiles.join(', '));
}
catch (error) {
  console.error('Error occurred:', error);
}

3
Tuyệt vời và dễ dàng để sử dụng mô-đun chìa khóa trao tay. Được sử dụng với async / await và toàn cầu trên một thư mục khá lớn và nó nhanh như chớp
Matt Fletcher

Nó có thể hoạt động với kích thước tệp lớn hơn 256 Mb hay không vì tôi đã đọc ở đâu đó giới hạn chuỗi trong nút js là 256 Mb
Alien128

Tôi tin rằng nó sẽ, nhưng cũng có một công việc đang tiến hành để thực hiện thay thế phát trực tuyến cho các tệp lớn hơn.
Adam Reis

1
thật tuyệt, tôi đã tìm và sử dụng gói này (cho công cụ CLI của nó) trước khi tôi đọc câu trả lời SO này. thích nó
Russell Chisholm

38

Có lẽ mô-đun "thay thế" ( www.npmjs.org/package/replace ) cũng sẽ phù hợp với bạn. Nó sẽ không yêu cầu bạn đọc và sau đó viết tệp.

Chuyển thể từ tài liệu:

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

Bạn có biết làm thế nào có thể lọc theo phần mở rộng tập tin trong đường dẫn? một cái gì đó giống như đường dẫn: ['path / to / your / file / *. js'] -> nó không hoạt động
Kalamarico

Bạn có thể sử dụng nút toàn cầu để mở rộng các mẫu toàn cầu thành một mảng các đường dẫn và sau đó lặp lại chúng.
RobW

3
Điều này là tốt đẹp, nhưng đã bị bỏ rơi. Xem stackoverflow.com/a/31040890/1825390 để biết gói bảo trì nếu bạn muốn một giải pháp vượt trội.
xavdid

1
Ngoài ra còn có một phiên bản duy trì được gọi là thay thế nút ; tuy nhiên, nhìn vào cơ sở mã, điều này cũng không thay thế trong tệp thực sự thay thế văn bản trong tệp, họ sử dụng readFile()writeFile()giống như câu trả lời được chấp nhận.
c1moore

26

Bạn cũng có thể sử dụng chức năng 'sed' là một phần của ShellJS ...

 $ npm install [-g] shelljs


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

Truy cập ShellJs.org để biết thêm ví dụ.


đây có vẻ là giải pháp sạch nhất :)
NGÀY 16/03/2016

1
shxcho phép bạn chạy từ các tập lệnh npm, ShellJs.org khuyến nghị nó. github.com/shelljs/shx
Joshua Robinson

Tôi cũng thích nó. Tốt hơn một oneliner, hơn mô-đun npm, nhưng các dòng mã
vượt trội

Nhập một phụ thuộc của bên thứ ba không phải là giải pháp sạch nhất.
bốn43

Điều này sẽ không làm đa dòng.
chovy

5

Bạn có thể xử lý tệp trong khi được đọc bằng cách sử dụng luồng. Nó giống như sử dụng bộ đệm nhưng với API thuận tiện hơn.

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
Nhưng ... chuyện gì sẽ xảy ra nếu đoạn chia tách chuỗi regrecFind? Không phải ý định thất bại sao?
Jaakko Karhu

Đó là một điểm rất tốt. Tôi tự hỏi nếu bằng cách đặt một bufferSizechuỗi dài hơn chuỗi bạn đang thay thế và lưu đoạn cuối cùng và nối với chuỗi hiện tại, bạn có thể tránh được vấn đề đó không.
sanbor

1
Có lẽ đoạn mã này cũng nên được cải thiện bằng cách ghi tệp đã sửa đổi trực tiếp vào hệ thống tệp thay vì tạo một biến lớn vì tệp có thể lớn hơn bộ nhớ khả dụng.
sanbor

1

Tôi gặp vấn đề khi thay thế một trình giữ chỗ nhỏ bằng một chuỗi mã lớn.

Tôi đã làm:

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

Tôi đã tìm ra vấn đề là các mẫu thay thế đặc biệt của JavaScript, được mô tả ở đây . Vì mã mà tôi đang sử dụng làm chuỗi thay thế có một số $trong đó, nó đã làm rối đầu ra.

Giải pháp của tôi là sử dụng tùy chọn thay thế chức năng mà KHÔNG thực hiện bất kỳ thay thế đặc biệt nào:

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

1

ES2017 / 8 cho Node 7.6+ với tệp ghi tạm thời để thay thế nguyên tử.

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
}

Lưu ý, chỉ dành cho các tệp nhỏ vì chúng sẽ được đọc vào bộ nhớ.


Không cần bluebird, sử dụng nguồn gốc Promiseproduc.promisify .
Francisco Mateo

1
@FranciscoMateo Đúng, nhưng ngoài 1 hoặc 2 chức năng thì promisify ALL vẫn siêu hữu ích.
Matt

1

Trên Linux hoặc Mac, keep rất đơn giản và chỉ cần sử dụng sed với shell. Không cần thư viện bên ngoài. Đoạn mã sau hoạt động trên Linux.

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

Cú pháp sed hơi khác một chút trên Mac. Tôi không thể kiểm tra nó ngay bây giờ, nhưng tôi tin rằng bạn chỉ cần thêm một chuỗi trống sau "-i":

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

Chữ "g" sau trận chung kết "!" làm cho sed thay thế tất cả các trường hợp trên một dòng. Xóa nó và chỉ lần xuất hiện đầu tiên trên mỗi dòng sẽ được thay thế.


1

Mở rộng câu trả lời của @ Sanbor, cách hiệu quả nhất để làm điều này là đọc tệp gốc dưới dạng luồng và sau đó truyền từng đoạn vào một tệp mới, sau đó thay thế tệp gốc bằng tệp mới.

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

Tôi sẽ sử dụng một luồng song công thay thế. giống như tài liệu ở đây các luồng song công của nodejs

Luồng chuyển đổi là luồng Song công trong đó đầu ra được tính theo cách nào đó từ đầu vào.


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

để chắc chắn rằng bạn có thể cải thiện chức năng đọc mẫu để đọc dưới dạng luồng và soạn các byte theo dòng để làm cho nó hiệu quả hơn

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.