แทนที่สตริงในไฟล์ด้วย nodejs


167

ฉันใช้งานmd5 gruntเพื่อสร้างชื่อไฟล์ MD5 ตอนนี้ฉันต้องการเปลี่ยนชื่อแหล่งที่มาในไฟล์ HTML ด้วยชื่อไฟล์ใหม่ในการเรียกกลับของงาน ฉันสงสัยว่าอะไรเป็นวิธีที่ง่ายที่สุดในการทำเช่นนี้


1
ฉันหวังว่าจะมีการเปลี่ยนชื่อและชุดแทนที่ในไฟล์ซึ่งทั้งสองจะเปลี่ยนชื่อไฟล์และค้นหา / แทนที่การอ้างอิงใด ๆ สำหรับไฟล์เหล่านั้นเช่นกัน
Brain2000

คำตอบ:


303

คุณสามารถใช้ regex ง่าย ๆ :

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

แน่นอน แต่ฉันต้องอ่านไฟล์แทนที่ข้อความแล้วเขียนไฟล์อีกครั้งหรือมีวิธีที่ง่ายกว่าขออภัยฉันเป็นคนที่มากกว่า
Andreas Köberle

อาจมีโมดูลโหนดเพื่อให้บรรลุนี้ แต่ฉันไม่ทราบ เพิ่มตัวอย่างแบบเต็ม btw
asgoth

4
@ Zax: ขอบคุณฉันแปลกใจที่ 'ข้อผิดพลาด' นี้สามารถอยู่รอดได้นาน)
asgoth

1
ขอโทษที่ฉันรู้ utf-8 รองรับหลายภาษาเช่น: เวีย
นาม

หากสตริงของคุณปรากฏหลายครั้งในข้อความของคุณมันจะแทนที่เฉพาะสตริงแรกที่พบ
eltongonc

79

เนื่องจากการแทนที่ไม่ได้ผลสำหรับฉันฉันได้สร้างแพ็คเกจ NPM แบบง่ายๆเพื่อแทนที่ข้อความในไฟล์อย่างน้อยหนึ่งไฟล์ มันขึ้นอยู่กับคำตอบของ @ asgoth บางส่วน

แก้ไข (3 ตุลาคม 2559) : แพคเกจรองรับสัญญาและ globs และคำแนะนำการใช้งานได้รับการปรับปรุงเพื่อสะท้อนถึงสิ่งนี้

แก้ไข (16 มีนาคม 2561) : แพ็คเกจดังกล่าวได้รับการดาวน์โหลดมากกว่า 100,000 ครั้งต่อเดือนและได้ขยายคุณสมบัติเพิ่มเติมรวมถึงเครื่องมือ 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 / คอยและก้อนหนึ่งค่อนข้างเป็นโฟลเดอร์ขนาดใหญ่และมันก็เร็วมาก
Matt Fletcher

มันจะสามารถทำงานกับขนาดไฟล์ที่ใหญ่กว่า 256 Mb หรือไม่เพราะฉันอ่านที่ไหนสักแห่งที่ขีด จำกัด สตริงในโหนด js คือ 256 Mb
Alien128

ฉันเชื่อว่าเป็นเช่นนั้น แต่ยังมีความคืบหน้าในการใช้การแทนที่การสตรีมสำหรับไฟล์ขนาดใหญ่
Adam Reis

1
ดีฉันพบและใช้แพคเกจนี้ (สำหรับเครื่องมือ CLI ของมัน) ก่อนที่ฉันจะอ่านคำตอบนี้ รักมัน
รัสเซลชิสโฮล์ม

38

บางทีโมดูล "replace" ( 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
นอกจากนี้ยังมีเวอร์ชันบำรุงรักษาที่เรียกว่าโหนดแทน ; อย่างไรก็ตามการดูที่ฐานรหัสไม่ว่าจะเป็นแบบนี้หรือแบบแทนที่ในไฟล์จริง ๆ แล้วจะเป็นการแทนที่ข้อความในไฟล์พวกมันใช้readFile()และwriteFile()เหมือนกับคำตอบที่ยอมรับได้
c1moore

26

คุณสามารถใช้ฟังก์ชั่น 'sed' ที่เป็นส่วนหนึ่งของ ShellJS ...

 $ npm install [-g] shelljs


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

เยี่ยมชมShellJs.orgเพื่อดูตัวอย่างเพิ่มเติม


นี้น่าจะเป็นทางออกที่สะอาด :)
Yerken

1
shxให้คุณเรียกใช้จากสคริปต์ npm, ShellJs.org แนะนำให้มัน github.com/shelljs/shx
Joshua Robinson

ฉันชอบสิ่งนี้เช่นกัน ออนไลเนอร์ที่ดีกว่าดีกว่าโมดูล npm แต่เป็นรหัสเซอร์เวอร์ ^ ^
suther

การนำเข้าการพึ่งพาของบุคคลที่สามไม่ใช่วิธีที่สะอาดที่สุด
four43

นี่จะไม่ทำหลายบรรทัด
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สตริงที่ยาวกว่าที่คุณกำลังแทนที่และบันทึกอันสุดท้ายและเชื่อมต่อกับอันปัจจุบันที่คุณสามารถหลีกเลี่ยงปัญหานั้นได้หรือไม่
sanbor

1
อาจเป็นข้อมูลโค้ดนี้ควรได้รับการปรับปรุงโดยการเขียนไฟล์ที่ถูกแก้ไขไปยังระบบไฟล์โดยตรงแทนที่จะสร้างตัวแปรขนาดใหญ่เนื่องจากไฟล์อาจใหญ่กว่าหน่วยความจำที่มีอยู่
sanbor

1

ฉันพบปัญหาเมื่อแทนที่ตัวยึดตำแหน่งขนาดเล็กด้วยรหัสสตริงจำนวนมาก

ฉันกำลังทำ:

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

ฉันคิดว่าปัญหาคือ JavaScript ของรูปแบบการเปลี่ยนพิเศษที่อธิบายไว้ที่นี่ เนื่องจากรหัสที่ฉันใช้เป็นสายอักขระการแทนที่มีบางอย่าง$อยู่ในนั้นจึงทำให้การส่งออกเกิดความสับสน

โซลูชันของฉันคือใช้ตัวเลือกการแทนที่ฟังก์ชันซึ่งไม่ได้ทำการทดแทนพิเศษใด ๆ :

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

1

ES2017 / 8 สำหรับ Node 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
}

หมายเหตุเฉพาะไฟล์ขนาดเล็กเท่านั้นเนื่องจากจะถูกอ่านในหน่วยความจำ


ไม่จำเป็นต้องbluebirdใช้พื้นเมืองPromiseและutil.promisify
Francisco Mateo

1
@ FranciscoMateo จริง แต่นอกเหนือจาก 1 หรือ 2 ฟังก์ชั่น promisifyAll ยังคงมีประโยชน์มาก
Matt

1

บน Linux หรือ Mac Keep ง่ายและใช้ 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`)

"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

ฉันจะใช้สตรีมเพล็กซ์แทน เช่นเอกสารที่นี่nodejs doc สตรีมเพล็กซ์

กระแสการแปลงเป็นกระแสข้อมูลแบบดูเพล็กซ์ที่มีการคำนวณเอาท์พุทในบางวิธีจากอินพุต


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

เพื่อให้แน่ใจว่าคุณสามารถปรับปรุงฟังก์ชันเทมเพลตการอ่านเพื่ออ่านเป็นสตรีมและเขียนไบต์ต่อบรรทัดเพื่อให้มีประสิทธิภาพมากขึ้น

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.