...
Node File System 모듈
파일시스템 모듈이란, 파일 처리와 관련된 작업을 하는 모듈로 보통 FileSystem을 줄여서 fs 모듈이라고 줄여 부릅니다.
노드에서 가장 많이 쓰이고 중요한 모듈 중 하나입니다.
fs 모듈에는 대부분의 메소드들이 동기/비동기로 나뉘는데, Sync라는 이름이 붙어있는 메소드가 동기방식을 사용한다고 보면 됩니다.
동기적 읽기 방식을 사용하면 파일을 읽으면서 다른 작업을 동시에 할 수 없습니다.
하지만 비동기적으로 읽으면 파일을 읽으면서 다른 작업도 동시에 수행할 수 있고, 파일을 다 읽으면 매개변수 callback으로 전달한 함수가 호출됩니다.
주로 비동기적 형식을 많이 사용하지만, 서버 시작 시 세팅 파일을 읽는 작업과 같이 동기적 형식이 더 적절한 경우도 있습니다.
노드에서 파일 다루는 전체 메소드는 다음 API 문서에서 확인할 수 있습니다.
파일 읽기/쓰기 메소드
메소드 이름 | 설명 |
readFile(filename, [encoding], [callback] | 비동기식 IO로 파일을 읽어 들인다. |
readFileSync(filename, [encoding]) | 동기식 IO로 파일을 읽어 들인다. |
writeFile(filename, data, encoding=’utf8’, [callback]) | 비동기식 IO로 파일을 쓴다. |
writeFileSync(filename, data, encoding=’utf8’) | 동기식 IO로 파일을 쓴다. |
먼저 readfile 메소드를 이용한 읽기 기능을 사용해보도록 하겠습니다.
파일을 읽어야 하기 때문에 text.txt라는 텍스트 파일을 코드를 실행할 main.js와 같은 폴더에 생성해주세요.
// text.txt 문서내용
Hello World !!
파일 읽기 - readfile
//main.js
var fs = require('fs');
// 비동기적 읽기
fs.readFile('text.txt', 'utf8', function(err, data) {
console.log('비동기적 읽기 ' + data);
});
// 동기적 읽기
var text = fs.readFileSync('text.txt', 'utf8');
console.log('동기적 읽기 ' + text);
실행 결과
> 동기적 읽기 Hello World !!
> 비동기적 읽기 Hello World !!
위 코드를 실행해보면 위에서 말했던 동기적/비동기적 방식의 특징을 알 수 있을 것입니다.
즉, 비동기적 읽기가 먼저 실행됨에도 불구하고 console에는 동기적 읽기가 먼저 출력됨을 볼 수 있습니다.
파일 쓰기 - writefile
// main.js
var fs = require('fs');
var data = 'This is a test text'; // 파일에 쓸 내용
fs.writeFile('text1.txt', data, 'utf8', function(err) {
console.log('비동기적 파일 쓰기 완료');
});
fs.writeFileSync('text2.txt', data, 'utf8');
console.log('동기적 파일 쓰기 완료');
쓰기도 마찬가지로, 아래 코드를 실행하면 '동기적 파일 쓰기 완료'가 먼저 출력될 것입니다.
파일 입출력은 다양한 원인으로 예외가 발생할 수 있으므로 파일 입출력을 하면서 중요한 부분 중 하나가 예외처리입니다.
시스템이 비정상적으로 종료되지 않게 하기 위한 필수 사항인데, 동기적 방식과 비동기 방식에서의 예외 처리 방식이 조금씩 다릅니다.
동기적 방식 예외처리
그냥 일반 코드 쓰듯이 try {} catch {} 로 묶어 주면 됩니다.
// main.js
var fs = require('fs');
// 파일 읽기(동기적)
try {
var data = fs.readFileSync('notexist.txt', 'utf8'); // 파일이 없는데 읽으려 함
console.log(data);
} catch (err) {
console.log(err);
}
비동기적 방식 예외처리
콜백함수 안에 단순히 if문으로 걸러주면 됩니다.
// main.js
var fs = require('fs');
// 파일 읽기
fs.readFile('notexist.txt', 'utf8', function(err, data) { // 존재하지 않는 파일 읽기
if (err) {
console.log(err); // 읽기 실패
}
else {
console.log(data); // 읽기 성공
}
});
프로미스 방식으로 파일 읽기
노드의 메소드들은 대부분 비동기 호출 콜백함수 방식으로 처리하고 있습니다.
하지만 많은 사람들이 promise방식으로 비동기 호출을 처리하길 원했고, 이에따른 메소드를 지원하게 되었습니다.
기존 콜백 방식
const fs = require('fs');
// 콜백 방식
fs.readFile('./text.txt', (err, data) => {
if (err) {
throw err;
}
console.log(data); // 바이너리 데이터. 2진법을 16진법으로 변환된 데이터. <Buffer 48 65 6c 6c 6f 20 57 6f 72 6c 64 20 21 21>
console.log(data.toString()); // Hello World !!
})
Util모듈을 이용한 프로미스 변환 방식
const fs = require('fs');
const util = require('util');
const fs_util_read = util.promisify(fs.readFile); // 노드메소드를 프로미스화
fs_util_read('./text.txt')
.then((data) => {
console.log(data);
console.log(data.toString());
})
.catch((err) => {
throw err;
})
파일 프로미스 메소드
const fs = require('fs');
const fs_promise = fs.promises; // 바로 require('fs').promises 해도 된다.
// 프로미스 방식
fs_promise.readFile('./text.txt')
.then((data) => {
console.log(data); // 바이너리 데이터. 2진법을 16진법으로 변환된 데이터. <Buffer 48 65 6c 6c 6f 20 57 6f 72 6c 64 20 21 21>
console.log(data.toString()); // Hello World !!
})
.catch((err) => {
throw err;
})
/* 프로미스 파일 쓰고 읽기 */
const fs = require('fs');
const fs_promise = fs.promises; // 바로 require('fs').promises 해도 된다.
fs_promise.writeFile('./text2.txt', "방금 내가 썼습니다.") // 파일을 쓰고
.then(() => {
return fs_promise.readFile('./text2.txt'); // 위에서 쓴 파일을 바로 읽기도 가능하다.
})
.then((data) => {
console.log(data.toString()); // "방금 내가 썼습니다."
})
.catch((err) => {
throw err;
})
파일 비동기 처리 순서를 제어하기
따로 filesync를 써주지 않는한, 파일은 기본적으로 비동기로 처리됩니다.
이는 성능을 향상시켜주는 장점을 가지지만, 무엇이 먼저 처리될지 순서가 뒤죽박죽 되어버리는 단점을 가지고 있습니다.
const fs = require('fs');
fs.readFile('./text.txt', (err, data) => {
if (err) throw err;
console.log("1번");
console.log(data.toString());
})
fs.readFile('./text.txt', (err, data) => {
if (err) throw err;
console.log("2번");
console.log(data.toString());
})
fs.readFile('./text.txt', (err, data) => {
if (err) throw err;
console.log("3번");
console.log(data.toString());
})
* 순서가 뒤죽 박죽 입니다.
이에 대한, 해결 방법은 총 3가지 입니다. 하나씩 알아보도록 합시다.
콜백중첩 (콜백지옥)
콜백함수안에 또 파일을 불러오고, 다시 그 불러온 파일의 콜백함수안에 또 불러오는 식으로 해결 할 수 있습니다.
가장 간단한게 구현할 수 있지만, 콜백지옥에 빠지는 단점이 존재 합니다.
const fs = require('fs');
fs.readFile('./text.txt', (err, data) => {
if (err) throw err;
console.log("1번");
console.log(data.toString());
fs.readFile('./text.txt', (err, data) => {
if (err) throw err;
console.log("2번");
console.log(data.toString());
fs.readFile('./text.txt', (err, data) => {
if (err) throw err;
console.log("3번");
console.log(data.toString());
})
})
})
파일 프로미스 사용
const fs = require('fs');
const fs_promise = fs.promises;
fs_promise.readFile('./text.txt')
.then((data) => {
console.log("1번");
console.log(data.toString());
return fs_promise.readFile('./text.txt');
})
.then((data) => {
console.log("2번");
console.log(data.toString());
return fs_promise.readFile('./text.txt');
})
.then((data) => {
console.log("3번");
console.log(data.toString());
return fs_promise.readFile('./text.txt');
})
.catch(err => {
throw err;
})
async / await 사용
const fs = require('fs');
const fs_promise = fs.promises;
(async () => {
let data = await fs_promise.readFile('./text.txt');
console.log("1번");
console.log(data.toString());
let data2 = await fs_promise.readFile('./text.txt');
console.log("2번");
console.log(data2.toString());
let data3 = await fs_promise.readFile('./text.txt');
console.log("3번");
console.log(data3.toString());
})();
기타 fs 파일 메소드 종류
파일/폴더 접근 체크 - access
폴더나 파일에 접근할 수 있는지를 체크합니다. 두 번째 인자로 상수들을 넣었습니다.
- F_OK는 파일 존재 여부,
- R_OK는 읽기 권한 여부,
- W_OK는 쓰기 권한 여부를 체크합니다.
파일/폴더나 권한이 없다면 에러가 발생하는데, 파일/폴더가 없을 때의 에러 코드는 ENOENT입니다.
fs.access(경로, 옵션, 콜백)
폴더 생성 - mkdir
폴더를 만드는 메서드입니다. 이미 폴더가 있다면 에러가 발생하므로 먼저 access() 메서드를 호출해서 확인하는 것이 중요합니다.
const fs = require('fs').promises;
const constants = require('fs').constants;
/* access가 나타내는 경우의 수는 3가지임
1. 폴더가 존재하고 모든 권한이 있을 경우
2. 폴더가 존재하고 권한이 없을 경우
3. 폴더가 존재하지 않을 경우
*/
fs.access('./folder', constants.F_OK | constants.W_OK | constants.R_OK)
.then(() => { // 폴더가 존재하고 모든 권한이 있을 경우
return Promise.reject('이미 폴더 있음');
})
.catch(err => {
if (err.code === 'ENOENT') { // 폴더가 존재하지 않을 경우에만 발동
console.log('폴더 없음');
return fs.mkdir('./folder');
}
// 폴더가 존재하고 모든 권한이 있을 경우
// 폴더가 존재하고 권한이 없을 경우
return Promise.reject(err);
})
.then(() => {
console.log('폴더 만들기 성공');
// 여기서 fs는 Promise객체이다. 프로미스를 리턴하기에 다음 then()으로 넘어감
return fs.open('./folder/files.js', 'w');
})
.then(fd => {
console.log('빈 파일 만들기 성공', fd);
fs.rename('./folder/file.js', './folder/newfile.js');
})
.then(() => {
console.log('이름 바꾸기 성공');
})
.catch(err => {
console.log(err);
});
파일 목록 가져오기 - readdir
폴더 안의 내용물을 확인할 수 있습니다. 파일 목록을 배열에 담아서 가져옵니다
var fs = require('fs');
fs.readdir('./test',function(err,filelist){
console.log(filelist);
}); //[ 'css3', 'file.js' ]
파일 이름 수정 - rename
파일의 이름을 바꾸는 메서드입니다. 기존 파일 위치와 새로운 파일 위치를 적어주면 됩니다. 반드시 같은 폴더를 지정할 필요는 없으므로 잘라내기 같은 기능을 할 수도 있습니다.
// fs.rename(oldPath, newPath, callback)
fs.rename(`./data/${title_ori}`,`./data/${title}`,()=>{ //파일 이름 바꾸기
});
파일 쓰기 - appendFile
이미 쓰여진 파일을 열어 추가로 작성하는 것으로, 'a' 모드로 파일을 쓰기 하는 것과 비슷합니다.
파일 삭제 - unlink
파일을 지울 수 있습니다. 파일이 없다면 에러가 발생하므로 먼저 파일이 있는지를 꼭 확인해야 합니다.
// fs.unlink(Path, callback)
fs.unlink(`./data/${post.id}`,(err)=>{
console.log(post.id);
})
폴더 삭제 - rmdir
폴더를 지울 수 있습니다. 폴더 안에 파일이 있다면 에러가 발생하므로 먼저 내부 파일을 모두 지우고 호출해야 합니다.
파일 복사 - copyFile
파일 감시
파일 변경 사항 발생이 이벤트 호출
fs.existsSync
: 파일이나 폴더가 존재하는지??
파일 검사
fs.stat : 해당 파일이 일반 파일인지 폴더인지 바로가기인지 알아낼수 있음
파일 디스크립터 메소드
C나 자바 같은 파일 함수들도 노드에서 쓸수 있습니다.
이러한 메소드들의 특징은 파일 디스크립터를 이용하여 읽습니다.
위에서 배운바와 같이 노드에서도 C나 자바처럼 파일디스크립터 메소드를 사용할수있지만 좀더 자바스크립트 환경에서 사용하기 편하게 새로 파일 메소드를 만든것이fs.readFile(),fs.writeFile(),fs.appendFile()이라고 보면 된다.
메소드 | 설명 |
open(path, flags, [mode], [callback]]) | 파일을 연다. |
read(fd, buffer, offset, length, position, [callback]) | 지정한 부분의 파일 내용을 읽어 들인다. |
write(fd, buffer, offset, length, position, [callback]) | 파일의 지정한 부분에 데이터를 쓴다. |
close(fd, [callback]) | 파일을 닫는다. |
var fs = require('fs');
// 파일의 아이디(fd 변수)를 가져오는 메서드입니다. 파일이 없다면 파일을 생성한 뒤 그 아이디를 가져옵니다.
// 가져온 아이디를 사용해 fs.read()나 fs.write()로 읽거나 쓸 수 있습니다.
// 번째 인자로 어떤 동작을 할 것인지 설정할 수 있습니다. 쓰려면 w, 읽으려면 r, 기존 파일에 추가하려면 a입니다.
fs.open('./output.txt', 'w', function(err, fd) {
if(err) throw err;
var buf = new Buffer('안녕\n');
fs.write(fd, buf, 0, buf.length, null, function(err, written, buffer) {
if(err) throw err;
console.log(err, written, buffer);
fs.close(fd, function() {
console.log('파일 열고 데이터 쓰고 파일 닫기 완료');
});
});
});
파일을 열기 위해 open()메소드를 호출할 때 세 개의 파라미터가 전달되었습니다.
첫 번째 파라미터는 파일의 이름, 두 번째 파라미터는 파일을 읽거나 쓰기 위한 플래그입니다.
[대표적인 플래그의 종류]
flags | 설명 |
r | 읽기에 사용하는 플래그이다. 파일이 없으면 예외가 발생한다. |
w | 쓰기에 사용하는 플래그이다. 파일이 없으면 만들어지고 파일이 있으면 이전 내용을 모두 삭제한다. |
w+ | 읽기와 쓰기에 사용하는 플래그이다. 파일이 없으면 만들어지고 파일이 있으면 이전 내용을 모두 삭제한다. |
a+ | 읽기와 추가에 사용하는 플래그이다. 파일이 없으면 만들어지고 파일이 있으면 이전 내용에 새로운 내용을 추가한다. |
다양한 예시
var fs = require("fs")
// 문자 하나만 입력받을 경우
var input = fs.readFileSync("/dev/stdin").toString()
// 한칸 띄어쓰기로 구분
// input[0], input[1] 배열에서 꺼내쓰면 된다.
var input = fs
.readFileSync("/dev/stdin")
.toString()
.split(" ")
// 줄바꿈으로 구분
var input = fs
.readFileSync("/dev/stdin")
.toString()
.split("\n")
// 만약 인풋값이 숫자라면
var input = fs
.readFileSync("/dev/stdin")
.toString()
.split(" ")
.map(function(a) {
return +a // 문자열 숫자를 유리수 숫자로 처리하기 위한 작업
})
이 글이 좋으셨다면 구독 & 좋아요
여러분의 구독과 좋아요는
저자에게 큰 힘이 됩니다.