pro-node 之 文件IO

作为脚本语言,学习文件操作很有价值,不是缩进党,所以与python/ruby无缘,但python确实用的越来越多,接触多了还是不喜欢…废话别扯多了,js 的 IO操作

#path相关

__dirname __filename

这两个参数是在require的时候传入的,node将js包装成一个
function(exports, require, module, __filename, __dirname)来传参执行,__dirname表示脚本js文件所在目录,__filename表示文件名

前几天在v2ex上看到一个问题,python中的

1
2
if __name__ == "main"
main()

如何模拟,因为我之前写的node基础里面有,require的parent children属性,被require时parent不为空

1
2
3
if(!module.parent){
//main
}

各大神支招,当js直接被执行,不是被require的时候有

1
process.mainModule = require.main = module

于是方法多了去了…乱扯几句吧

##process.cwd()
当前工作目录
process.chdir()切换目录,这个是同步执行的,可以try catch

##process.execPath
表示node可执行文件的位置,如windows上是node.exe位置


path模块

1
2
3
4
5
6
7
8
9
10
11
12
13
> path
{ resolve: [Function],
normalize: [Function],
join: [Function],
relative: [Function],
sep: '\\',
delimiter: ';',
dirname: [Function],
basename: [Function],
extname: [Function],
exists: [Function: deprecated],
existsSync: [Function: deprecated],
_makeLong: [Function] }

##path属性
path.sep表示路径分隔符,win上是\即backslash,其他系统/forwardslash
path.delimiter表示界定符,即PATH环境变量中每个文件夹的分隔符,在win上是;,其他系统是:

##获取path的各个部分

###扩展名path.extname()

1
2
> path.extname("d:/dir/abc.txt")
'.txt'

注意扩展名带有.

###文件名path.basename

1
2
3
4
5
6
7
8
9
10
11
12
> txt = "d:/dir/abc.txt"
'd:/dir/abc.txt'
> path.basename(txt)
'abc.txt'
> path.basename(txt,'txt')
'abc.'
> path.basename(txt,'.txt')
'abc'
> path.basename("d:/dir/abc")
'abc'
> path.basename("d:/dir/abc/")
'abc'

  • basename传一个参数,将最后一部分返回,用分隔符分隔\\/
  • basename传两个参数,后面是它的扩展名,会将扩展名从文件名中去处
    如果传递的扩展名不带.号,则剩下的文件名带.
  • basename还可以用于文件夹,简单地取最后一部分

###文件夹path.dirname

1
2
3
4
5
6
7
8
9
10
> txt
'd:/dir/abc.txt'
> path.dirname(txt)
'd:/dir'
> dir = "d:/dir/abc"
'd:/dir/abc'
> path.dirname(dir)
'd:/dir'
> path.dirname('d:/dir/abc/')
'd:/dir'

返回文件或目录的父文件夹路径

##正常化path(normalization)
把绝对路径 相对路径 混杂的变成绝对路径

1
2
> path.normalize("/foo/bar/.././bar/../../baz")
'\\baz' //在linux上是/baz

path.join也可以混杂,相对路径,绝对路径,还有path.resolve作用也是解决路径混杂

##相对路径path.relative
path.relative(from,to)

1
2
> path.relative('d:/dir/abc','d:/abc')
'..\\..\\abc'

就是以第一个参数为起点,第二个参数为终点的相对路径

#file相关 fs module
fs模块的方法都有 异步版本 与 同步版本(添加Sync)后缀,虽然异步很美好,但是同步更好理解,遵循一个原则就好

As a general rule, code that can be called
multiple times simultaneously should be asynchronous
– pro nodejs for developers

simultaneously同时地,就是这段代码可能同时执行好几次,典型的request - server就是,应该为异步代码,其他如启动读读配置文件什么的还是可以用用Sync的代码的

##文件(夹)是否存在fs.exists
打上文件夹是因为可以用来判断文件夹是否存在,而且path.exists这个方法也可以调用,但是已经作废了,改到fs.exists里面了
fs.exists(path,callback) //callback(exists)
fs.existsSync(path)

