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