Node.js 將檔案讀取與寫入, 刪除檔案, 更改檔名, 查詢目錄或連結等操作封裝在核心模組 fs 中, 使用標準 POSIX 呼叫進行檔案操作. 此模組較特別之處是每個函數都提供了同步 (Sync) 與非同步版本, 例如讀取檔案同步版是呼叫 fs.redFileSync(), 而非同步則是 fs.readFile().
本系列之前的測試紀錄參考 :
# 關於 Node.js
# 如何更新樹莓派的 Node.js
# Node.js 學習筆記 (一) : 在 Windows 安裝 Node.js
Node.js 8.x 版檔案系統 API 文件參考 :
# https://nodejs.org/dist/latest-v8.x/docs/api/fs.html
# w3schools : Node.js Tutorial
以下僅就常用的 fs 模組函數進行測試, 除了官網範例外, 還參考了下面兩本書 :
# 你不能錯過的 Node.js 指南
# 不一樣的 Node.js
使用 fs 模組前需先用 require() 載入 :
var fs=require("fs");
然後就可以呼叫它的函數進行檔案操作. 與其他模組不同之處是, fs 模組的函數有非同步或同步之分, 同步版的函數以 Sync 結尾, 其差異如下 :
- 同步 : 無回呼函數 (call back), 執行時程序會阻塞 (blocked) 或停住等待
- 非同步 : 有回呼函數 (call back), 執行時程序不會阻塞 (blocked) 或停住等待
常見的檔案操作測試如下 :
1. 讀取檔案 :
非同步 : fs.readFile(filename, [options,] callback)
同步 : fs.readFileSync(filename, [options,])
此函數用來讀取指定之檔案內容. 第一參數 filename 為包含如 "./" 或 "../" 等路徑之檔案名稱, 第二參數則是一個可有可無之選項物件, 例如指定檔案編碼格式則傳入 {encoding : "utf-8"} 或 {encoding : "ascii"}, 也可以傳入字串如 "utf-8" 或 "ascii". 非同步操作須傳入一個回呼函數來處理檔案操作結束後的傳回值, 它預設為最後一個參數. 以讀取檔案為例, fs.readFile() 處理結束後會傳回 err(錯誤訊息) 與 data(讀取到的檔案內容) 兩個參數, 須將其傳入回呼函數中處理, 例如 :
//fs_test_1.js
var fs=require("fs");
fs.readFile("./test.txt", function(err, data) {
if (err) {console.error(err);}
else {console.log(data);}
})
console.log("檔案讀取操作");
上例是讀取程式所在目錄底下的 test.txt 檔案, 若讀取成功則傳回之 err 為 null, 就會執行 else 輸出讀取到的檔案內容, 否則 err 物件不為 null, 表示檔案讀取失敗, 就輸出 err 物件內容, 例如 :
D:\Node.js\test>node fs_test_1.js
{ Error: ENOENT: no such file or directory, open 'D:\Node.js\test\test.txt'
errno: -4058,
code: 'ENOENT',
syscall: 'open',
path: 'D:\\Node.js\\test\\test.txt' }
如果在程式所在目錄下準備了一個 test.txt 檔, 裡面就只有 Hello 與 World 兩行字 :
Hello
World
將 test.txt 以 ANSI (ASCII) 格式存檔, 執行結果如下 :
D:\Node.js\test>node fs_test_1.js
<Buffer 48 65 6c 6c 6f 0d 0a 57 6f 72 6c 64>
可見傳回的 data 是存在緩衝區物件 Buffer 內的未解碼 ASCII 碼, 其中 0d 0a 分別為跳行字元 CR LF. 如果將 test.txt 以 UTF-8 格式存檔, 則執行結果如下 :
D:\Node.js\test>node fs_test_1.js
檔案讀取操作
<Buffer ef bb bf 48 65 6c 6c 6f 0d 0a 57 6f 72 6c 64>
可見開頭多了 3 個 byte 的 UTF-8 位元組順序記號 ef bb bf.
如果要得到解碼後的文字, 必須在 fs.readFile() 方法中傳入 encoding 參數 (第二參數), 如果 test.txt 是以 ANSI (ASCII) 存檔, 則 encoding 為 "ASCII" 或 "ascii" :
//fs_test_2.js
var fs=require("fs");
fs.readFile("./test.txt", "ASCII", function(err, data) {
if (err) {console.error(err);}
else {console.log(data);}
})
console.log("檔案讀取操作");
可見執行結果不再是 ASCII 碼, 而是已解碼之可讀文字 :
D:\Node.js\test>node fs_test_1.js
檔案讀取操作
Hello
World
或者呼叫 toString() 函數也可以, 例如 :
//fs_test_2.js
var fs=require("fs");
fs.readFile("./test.txt", function(err, data) {
if (err) {console.error(err);}
else {console.log(data.toString());}
})
console.log("檔案讀取操作");
注意, Node.js 非同步函數設計慣例最後一個參數為 callback 函數, 一個函數只有一個 callback. 回呼函數的第一個參數固定為 error 物件, 其餘參數為回呼函數的其他傳回值.
如果將 test.txt 存成 UTF-8 格式, 但卻用 fs_test_2.js 的 ASCII 格式讀取, 則前面三個位元組順序記號將無法解碼為可讀文字 :
D:\Node.js\test>node fs_test_2.js
檔案讀取操作
o;?Hello
World
這時應該用 UTF-8 格式讀取 :
//fs_test_3.js
var fs=require("fs");
fs.readFile("./test.txt", "UTF-8", function(err, data) {
if (err) {console.error(err);}
else {console.log(data);}
console.log("檔案讀取操作完成");
})
console.log("檔案讀取操作中 ... ");
執行結果如下 :
D:\Node.js\test>node fs_test_3.js
檔案讀取操作中 ...
Hello
Tony
檔案讀取操作完成
上面程式均為檔案讀取的非同步操作, 當呼叫 fs.readFile() 之後, 執行緒不會停下來等候檔案讀取動作結束, 而是繼續往下執行, 因此會先印出 "檔案讀取操作", 然後才出現讀取到的檔案內容. 然而 fs.readFile() 的同步版函數 fs.readFileSync() 則非如此, 執行緒會停住等待檔案 I/O 完成. 由於同步版沒有回呼函數, 因此必須用 try catch 來捕捉例外, 例如 :
//fs_test_4.fs
var fs=require("fs");
try {
var data=fs.readFileSync("./test.txt","UTF-8");
console.log(data);
}
catch (err){
console.error(err);
}
console.log("檔案讀取操作");
執行結果如下 :
D:\Node.js\test>node fs_test_4.js
Hello
World
檔案讀取操作
可見與上面非同步操作不同的是, 輸出 "檔案讀取操作" 是在檔案讀取完畢之後才會執行. 以下測試將僅針對非同步.
2. 寫入檔案 :
非同步 : fs.writeFile(file, data[, options], callback)
同步 : fs.writeFileSync(file, data[, options])
第一參數 file 為檔案名稱, 前面可帶 "./" (目前目錄), "../" (上一層目錄) 或 "html/" 等路徑, 不帶時表示目前目錄. 第二參數 data 為欲寫入之資料, 可以是字串或緩衝區物件 buffer. 寫入字串若要跳行在 Linux 中只要用 "\n" 即可, 但在 Windows 中則必須使用 "\r\n". 備選參數 options 可以是物件 (可有 encoding, mode, flag 等三屬性) 或字串 (僅表示 encoding, 預設為 'utf8'), 若寫入資料為 buffer 物件, 則 encoding 會被忽略.
注意, 使用寫入函數前不需要用 fs.exists() 檢查檔案是否已存在, 如果檔案不存在會自動建立一個空檔案; 如果檔案已存在則會覆蓋裡面的內容. 此外, writeFile() 的回呼函數只有 err 一個參數而已, 亦即只在發生錯誤時才會傳回 Error 物件, 否則不傳回任何值. 例如 :
//fs_test_6.js
var fs=require("fs");
var data="Hello\r\nTony"; //在 Windows 中跳行須用 \r\n
fs.writeFile("./test.txt", data, "UTF8", function(err) {
if (err) throw err;
console.log("檔案寫入操作完成!");
})
console.log("檔案寫入操作中 ... ");
開啟 test.txt 內容如下 :
Hello
Tony
可見確實有跳行. 寫入資料也可以使用 Buffer 物件, 例如 :
//fs_test_7.js
var fs=require("fs");
var data=new Buffer("Hello\r\nTony"); //使用 Buffer 物件
fs.writeFile("./test.txt", data, "UTF8", function(err) {
if (err) throw err;
console.log("檔案寫入操作完成!");
})
console.log("檔案寫入操作中 ... ");
上面的 writeFile() 與 writeFileSync() 函數會覆蓋檔案內原來的內容, 如果不希望覆蓋, 而是希望將內容附加在原來內容後面就要使用下面的 appendFile() 與 appendFileSync() 函數.
3. 追加寫入檔案 (Append) :
非同步 : fs.appendFile(file, data[, options], callback)
同步 : fs.appendFileync(file, data[, options])
參數用法同 writeFile() 與 writeFileSync(). 例如 :
//fs_test_8.js
var fs=require("fs");
var data="Hello\r\nWorld";
fs.appendFile("./test.txt", data, "UTF8", function(err) { //只有一個參數 err
if (err) throw err;
console.log("檔案附加寫入操作完成!");
})
console.log("檔案附加寫入操作中 ... ");
執行結果 :
D:\Node.js\test>node fs_test_8.js
檔案附加寫入操作中 ...
檔案附加寫入操作完成!
開啟 test.txt 內容如下 :
Hello
TonyHello
World
因為上面寫入 "Hello\r\nTony" 結尾沒跳行, 因此 "Hello\r\nWorld" 就黏在 Tony 後面了.
4. 讀取檔案目錄 :
非同步 : fs.readdir(path[, options], callback)
同步 : fs.readdirSync(path[, options])
此函數會讀取指定目錄下的所有檔案名稱 (只限檔案, 不包含子目錄名稱), 然後放在 files 陣列中傳回. 第一參數 path 為包含目錄名稱的路徑, 例如程式所在位置的子目錄 "tmp", 根目錄 "/", 上一層目錄 "../" 等. 備選參數 options 可以是物件 (僅 encoding) 或字串 (僅表示 encoding, 預設為 'utf8'). 非同步回呼函數 callback 有兩個傳入參數 : err 與 files, 讀取到的檔案列表會放在 files 陣列中, 例如 :
//fs_test_9.js
var fs=require("fs");
fs.readdir("/Node.js", function(err, files) { //讀取根目錄下子目錄 Node.js 的檔案列表
if (err) throw err;
console.log(files);
})
console.log("讀取目錄操作中 ... ");
執行結果如下 :
D:\Node.js\test>node fs_test_9.js
讀取目錄操作中 ...
[ 'app.nw',
'arduino 當機事件.txt',
'duration.js',
'ebook',
'hello.js',
'http_1.js',
'index.html',
'node.exe',
'Node.js.zip',
'nw.exe',
'package.json',
'process_argv.js',
'serial.js',
'serialport.js',
'server.js',
'test',
'露天.txt' ]
5. 建立目錄 :
非同步 : fs.mkdir(path[, mode], callback)
同步 : fs.mkdirSync(path[, mode])
此函數用來建立目錄. 備選參數 mode 為一整數, 用來設定新建目錄之存取權限, 預設為 0o777. 注意, 非同步之回呼函數只有一個參數 err. 例如 :
//fs_test_10.js
var fs=require("fs");
fs.mkdir("./tmp", function(err) {
if (err) throw err;
console.log("新建目錄操作完成");
})
console.log("新建目錄操作中 ... ");
D:\Node.js\test>node fs_test_10.js
新建目錄操作中 ...
新建目錄操作完成
檢查目前目錄下確實多了一個 tmp 子目錄.
6. 更改檔案或目錄名稱 :
非同步 : fs.rename(oldPath, newPath, callback)
同步 : fs.renameSync(oldPath, newPath)
此函數用來更改檔案或名稱, 前兩個參數為包含路徑之舊與新檔案或目錄名稱. 注意, 非同步之回呼函數只有一個參數 err. 例如 :
//fs_test_11.js
var fs=require("fs");
fs.rename("test.txt", "test_new.txt", function(err) {
if (err) throw err;
console.log("檔案更名操作完成");
})
console.log("檔案更名操作中 ... ");
執行結果如下 :
D:\Node.js\test>node fs_test_11.js
檔案更名操作中 ...
檔案更名操作完成
檢查確定 test.txt 已經被改成 test_new.txt 了.
函數 rename() 也可以更改目錄名稱 (但目前程式所在目錄因為被鎖定無法更名), 例如在上面範例中用 fs.mkdir() 所建立的子目錄 tmp 可以利用 rename() 將其改為 tmp_new, 例如 :
//fs_test_12.js
var fs=require("fs");
fs.rename("./tmp", "./tmp_new", function(err) { //更改目錄名稱
if (err) throw err;
console.log("目錄更名操作完成");
})
console.log("目錄更名操作中 ... ");
執行結果如下 :
D:\Node.js\test>node fs_test_12.js
目錄更名操作中 ...
目錄更名操作完成
檢查目前目錄下之子目錄 tmp 確實被改為 tmp_new 了.
7. 刪除目錄 :
非同步 : fs.rmdir(path, callback)
同步 : fs.rmdirSync(path)
此函數用來建立目錄. 備選參數 mode 為一整數, 用來設定新建目錄之存取權限, 預設為 0o777. 注意, 非同步之回呼函數只有一個參數 err.
在上面範例中我們用 fs.rename() 將子目錄 tmp 更名為 tmp_new, 此處可用 fs.rmdir() 將其刪除, 例如 :
//fs_test_13.js
var fs=require("fs");
fs.rmdir("./tmp_new", function(err) {
if (err) throw err;
console.log("刪除目錄操作完成");
})
console.log("刪除目錄操作中 ... ");
D:\Node.js\test>node fs_test_13.js
刪除目錄操作中 ...
刪除目錄操作完成
檢查目前目錄下確實已無 tmp_new 這個子目錄了.
7. 修改檔案長度 :
非同步 : fs.truncate(path[, len], callback)
同步 : fs.truncateSync(path[, len])
此函數用來削減檔案長度. 第一參數 path 為包含路徑之檔案名稱, 備選參數 len 為新檔案內容之長度 (bytes), 預設為 0 (即全部內容刪除), 第三參數 callback 為回呼函數, 只有一個 err 參數.
我先在目前目錄下編輯 test.txt 檔案, 內容為 "你好啊!Tony". 然後執行下列程式 :
//fs_test_14.js
var fs=require("fs");
fs.truncate("./test.txt", function(err) {
if (err) throw err;
console.log("檔案長度修改操作完成!");
})
console.log("檔案長度修改操作中 ... ");
//fs_test_15.js
var fs=require("fs");
fs.truncate("./test.txt", 8, function(err) {
if (err) throw err;
console.log("檔案長度修改操作完成!");
})
console.log("檔案長度修改操作中 ... ");
執行後開啟 test.txt, 發現裡面內容只剩下 "你好啊!T", 這是因為中文字型佔 2 個 byte, 前面三個中文字 "你好啊" 佔了 6 個 bytes, 後面再取 2 個 bytes "!T" 就成為 "你好啊!T" 了.
另外若檔案內容有跳行, fs.truncate() 還是可以運作, 但要注意, 在 Windows 中跳行字元 "\r\n" 佔 2 個 bytes, 例如將 test.txt 內容改為如下兩行內容的話 :
Hello
World
執行上面的程式擷取前面 8 個 bytes 會得到如下結果 :
Hello
W
這是因為第一行 "Hello\r\n" 佔了 7 個字元, 接著到下一行取 "W" 即 8 個 bytes 了. 將上面的測試寫成自動檢視檔案內容的程式如下, 使用非同步寫法必須將下一個要做的動作放在回呼函數, 因此會形成所謂的 "回呼地獄", 比較不好閱讀 :
//fs_test_16.js
var fs=require("fs");
var data="Hello\r\nWorld"; //檔案初始內容
fs.writeFile("./test.txt", data, "UTF8", function(err) { //寫入檔案
if (err) throw err;
console.log("檔案寫入操作完成!");
fs.readFile("./test.txt", "UTF8", function(err, data) { //讀取檔案內容
if (err) throw err;
console.log("檔案讀取操作完成!");
console.log("檔案內容 : \n" + data);
fs.truncate("./test.txt", 8, function(err) { //擷取檔案前 8 個 bytes
if (err) throw err;
console.log("檔案長度修改操作完成!");
fs.readFile("./test.txt", "UTF8", function(err, data) { //讀取檔案內容
if (err) throw err;
console.log("檔案讀取操作完成!");
console.log("檔案內容 : \n" + data);
});
console.log("檔案讀取操作中 ... ");
});
console.log("檔案長度修改操作中 ... ");
});
console.log("檔案讀取操作中 ... ");
});
console.log("檔案寫入操作中 ... ");
執行結果如下 :
D:\Node.js\test>node fs_test_16.js
檔案寫入操作中 ...
檔案寫入操作完成!
檔案讀取操作中 ...
檔案讀取操作完成!
檔案內容 :
Hello
World
檔案長度修改操作中 ...
檔案長度修改操作完成!
檔案讀取操作中 ...
檔案讀取操作完成!
檔案內容 :
Hello
W
上面程式的同步版如下 :
//fs_test_17.js
var fs=require("fs");
var data="Hello\r\nWorld";
try { //寫入檔案
fs.writeFileSync("./test.txt", data);
console.log("檔案寫入操作完成!");
}
catch (err) {console.error(err);}
try { //讀取檔案
var file=fs.readFileSync("./test.txt", 'utf8', data);
console.log("檔案讀取操作完成!");
console.log("檔案內容 : \n" + file);
}
catch (err) {console.error(err);}
try { //變更檔案長度
var data=fs.truncateSync("./test.txt", 8);
console.log("檔案長度修改完成!");
}
catch (err) {console.error(err);}
try { //讀取檔案
var file=fs.readFileSync("./test.txt", 'utf8', data);
console.log("檔案讀取操作完成!");
console.log("檔案內容 : \n" + file);
}
catch (err) {console.error(err);}
執行結果是一樣的 :
D:\Node.js\test>node fs_test_17.js
檔案寫入操作完成!
檔案讀取操作完成!
檔案內容 :
Hello
World
檔案長度修改完成!
檔案讀取操作完成!
檔案內容 :
Hello
W
8. 取得檔案或目錄資訊 :
非同步 : fs.stat(path, callback)
同步 : fs.statSync(path)
此函數用來取得檔案或目錄之資訊. 第一參數 path 為包含路徑之檔案或目錄名稱, 第二參數 callback 為回呼函數, 有 err 與 ststs 兩個參數, 其中 stats 為查詢所得之檔案或目錄資訊, 為一 fs.Stats 物件, 可用迴圈列舉其 key, value 以檢視物件內容, 例如 :
var fs=require("fs");
fs.stat("./test.txt", function(err, stats) {
if (err) throw err;
console.log("檔案資訊取得操作完成!");
console.log("檔案資訊 :\n");
for(var k in stats) {
console.log(k + ":" + stats[k]);
}
});
console.log("檔案資訊取得操作中 ... ");
執行結果如下 :
D:\Node.js\test>node fs_test_18.js
檔案資訊取得操作中 ...
檔案資訊取得操作完成!
檔案資訊 :
dev:2089136718
mode:33206
nlink:1
uid:0
gid:0
rdev:0
blksize:undefined
ino:562949954052903
size:138
blocks:undefined
atimeMs:1522111655025.0005
mtimeMs:1522202247354.802
ctimeMs:1522202247354.802
birthtimeMs:1521426043495.4006
atime:Tue Mar 27 2018 08:47:35 GMT+0800 (台北標準時間)
mtime:Wed Mar 28 2018 09:57:27 GMT+0800 (台北標準時間)
ctime:Wed Mar 28 2018 09:57:27 GMT+0800 (台北標準時間)
birthtime:Mon Mar 19 2018 10:20:43 GMT+0800 (台北標準時間)
_checkModeProperty:function (property) {
return ((this.mode & S_IFMT) === property);
}
isDirectory:function () {
return this._checkModeProperty(constants.S_IFDIR);
}
isFile:function () {
return this._checkModeProperty(S_IFREG);
}
isBlockDevice:function () {
return this._checkModeProperty(constants.S_IFBLK);
}
isCharacterDevice:function () {
return this._checkModeProperty(constants.S_IFCHR);
}
isSymbolicLink:function () {
return this._checkModeProperty(S_IFLNK);
}
isFIFO:function () {
return this._checkModeProperty(S_IFIFO);
}
isSocket:function () {
return this._checkModeProperty(S_IFSOCK);
}
這裡面 uid (使用者 ID), gid (群組 ID) 等資訊在 Linux 較常用. fs.stat() 也可以查詢目錄資訊, 例如目前目錄底下的空子目錄 tmp :
//fs_test_19.js
var fs=require("fs");
fs.stat("./tmp", function(err, stats) { //空的子目錄
if (err) throw err;
console.log("檔案資訊取得操作完成!");
console.log("檔案資訊 :\n");
for(var k in stats) {
console.log(k + ":" + stats[k]);
}
});
console.log("檔案資訊取得操作中 ... ");
執行結果如下 :
D:\Node.js\test>node fs_test.js
檔案資訊取得操作中 ...
檔案資訊取得操作完成!
檔案資訊 :
dev:2089136718
mode:16822
nlink:1
uid:0
gid:0
rdev:0
blksize:undefined
ino:281474977342527
size:0
blocks:undefined
atimeMs:1522135068643.0005
mtimeMs:1522135068643.0005
ctimeMs:1522135068643.0005
birthtimeMs:1522135068643.0005
atime:Tue Mar 27 2018 15:17:48 GMT+0800 (台北標準時間)
mtime:Tue Mar 27 2018 15:17:48 GMT+0800 (台北標準時間)
ctime:Tue Mar 27 2018 15:17:48 GMT+0800 (台北標準時間)
birthtime:Tue Mar 27 2018 15:17:48 GMT+0800 (台北標準時間)
_checkModeProperty:function (property) {
return ((this.mode & S_IFMT) === property);
}
isDirectory:function () {
return this._checkModeProperty(constants.S_IFDIR);
}
isFile:function () {
return this._checkModeProperty(S_IFREG);
}
isBlockDevice:function () {
return this._checkModeProperty(constants.S_IFBLK);
}
isCharacterDevice:function () {
return this._checkModeProperty(constants.S_IFCHR);
}
isSymbolicLink:function () {
return this._checkModeProperty(S_IFLNK);
}
isFIFO:function () {
return this._checkModeProperty(S_IFIFO);
}
isSocket:function () {
return this._checkModeProperty(S_IFSOCK);
}
9. 取得絕對路徑 :
非同步 : fs.realpath(path[, options], callback)
此函數用來取得檔案或目錄之絕對路徑. 第一參數 path 為包含路徑之檔案或目錄名稱, 備選參數 options 為一字串或物件, 用來指定檔案內容之編碼 encoding, 預設為 utf8. 第三參數 callback 為回呼函數, 只有一個 err 參數. 例如 :
//fs_test_20.js
var fs=require("fs");
fs.realpath("./test.txt", function(err, resolvedPath) {
if (err) throw err;
console.log("絕對路徑取得操作完成!");
console.log("相對路徑 : ./test.txt");
console.log("絕對路徑 : " + resolvedPath);
});
console.log("絕對路徑取得操作中 ... ");
執行結果 :
D:\Node.js\test>node fs_test_20.js
絕對路徑取得操作中 ...
絕對路徑取得操作完成!
相對路徑 : ./test.txt
絕對路徑 : D:\Node.js\test\test.txt
查詢目錄之絕對路徑 :
//fs_test_21.js
var fs=require("fs");
fs.realpath("./tmp", function(err, resolvedPath) {
if (err) throw err;
console.log("絕對路徑取得操作完成!");
console.log("相對路徑 : ./tmp");
console.log("絕對路徑 : " + resolvedPath);
});
console.log("絕對路徑取得操作中 ... ");
執行結果 :
D:\Node.js\test>node fs_test_21.js
絕對路徑取得操作中 ...
絕對路徑取得操作完成!
相對路徑 : ./tmp
絕對路徑 : D:\Node.js\test\tmp
10. 開啟與關閉檔案 :
除了上述的高階檔案操作外, 有時需進行低階操作, 這時需要用到 fs.open() 所傳回之檔案描述子 (file descriptor), 這是一個參照或索引, 作業系統內核用它來指向所開啟檔案之檔案紀錄表. Linux 的系統呼叫大都依賴檔案描述子. 使用 fs.open() 操作檔案完畢後, 須使用 fs.close() 予以關閉. 關於檔案描述子參考 :
# https://zh.wikipedia.org/wiki/文件描述符
非同步 : fs.open(path, flags[, mode], callback)
同步 : fs.openSync(path, flags[, mode])
此函數用來開啟檔案, 第一參數 path 為包含路徑之檔案或目錄名稱, 第二參數 flags 旗標字串設定檔案操作模式 :
備選參數 mode 為表示權限之整數, 預設為 0o666. 最後一個參數 callback 為回呼函數, 有兩個參數 err 與 fd, 其中 fd 即為傳回之檔案描述子 (整數).
關閉檔案 :
非同步 : fs.close(fd, callback)
此函數用來關閉檔案, 第一參數 fd 為檔案描述子 (整數), 參數 callback 為回呼函數, 只有一個參數 err. 例如 :
//fs_test_22.js
var fs=require("fs");
fs.open("./test.txt", 'a', function(err, fd) { //開啟檔案
if (err) throw err;
console.log("檔案開啟操作完成!");
console.log("檔案描述表 : " + fd);
fs.close(fd, function(err) { //關閉檔案
if (err) throw err;
console.log("檔案關閉操作完成!");
});
console.log("檔案關閉操作中 ... ");
})
console.log("檔案開啟操作中 ... ");
在 Windows 執行結果如下 :
D:\Node.js\test>node fs_test.js
檔案開啟操作中 ...
檔案開啟操作完成!
檔案描述表 : 3
檔案關閉操作中 ...
檔案關閉操作完成!
在 Windows 不管是哪個檔案都是傳回 3.
上面提到 fs.stat(), fs.truncate(), fs.chown(), fs.chmod(), fs.utimes() 等函數有 fd 版本之函數, 即函數名稱前面冠 'f' : fs.fstat(), fs.truncate(), fs.fchown(), fs.fchmod(), fs.futimes() 等. 例如可使用檔案描述子與 fs.fstat() 顯示檔案資訊 :
非同步 : fs.fstat(fd, callback)
其中第一參數 fd 為檔案描述子, 參數 callback 為有 err 與 stats 參數之回呼函數. stats 為傳回之 fs.Stats 物件.
將上面 fs.stat() 的範例修改為如下的 fs.fstat() 版 :
//fs_test_23.js
var fs=require("fs");
fs.open("./test.txt", 'a', function(err, fd) {
if (err) throw err;
console.log("檔案開啟操作完成!");
console.log("檔案描述表 : " + fd);
fs.fstat(fd, function(err, stats) {
if (err) throw err;
console.log("檔案資訊取得操作完成!");
console.log("檔案資訊 :\n");
for(var k in stats) {
console.log(k + ":" + stats[k]);
}
fs.close(fd, function(err) {
if (err) throw err;
console.log("檔案關閉操作完成!");
});
console.log("檔案關閉操作中 ... ");
});
console.log("檔案資訊取得操作中 ... ");
})
console.log("檔案開啟操作中 ... ");
執行結果如下 :
D:\Node.js\test>node fs_test_23.js
檔案開啟操作中 ...
檔案開啟操作完成!
檔案描述表 : 3
檔案資訊取得操作中 ...
檔案資訊取得操作完成!
檔案資訊 :
dev:2089136718
mode:33206
nlink:1
uid:0
gid:0
rdev:0
blksize:undefined
ino:562949954052903
size:15
blocks:undefined
atimeMs:1522111655025.0005
mtimeMs:1522227853515.802
ctimeMs:1522227853515.802
birthtimeMs:1521426043495.4006
atime:Tue Mar 27 2018 08:47:35 GMT+0800 (台北標準時間)
mtime:Wed Mar 28 2018 17:04:13 GMT+0800 (台北標準時間)
ctime:Wed Mar 28 2018 17:04:13 GMT+0800 (台北標準時間)
birthtime:Mon Mar 19 2018 10:20:43 GMT+0800 (台北標準時間)
_checkModeProperty:function (property) {
return ((this.mode & S_IFMT) === property);
}
isDirectory:function () {
return this._checkModeProperty(constants.S_IFDIR);
}
isFile:function () {
return this._checkModeProperty(S_IFREG);
}
isBlockDevice:function () {
return this._checkModeProperty(constants.S_IFBLK);
}
isCharacterDevice:function () {
return this._checkModeProperty(constants.S_IFCHR);
}
isSymbolicLink:function () {
return this._checkModeProperty(S_IFLNK);
}
isFIFO:function () {
return this._checkModeProperty(S_IFIFO);
}
isSocket:function () {
return this._checkModeProperty(S_IFSOCK);
}
檔案關閉操作中 ...
檔案關閉操作完成!
11. 使用 fs.read() 操作低階檔案讀取 :
除了上面的 fs.readFile() 高階檔案讀取操作外, 也可以使用檔案描述子與 Buffer 物件以 fs.read() 進行低階檔案讀取操作, 例如指定讀取位置與讀取多少 bytes.
非同步 : fs.read(fd, buffer, offset, length, position, callback)
第一參數 fd 為 fs.open() 傳回之檔案描述子; 第二參數 buffer 為儲存讀取內容之 Buffer 物件; 第三參數 offset 是讀取後寫入 Buffer 中的偏移位置 (0 起始之整數); 第四參數 length 指定要從檔案中讀取幾個 byte; 第五參數 position 用來指定要從檔案的哪一個位置開始讀取內容, 如果 position=null 表示從檔案指位器目前位置開始讀取; 第六參數 callback 為包含 err, bytesRead, 以及 buffer 三個參數之回呼函數, 其中 bytesRead 為讀取之 Bytes 數, 而 buffer 為 Buffer 物件.
在下面範例中, 我先在檔案 test.txt 中準備好 "Hello\r\nWorld" 這兩行內容供 fs.read() 讀取, 而且以 ASCII 格式存檔 :
//fs_test_24.js
var fs=require("fs");
fs.open("./test.txt", "r", function(err, fd) {
if (err) throw err;
console.log("檔案開啟操作完成!");
console.log("檔案描述子 : " + fd);
var buf=new Buffer(8); //儲存要讀取的 8 個 bytes
fs.read(fd, buf, 0, 8, 0, function(err, bytesRead, buffer) { //從檔案頭開始讀 8 個 bytes
if (err) throw err;
console.log("檔案讀取操作完成!");
console.log("讀取 Bytes 數 : " + bytesRead);
console.log("讀取內容 : " + buffer);
});
console.log("檔案讀取操作中 ... ");
});
console.log("檔案開啟操作中 ... ");
此程式中我們先建立一個長度為 8 的 Buffer 物件來儲存要讀取之 8 個 bytes 資料, 然後將此 Buffer 物件傳入 fs.read() 中, 讀取完成後, 此 Buffer 物件會被傳入回呼函數的參數 buffer, 讀取到的 Bytes 數則傳給 BytesRead 參數, 讀取到的資料為 "Hello\r\nW" :
D:\Node.js\test>node fs_test_24.js
檔案開啟操作中 ...
檔案開啟操作完成!
檔案描述子 : 3
檔案讀取操作中 ...
檔案讀取操作完成!
讀取 Bytes 數 : 8
讀取內容 : Hello
W
如果改為 fs.read(fd, buf, 0, 8, 3, ... 的話, 就會從 test.txt 的第 3 個 byte 開始讀取, 讀到的資料為 "lo\r\nWorld" 共 8 個 Bytes :
D:\Node.js\test>node fs_test_24.js
檔案開啟操作中 ...
檔案開啟操作完成!
檔案描述子 : 3
檔案讀取操作中 ...
檔案讀取操作完成!
讀取 Bytes 數 : 8
讀取內容 : lo
Worl
12. 使用 fs.write() 操作低階檔案寫入 :
除了上面的 fs.writeFile() 高階檔案寫入操作外, 也可以使用檔案描述子與 Buffer 物件以 fs.write() 進行低階檔案寫入操作, 例如指定寫入位置與寫入多少 bytes.
非同步 : fs.write(fd, buffer[, offset[, length[, position]]], callback)
此函數用來開啟檔案, 第一參數 path 為包含路徑之檔案或目錄名稱, 第二參數 flags 旗標字串設定檔案操作模式 :
flag | 說明 |
r | 讀取, 若檔案不存在則拋出例外 |
r+ | 讀取 + 寫入, 若檔案不存在則拋出例外 |
rs+ | 同步模式之讀取 + 寫入 |
w | 寫入, 若檔案不存在則建立, 否則覆蓋 |
wx | 寫入, 若檔案不存在則拋出例外 |
w+ | 讀取 + 寫入, 若檔案不存在則建立, 否則覆蓋 |
wx+ | 讀取 + 寫入, 若檔案不存在則拋出例外 |
a | 附加寫入, 若檔案不存在則建立 |
ax | 附加寫入, 若檔案不存在則拋出例外 |
a+ | 讀取 + 附加寫入, 若檔案不存在則建立 |
ax+ | 讀取 + 附加寫入, 若檔案不存在則拋出例外 |
備選參數 mode 為表示權限之整數, 預設為 0o666. 最後一個參數 callback 為回呼函數, 有兩個參數 err 與 fd, 其中 fd 即為傳回之檔案描述子 (整數).
關閉檔案 :
非同步 : fs.close(fd, callback)
同步 : fs.closeSync(fd)
此函數用來關閉檔案, 第一參數 fd 為檔案描述子 (整數), 參數 callback 為回呼函數, 只有一個參數 err. 例如 :
//fs_test_22.js
var fs=require("fs");
fs.open("./test.txt", 'a', function(err, fd) { //開啟檔案
if (err) throw err;
console.log("檔案開啟操作完成!");
console.log("檔案描述表 : " + fd);
fs.close(fd, function(err) { //關閉檔案
if (err) throw err;
console.log("檔案關閉操作完成!");
});
console.log("檔案關閉操作中 ... ");
})
console.log("檔案開啟操作中 ... ");
在 Windows 執行結果如下 :
D:\Node.js\test>node fs_test.js
檔案開啟操作中 ...
檔案開啟操作完成!
檔案描述表 : 3
檔案關閉操作中 ...
檔案關閉操作完成!
在 Windows 不管是哪個檔案都是傳回 3.
上面提到 fs.stat(), fs.truncate(), fs.chown(), fs.chmod(), fs.utimes() 等函數有 fd 版本之函數, 即函數名稱前面冠 'f' : fs.fstat(), fs.truncate(), fs.fchown(), fs.fchmod(), fs.futimes() 等. 例如可使用檔案描述子與 fs.fstat() 顯示檔案資訊 :
非同步 : fs.fstat(fd, callback)
同步 : fs.fstatSync(fd)
其中第一參數 fd 為檔案描述子, 參數 callback 為有 err 與 stats 參數之回呼函數. stats 為傳回之 fs.Stats 物件.
將上面 fs.stat() 的範例修改為如下的 fs.fstat() 版 :
//fs_test_23.js
var fs=require("fs");
fs.open("./test.txt", 'a', function(err, fd) {
if (err) throw err;
console.log("檔案開啟操作完成!");
console.log("檔案描述表 : " + fd);
fs.fstat(fd, function(err, stats) {
if (err) throw err;
console.log("檔案資訊取得操作完成!");
console.log("檔案資訊 :\n");
for(var k in stats) {
console.log(k + ":" + stats[k]);
}
fs.close(fd, function(err) {
if (err) throw err;
console.log("檔案關閉操作完成!");
});
console.log("檔案關閉操作中 ... ");
});
console.log("檔案資訊取得操作中 ... ");
})
console.log("檔案開啟操作中 ... ");
執行結果如下 :
D:\Node.js\test>node fs_test_23.js
檔案開啟操作中 ...
檔案開啟操作完成!
檔案描述表 : 3
檔案資訊取得操作中 ...
檔案資訊取得操作完成!
檔案資訊 :
dev:2089136718
mode:33206
nlink:1
uid:0
gid:0
rdev:0
blksize:undefined
ino:562949954052903
size:15
blocks:undefined
atimeMs:1522111655025.0005
mtimeMs:1522227853515.802
ctimeMs:1522227853515.802
birthtimeMs:1521426043495.4006
atime:Tue Mar 27 2018 08:47:35 GMT+0800 (台北標準時間)
mtime:Wed Mar 28 2018 17:04:13 GMT+0800 (台北標準時間)
ctime:Wed Mar 28 2018 17:04:13 GMT+0800 (台北標準時間)
birthtime:Mon Mar 19 2018 10:20:43 GMT+0800 (台北標準時間)
_checkModeProperty:function (property) {
return ((this.mode & S_IFMT) === property);
}
isDirectory:function () {
return this._checkModeProperty(constants.S_IFDIR);
}
isFile:function () {
return this._checkModeProperty(S_IFREG);
}
isBlockDevice:function () {
return this._checkModeProperty(constants.S_IFBLK);
}
isCharacterDevice:function () {
return this._checkModeProperty(constants.S_IFCHR);
}
isSymbolicLink:function () {
return this._checkModeProperty(S_IFLNK);
}
isFIFO:function () {
return this._checkModeProperty(S_IFIFO);
}
isSocket:function () {
return this._checkModeProperty(S_IFSOCK);
}
檔案關閉操作中 ...
檔案關閉操作完成!
11. 使用 fs.read() 操作低階檔案讀取 :
除了上面的 fs.readFile() 高階檔案讀取操作外, 也可以使用檔案描述子與 Buffer 物件以 fs.read() 進行低階檔案讀取操作, 例如指定讀取位置與讀取多少 bytes.
第一參數 fd 為 fs.open() 傳回之檔案描述子; 第二參數 buffer 為儲存讀取內容之 Buffer 物件; 第三參數 offset 是讀取後寫入 Buffer 中的偏移位置 (0 起始之整數); 第四參數 length 指定要從檔案中讀取幾個 byte; 第五參數 position 用來指定要從檔案的哪一個位置開始讀取內容, 如果 position=null 表示從檔案指位器目前位置開始讀取; 第六參數 callback 為包含 err, bytesRead, 以及 buffer 三個參數之回呼函數, 其中 bytesRead 為讀取之 Bytes 數, 而 buffer 為 Buffer 物件.
在下面範例中, 我先在檔案 test.txt 中準備好 "Hello\r\nWorld" 這兩行內容供 fs.read() 讀取, 而且以 ASCII 格式存檔 :
//fs_test_24.js
var fs=require("fs");
fs.open("./test.txt", "r", function(err, fd) {
if (err) throw err;
console.log("檔案開啟操作完成!");
console.log("檔案描述子 : " + fd);
var buf=new Buffer(8); //儲存要讀取的 8 個 bytes
fs.read(fd, buf, 0, 8, 0, function(err, bytesRead, buffer) { //從檔案頭開始讀 8 個 bytes
if (err) throw err;
console.log("檔案讀取操作完成!");
console.log("讀取 Bytes 數 : " + bytesRead);
console.log("讀取內容 : " + buffer);
});
console.log("檔案讀取操作中 ... ");
});
console.log("檔案開啟操作中 ... ");
此程式中我們先建立一個長度為 8 的 Buffer 物件來儲存要讀取之 8 個 bytes 資料, 然後將此 Buffer 物件傳入 fs.read() 中, 讀取完成後, 此 Buffer 物件會被傳入回呼函數的參數 buffer, 讀取到的 Bytes 數則傳給 BytesRead 參數, 讀取到的資料為 "Hello\r\nW" :
D:\Node.js\test>node fs_test_24.js
檔案開啟操作中 ...
檔案開啟操作完成!
檔案描述子 : 3
檔案讀取操作中 ...
檔案讀取操作完成!
讀取 Bytes 數 : 8
讀取內容 : Hello
W
如果改為 fs.read(fd, buf, 0, 8, 3, ... 的話, 就會從 test.txt 的第 3 個 byte 開始讀取, 讀到的資料為 "lo\r\nWorld" 共 8 個 Bytes :
D:\Node.js\test>node fs_test_24.js
檔案開啟操作中 ...
檔案開啟操作完成!
檔案描述子 : 3
檔案讀取操作中 ...
檔案讀取操作完成!
讀取 Bytes 數 : 8
讀取內容 : lo
Worl
12. 使用 fs.write() 操作低階檔案寫入 :
除了上面的 fs.writeFile() 高階檔案寫入操作外, 也可以使用檔案描述子與 Buffer 物件以 fs.write() 進行低階檔案寫入操作, 例如指定寫入位置與寫入多少 bytes.
同步 : fs.writeSync(fd, buffer[, offset[, length[, position]]])
第一參數 fd 為 fs.open() 傳回之檔案描述子; 第二參數 buffer 為儲存欲寫入內容之 Buffer 物件; 第三參數 offset 是要從 Buffer 中的哪一個偏移位置開始寫入 (0 起始之整數); 第四參數 length 指定要從 Buffer 物件中讀取幾個 byte 寫入檔案; 第五參數 position 用來指定要從檔案的哪一個位置開始寫入 Buffer 內容, 如果 position=null 表示從檔案指位器目前位置開始讀取; 第六參數 callback 為包含 err, bytesWritten, 以及 buffer 三個參數之回呼函數, 其中 bytesWritten 為寫入檔案之 Bytes 數, 而 buffer 為 Buffer 物件.
第一參數 fd 為 fs.open() 傳回之檔案描述子; 第二參數 buffer 為儲存欲寫入內容之 Buffer 物件; 第三參數 offset 是要從 Buffer 中的哪一個偏移位置開始寫入 (0 起始之整數); 第四參數 length 指定要從 Buffer 物件中讀取幾個 byte 寫入檔案; 第五參數 position 用來指定要從檔案的哪一個位置開始寫入 Buffer 內容, 如果 position=null 表示從檔案指位器目前位置開始讀取; 第六參數 callback 為包含 err, bytesWritten, 以及 buffer 三個參數之回呼函數, 其中 bytesWritten 為寫入檔案之 Bytes 數, 而 buffer 為 Buffer 物件.
下列範例使用 fs.write() 將儲存於 Buffer 物件中的 "Hello\r\nTony" 字串寫入 test.txt 中 :
//fs_test_25.js
var fs=require("fs");
fs.open("./test.txt", "r+", function(err, fd) { //以讀取+寫入模式開啟
if (err) return console.log(err);
console.log("檔案開啟操作完成!");
console.log("檔案描述子 : " + fd);
var buf=new Buffer("Hello\r\nTony");
fs.write(fd, buf, 0, buf.length, 0, function(err, bytesWritten, buffer) {
if (err) return console.log(err);
console.log("檔案寫入操作完成!");
console.log("寫入 Bytes 數 : " + bytesWritten);
console.log("寫入內容 : " + buffer);
});
console.log("檔案寫入操作中 ... ");
});
console.log("檔案開啟操作中 ... ");
執行結果如下 :
D:\Node.js\test>node fs_test_25.js
檔案開啟操作中 ...
檔案開啟操作完成!
檔案描述子 : 3
檔案寫入操作中 ...
檔案寫入操作完成!
寫入 Bytes 數 : 11
寫入內容 : Hello
Tony
注意, 若以 "r" 唯讀模式開啟檔案, 執行時將出現如下 "EPERM: operation not permitted" 錯誤 :
D:\Node.js\test>node fs_test_25.js
檔案開啟操作中 ...
檔案開啟操作完成!
檔案描述子 : 3
檔案寫入操作中 ...
{ Error: EPERM: operation not permitted, write errno: -4048, code: 'EPERM', syscall: 'write' }
fs.write() 還有另一個使用字串而非 Buffer 物件之多型函數 :
fs.write() 還有另一個使用字串而非 Buffer 物件之多型函數 :
非同步 : fs.write(fd, string[, position[, encoding]], callback)
同步 : fs.writeSync(fd, string[, position[, encoding]])
其中回呼函數 callback 之參數有 3 : err, written (寫入 bytes 數), string (寫入字串).
//fs_test_26.js
var fs=require("fs");
fs.open("./test.txt", "r+", function(err, fd) {
if (err) return console.log(err);
console.log("檔案開啟操作完成!");
console.log("檔案描述子 : " + fd);
var str="Hello\r\nTony";
fs.write(fd, str, "utf8", function(err, written, string) {
if (err) return console.log(err);
console.log("檔案寫入操作完成!");
console.log("寫入 Bytes 數 : " + written);
console.log("寫入內容 : " + string);
});
console.log("檔案寫入操作中 ... ");
});
console.log("檔案開啟操作中 ... ");
執行結果同上.
13. 使用 fs.createReadStream() 操作低階檔案讀取 :
除了 fs.read() 外, fs.createReadStream() 也可以操作低階檔案讀取, 此函數沒有回呼函數, 須掛上readable 事件監聽器, 其 API 如下 :
fs.createReadStream(path[, options])
第一參數 path 為包含路徑之檔案或目錄名稱; 備選參數 options 包含如下選項 :
flags <string> : 讀寫旗標
encoding <string> : 編碼
fd <integer> : 檔案描述器
mode <integer> : 模式
autoClose <boolean> : 自動關閉
start <integer> : 讀取開始索引
end <integer> : 讀取結束索引
//fs_test_26.js
var fs=require("fs");
var rs=fs.createReadStream("./test.txt",{encoding:'utf8'});
console.log("檔案讀取操作中 ...");
rs.on("readable", function() {
var chunk=rs.read();
console.log(chunk);
});
rs.on("end", function() {
console.log("檔案讀取操作完成!");
});
執行結果如下 :
D:\Node.js\test>node fs_test_stream.js
檔案讀取操作中 ...
Hello
Tony
null
檔案讀取操作完成!
同步 : fs.writeSync(fd, string[, position[, encoding]])
其中回呼函數 callback 之參數有 3 : err, written (寫入 bytes 數), string (寫入字串).
//fs_test_26.js
var fs=require("fs");
fs.open("./test.txt", "r+", function(err, fd) {
if (err) return console.log(err);
console.log("檔案開啟操作完成!");
console.log("檔案描述子 : " + fd);
var str="Hello\r\nTony";
fs.write(fd, str, "utf8", function(err, written, string) {
if (err) return console.log(err);
console.log("檔案寫入操作完成!");
console.log("寫入 Bytes 數 : " + written);
console.log("寫入內容 : " + string);
});
console.log("檔案寫入操作中 ... ");
});
console.log("檔案開啟操作中 ... ");
執行結果同上.
13. 使用 fs.createReadStream() 操作低階檔案讀取 :
除了 fs.read() 外, fs.createReadStream() 也可以操作低階檔案讀取, 此函數沒有回呼函數, 須掛上readable 事件監聽器, 其 API 如下 :
fs.createReadStream(path[, options])
第一參數 path 為包含路徑之檔案或目錄名稱; 備選參數 options 包含如下選項 :
flags <string> : 讀寫旗標
encoding <string> : 編碼
fd <integer> : 檔案描述器
mode <integer> : 模式
autoClose <boolean> : 自動關閉
start <integer> : 讀取開始索引
end <integer> : 讀取結束索引
//fs_test_26.js
var fs=require("fs");
var rs=fs.createReadStream("./test.txt",{encoding:'utf8'});
console.log("檔案讀取操作中 ...");
rs.on("readable", function() {
var chunk=rs.read();
console.log(chunk);
});
rs.on("end", function() {
console.log("檔案讀取操作完成!");
});
執行結果如下 :
D:\Node.js\test>node fs_test_stream.js
檔案讀取操作中 ...
Hello
Tony
null
檔案讀取操作完成!
沒有留言:
張貼留言