注意这个回调没有err的,存在true,不存在false,没理由err

##查看文件状态Statstics
fs.stat
fs.Stats fs.statSync(path)
返回的fs.Stats类型成员

字段

方法,这些方法是同步执行,返回true or false值

fs.stat变种

  • fs.lstat,如果参数文件是个link的话,fs.stat只看这个link的statistic数据,而lstat会查看这个link指向的文件的数据
  • fs.fstat,这个第一个参数是file descriptor而不是string

##打开/关闭文件 fs.open/close

1
2
3
4
fs.open(path,flag,callback)//callback(err,fd) fd=file descriptor
fd = fs.openSync(path,flag)

fs.close(fd,function(err){ //... })

可选参数mode默认0666
关于flag

带x的是exclusive,独占模式
使用w+,可读可写,文件不存在,创建,已存在,清空原有内容

##fs.read()/write()
提供C#类似Stream的功能,可自定义文件读取位置什么的

1
2
3
4
5
6
7
fs.open("d:/dir/abc.txt","w+",function(err,fd){
fs.read(fd,buffer,offset,length,from,callback);
//callback(err,bytesRead,buffer)

fs.write(fd, buffer, 0, buffer.length, null,
function(error, written, buffer))
});

  • fd : 文件描述符
  • buffer : Buffer数组
  • offset : 从buffer的那个位置开始放置
  • length : 要读取得长度
  • from : 表示要从文件哪里开始读,null表示文件开头

同步版本返回已读取的字节数

##fs.readFile/writeFile
就是open read的封装了,相当于C#里的System.IO.File.ReadAllText()不用管文件打开关闭那些闲杂事了

1
2
3
4
5
data = fs.readFileSync(path,flag)
fs.readFile(path.flag,callback) //callback(err,data)

fs.writeFile(path,data,function(err){ //balbal })
fs.writeFileSync(path,data)

encoding可选,默认”utf8”,第三个参数
默认flag,w+

##重命名fs.rename

1
2
fs.rename(old,new,function(err){ })
fs.renameSync(old,new)

##删除文件fs.unlink

1
2
fs.unlink(path,function(err){ ... })
fs.unlinkSync(path)

当文件不存在则会报错…

##新建文件夹fs.mkdir

1
2
fs.mkdir("abcd",function(err){ ... });
fs.mkdirSync("abcd")

只能新建最底层的文件夹,比如新建d:/not/exists/sub就会出错,文件夹已存在出错,可以使用fs-extra来新建多重文件夹

##读取文件夹内容fs.readdir
注意readdir,dir小写

1
2
path.readdir(path,function(err,stats))
var contents = fs.readdirSync(path)

得到的是fs.Stats数组,可以使用isFile()/isDirectory来判断文件还是文件夹

##删除文件夹fs.rmdir

1
2
fs.rmdir(path,function(err){ ... })
fs.rmdirSync(path)

只能删除非空文件夹,有内容的话,删除会报错,可以自己实现递归删除的…

##watch目录,fs.watch
找不到合适的中文,就用watch吧

1
2
3
FSWatcher watcher = fs.watch(path,{ persistent : true } , function(event,path){
//哪个文件发生了什么变动
});

返回的是FSWatcher类型实例,可以监听change事件

1
watcher.on("change",function(){ ... })

跟watch方法传递的参数一致,event有change|rename,但我测试仅仅创建一个文件,就有这么多事件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[change] : Desktop
[change] : ntuser.dat.LOG1
[change] : NTUSER.DAT
[change] : NTUSER.DAT
[change] : NTUSER.DAT
[rename] : null
[rename] : a.txt
[change] : Desktop
[change] : a.txt
[change] : ntuser.dat.LOG1
[change] : NTUSER.DAT
[change] : NTUSER.DAT
[change] : NTUSER.DAT
[change] : ntuser.dat.LOG1
[change] : NTUSER.DAT
[change] : NTUSER.DAT
[change] : NTUSER.DAT

推荐使用chokidar这个library,hexo也在用
使用watcher.close()关闭watcher