node.js 文件操作
既然到了后台,只要你有权限就可以为所欲为了!
创建文件夹
//fs.js var fs = require( 'fs' ); fs.mkdirSync( 'a' , 0755); fs.mkdirSync( 'a/b' , 0755); fs.mkdirSync( 'a/b/c' , 0755); |
那么它会在fs.js所在目录中创建一个a目录,a目录下再创建b目录,b目录下创建c目录。mkdirSync是一个同步方法,拥有三个参数,第一个是路径,第二个是目录权限,第三个是回调
但这样做有点不妥,如果指定目录已存在,就报错。如果要建立多级目录,一层层判定此目录是否存在,立即会陷入“回调地狱”的境地。虽然node.js对于IO操作的方法都提供了两个版本,一个是同步的,一个是异步的。想了想,搞出我自己的mkdirSync函数,实现比mkdirp的作者好很多。
var fs = require( 'fs' ); function mkdirSync(url,mode,cb){ var path = require( "path" ), arr = url.split( "/" ); mode = mode || 0755; cb = cb || function (){}; if (arr[0]=== "." ){ //处理 ./aaa arr.shift(); } if (arr[0] == ".." ){ //处理 ../ddd/d arr.splice(0,2,arr[0]+ "/" +arr[1]) } function inner(cur){ if (!path.existsSync(cur)){ //不存在就创建一个 fs.mkdirSync(cur, mode) } if (arr.length){ inner(cur + "/" +arr.shift()); } else { cb(); } } arr.length && inner(arr.shift()); } //测试代码 mkdirSync( "aaa/ddd/dd" ,0, function (e){ if (e){ console.log( '出错了' ); } else { console.log( "创建成功" ) } }); |
有关目录的创建的讨论,详见这里
创建文件与写入内容
创建文件的方法fs.open(path, flags, [mode], [callback])好像所有语言都很一致,可能是想与最原始的C语言的方法保持一致。这是个异步方法,其同步版本,与其他同步方法一样,就是少了个回调。说一下flags中w与a的不同,w会把已存在的同名文件删除再创建一个,因此要注意。
fs.open( "test.txt" , "w" ); |
写入内容。这有几种方法,比如fs.write,不过它的第一个参数要求类型为Buffer,而fs.openSync不是返回一个Buffer,因此它不能与fs.write配合使用。
//fs.js var fs = require( 'fs' ); fs.open( "test.txt" , "w" ,0644, function (e,fd){ if (e) throw e; fs.write(fd, "first fs!" ,0, 'utf8' , function (e){ if (e) throw e; fs.closeSync(fd); }) }); |
这时,我们就创建一个文本文件,里面写入"first fs"。不过open中间那两个参数的不是必要的。我们可以再搞一个fs2.js脚本
//fs2.js var fs = require( 'fs' ); fs.open( "test.txt" , "a" ,0644, function (e,fd){ if (e) throw e; fs.write(fd, "second fs!" , function (e){ if (e) throw e; fs.closeSync(fd); }) }); |
执行它,它会在test.txt追加"second fs"这句话。
fs的open, write, close有点像我们在浏览器对document的操作。此外,还有fs.writeFile与fs.writeFileSync,可惜它们不能做追加内容的操作。
var fs = require( 'fs' ); fs.writeFile( "test.txt" , "third fs!" , function (e){ //会先清空原先的内容 if (e) throw e; }) |
删除目录或文件
var fs = require( 'fs' ); fs.rmdir( "aaa" , function (e){ if (e){ console.log(e) } }) /** { stack: [Getter/Setter], arguments: undefined, type: undefined, message: 'ENOTEMPTY, Directory not empty \'aaa\'', errno: 39, code: 'ENOTEMPTY', path: 'aaa' } */ |
报错,缘由是之前我们还为aaa创建了子目录,必须逐个删除,实在不人性化,于是又造了一个轮子。
var fs = require( 'fs' ); var rmdirSync = ( function (){ function iterator(url,dirs){ var stat = fs.statSync(url); if (stat.isDirectory()){ dirs.unshift(url); //收集目录 inner(url,dirs); } else if (stat.isFile()){ fs.unlinkSync(url); //直接删除文件 } } function inner(path,dirs){ var arr = fs.readdirSync(path); for ( var i = 0, el ; el = arr[i++];){ iterator(path+ "/" +el,dirs); } } return function (dir,cb){ cb = cb || function (){}; var dirs = []; try { iterator(dir,dirs); for ( var i = 0, el ; el = dirs[i++];){ fs.rmdirSync(el); //一次性删除所有收集到的目录 } cb() } catch (e){ //如果文件或目录本来就不存在,fs.statSync会报错,不过我们还是当成没有异常发生 e.code === "ENOENT" ? cb() : cb(e); } } })(); rmdirSync( "aaa" , function (e){ console.log( "!!!" +e) console.log( "删除aaa目录以及子目录成功" ) }) |
上面的rmdirSync还可以分解出一个方法,取得给定目录下的所有目录与文件。
var fs = require( 'fs' ); var getAllFolersAndFiles = ( function (){ function iterator(url, folders, files){ var stat = fs.statSync(url); if (stat.isDirectory()){ folders.unshift(url); //收集目录 inner(url,folders, files); } else if (stat.isFile()){ files.unshift(url); //收集文件 } } function inner(path,folders,files){ var arr = fs.readdirSync(path); for ( var i = 0, el ; el = arr[i++];){ iterator(path+ "/" +el,folders,files); } } return function (dir){ var folders = [], files = []; try { iterator(dir,folders,files); } catch (e){ }finally{ return { folders : folders, files : files } } } })() |
如果我们只想取得指定目下的所有文件,还可以更精简些!
function getAllFiles(root) { var result = [], files = fs.readdirSync(root) files.forEach( function (file) { var pathname = root+ "/" + file , stat = fs.lstatSync(pathname) if (stat === undefined) return // 不是文件夹就是文件 if (!stat.isDirectory()) { result.push(pathname) // 递归自身 } else { result = result.concat(getAllFiles(pathname)) } }); return result } |
监听文件的改动
就是使用watchFile及其同步版本。
var fs = require( "fs" ), sys = require( "util" ); fs.open( "aaa.js" , "w" ,0644, function (e,fd){ fs.writeSync(fd, "console.log(1111);" ); fs.closeSync(fd); }) fs.watchFile( "aaa.js" , function (curr, prev) { sys.puts( "\n\ttest_file.txt has been edited" ); sys.puts( "\tThe current mtime is: " + curr.mtime); sys.puts( "\tThe previous mtime was: " + prev.mtime + "\n" ); }); fs.open( "aaa.js" , "a" ,0644, function (e,fd){ fs.writeSync(fd, "console.log(2222)" ); fs.closeSync(fd); }); |
此外,node.js还提供了修改了目录文件权限的操作,但这些都是不值一提。自己看看文档就会了。
//合并脚本 var fs = require( "fs" ) var path = require( "path" ) //不同的操作系统,其 文件目录 分割符是不一样的,不能直接使用 + "/"来实现 var curDir = process.cwd() //当前目录 var otherDir = curDir.replace(/avalon[\/\\]src/, "" ) console.log( "otherDir " + otherDir) var Buffer = require( 'buffer' ).Buffer var now = new Date var date = now.getFullYear() + "." + now.getMonth() + "." + now.getDate() function directive(name) { return path.join( "15 directive" , name) } //avalon.js 所需要合并的子文件 var compatibleFiles = [ "00 inter" , "01 variable" , "01 variable.share" , "02 core" , "03 es5.shim" , "04 dom.polyfill" , "05 configuration" , "06 EventBus" , "06 findNodes" , "07 modelFactory" , "07 modelFactory.shim" , "08 Collection" , "09 dispatcher" , "10 HTML" , "12 scan" , "12 scanTag" , "12 scanNode" , "12 scanAttr" , "12 scanText" , "13 dom" , "14 parser" , "14 parser.share" , directive( "skip" ), directive( "controller" ), directive( "important" ), directive( "attr" ), directive( "include" ), directive( "class.hover.active" ), directive( "data" ), directive( "text" ), directive( "html" ), directive( "if" ), directive( "visible" ), directive( "on" ), directive( "widget" ), directive( "duplex.1" ), directive( "duplex.2" ), directive( "duplex.3" ), directive( "repeat.each.with" ), "16 filter" , "17 loader" , "18 domReady" , "19 outer" ] //avalon.modern.js 所需要合并的子文件 var modernFiles = [ "00 inter" , "01 variable.modern" , "01 variable.share" , "02 core.modern" , "04 dom.polyfill.modern" , "05 configuration" , "06 EventBus" , "06 findNodes.modern" , "07 modelFactory" , "08 Collection" , "09 dispatcher" , "10 HTML.modern" , "12 scan" , "12 scanTag" , "12 scanNode" , "12 scanAttr.modern" , "12 scanText" , "13 dom.modern" , "14 parser.modern" , "14 parser.share" , directive( "skip" ), directive( "controller" ), directive( "important" ), directive( "attr" ), directive( "include" ), directive( "class.hover.active" ), directive( "data" ), directive( "text.modern" ), directive( "html" ), directive( "if" ), directive( "visible" ), directive( "on" ), directive( "widget" ), directive( "duplex.1" ), directive( "duplex.2.modern" ), directive( "duplex.3" ), directive( "repeat.each.with" ), "16 filter" , "17 loader" , "18 domReady.modern" , "19 outer" ] //avalon.shim.js 所需要合并的子文件 var shimFiles = [ "00 inter" , "01 variable" , "01 variable.share" , "02 core" , "03 es5.shim" , "04 dom.polyfill" , "05 configuration" , "06 EventBus" , "06 findNodes" , "07 modelFactory" , "07 modelFactory.shim" , "08 Collection" , "09 dispatcher" , "10 HTML" , "12 scan" , "12 scanTag" , "12 scanNode" , "12 scanAttr" , "12 scanText" , "13 dom" , "14 parser" , "14 parser.share" , directive( "skip" ), directive( "controller" ), directive( "important" ), directive( "attr" ), directive( "include" ), directive( "class.hover.active" ), directive( "data" ), directive( "text" ), directive( "html" ), directive( "if" ), directive( "visible" ), directive( "on" ), directive( "widget" ), directive( "duplex.1" ), directive( "duplex.2" ), directive( "duplex.3" ), directive( "repeat.each.with" ), "16 filter" , "18 domReady.noop" , "19 outer" ] var writable = fs.createWriteStream(path.join(curDir, 'avalon.js' ), { encoding: "utf8" }); writable.setMaxListeners(100) //默认只有添加11个事件,很容易爆栈 compatibleFiles.forEach( function (fileName) { var filePath = path.join(curDir, fileName + ".js" ) var readable = fs.createReadStream(filePath) if (fileName == "00 inter" ) { readable.on( 'data' , function (chunk) { var str = chunk.toString( "utf8" ) var offset = ( new Buffer(str.slice(0, str.indexOf( "!!" )), "utf8" )).length chunk.write( " build in " + date + " \n" , offset) }) } // readable.push("//都会插到新文件的最前面") // writable.write("//都会插到新文件的最前面 ") readable.pipe(writable) readable.on( "readable" , function () { writable.write( "\n" ) console.log( "add " + filePath) }) //更新avalon.test中的文件 writable.on( "finish" , function () { var readable3 = fs.createReadStream(path.join(curDir, 'avalon.js' )) var writable3 = fs.createWriteStream(path.join(otherDir, 'avalon.test' , "src" , "avalon.js" )) readable3.pipe(writable3) }); }) var writable2 = fs.createWriteStream(path.join(curDir, 'avalon.modern.js' ), { encoding: "utf8" }) writable2.setMaxListeners(100) //默认只有添加11个事件,很容易爆栈 modernFiles.forEach( function (fileName) { var filePath = path.join(curDir, fileName + ".js" ) var readable = fs.createReadStream(filePath) if (fileName == "00 inter" ) { readable.on( 'data' , function (chunk) { var str = chunk.toString( "utf8" ) var offset = ( new Buffer(str.slice(0, str.indexOf( "!!" )), "utf8" )).length chunk.write( " build in " + date + " \n" , offset) }) } // readable.push("//都会插到新文件的最前面") // writable.write("//都会插到新文件的最前面 ") readable.pipe(writable2) readable.on( "readable" , function () { writable2.write( "\n" ) console.log( "add " + filePath) }) //更新avalon.test中的文件 writable2.on( "finish" , function () { var readable3 = fs.createReadStream(path.join(curDir, 'avalon.modern.js' )) var writable3 = fs.createWriteStream(path.join(otherDir, 'avalon.test' , "src" , "avalon.modern.js" )) readable3.pipe(writable3) }); }) |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?