Node.js | fs 모듈
포스트
취소

Node.js | fs 모듈

node:fs

node:fs 모듈은 파일과 디렉토리에 접근 및 조작할 수 있는 기능을 제공한다.

목차

임포트

1
2
3
4
5
// promise-based APIs
const fs = require("node:fs/promises");

// callback and sync APIs
const fs = require("node:fs");

node:fs 대신 fs를 사용해도 동일한 모듈을 가져온다.

각 API 타입별 차이

node:fs 모듈은 세가지 타입의 API를 가진다.

  • promise-based : node:fs/promises 모듈에서 제공
  • callback, sync : node:fs 모듈에서 제공

Promise-based API

1
2
3
4
5
6
7
8
9
async unlinkFile() {
    try {
        await fs.unlink("./test.txt");
        console.log("삭제 성공!")
    }
    catch(err) {
        console.log("에러가 발생했습니다:", err.message)
    }
}

Promise-based API는 Promise를 리턴한다. 따라서 async, await 키워드 또는 then(), catch() 메서드로 값을 처리해야한다.

Callback API

1
2
3
4
fs.unlink("./test.txt", (err) => {
    if (err) throw err;
    console.log('삭제 성공!');
})

대부분의 Callback API는 마지막 인자로 callback 함수를 받는다. callback 함수의 인자는 API마다 차이가 있으나 첫번째 인자는 항상 예외를 위해 예약된다.

예외가 발생하지 않는다면 callback 첫번째 인자로 null또는 undefined가 넘어간다.

Promised-based API보다 Callback API가 성능(실행 시간과 메모리 할당 모두)상 더 나은 선택이다.

Sync API

1
2
3
4
5
6
7
try {
    fs.unlinkSync("./test.txt");
    console.log("삭제 성공!")
}
catch(err) {
    console.log("에러가 발생했습니다:", err.message)
}

Sync API는 메서드명의 마지막에 Sync가 붙는다.

Promised-based API의 동기 버전이다. 즉, Promised-based API에서 await 키워드를 단 것과 유사하게 작동한다.

기능

Promised-based API와 Sync API의 인자와 리턴 값이 매우 유사하기 때문에 Callback API와 Sync API에 대해서만 작성한다. 비동기 여부 외 두 API간 차이가 있는 경우에만 설명을 작성한다.

파일 쓰기

1
2
3
4
5
6
// <Callback API>
// fs.writeFile(file, data[, options], callback);

fs.writeFile("text.txt", "Hello world", "utf8", (err) => {
    if (err) throw err;
});
1
2
3
4
// <Sync API>
// fs.writeFileSync(file, data[, options]);

fs.writeFileSync("text.txt", "Hello world", "utf8");

options 자리에 문자열을 넣으면 encoding을 가리키게 된다.

비동기 API를 이용해 동일한 파일을 여러번 쓸 경우 반드시 이전 작업이 끝났는지 확인해야 한다. 이전 작업을 취소해야 한다면, AbortController를 이용한다.

Documentation

파일 읽기

1
2
3
4
5
6
7
// <Callback API>
// fs.readFile(file[, options], callback);

fs.readFile("/etc/passwd", (err, data) => {
    if (err) throw err;
    console.log(data);
});
1
2
3
4
5
// <Sync API>
// fs.readFileSync(file[, options]);

const data = fs.readFileSync("/etc/passwd");
console.log(data);

파일 제거

unlink

1
2
3
4
5
6
7
// <Callback API>
// fs.unlink(path, callback);

fs.unlink("path/file.txt", (err) => {
    if (err) throw err;
    console.log("파일 제거됨");
});
1
2
3
4
5
// <Sync API>
// fs.unlinkSync(path);

fs.unlink("path/file.txt");
console.log("파일 제거됨");

Documentation

파일을 제거한다.

디렉토리 생성

1
2
3
4
5
6
// <Callback API>
// fs.mkdir(path[, options], callback)

fs.mkdir("./tmp/a/apple", { recursive: true }, (err) => {
    if (err) throw err;
})
1
2
3
// fs.mkdir(path[, options])

fs.mkdirSync("./tmp/a/apple", { recursive: true });

recursive가 false 인 경우, 디렉토리 생성 실패시 예외가 발생한다. 항상 undefined를 리턴한다.

recursive가 true 인 경우, 재귀적으로 디렉토리를 생성한다. 생성한 첫 디렉토리의 절대경로를 리턴하고, 디렉토리를 생성하지 않았다면 undefined를 리턴한다.

예시를 들면 아래와 같다.

fs.mkdirSync("/a/b", { recursive: true }) 를 실행했을때

  • “/a”가 없다면, “/a” 리턴
  • “/a”는 있으나 “/a/b”가 없다면, “/a/b” 리턴
  • “/a/b”가 있다면, undefined 리턴

Documentation

디렉토리 제거

rm

1
2
3
4
5
6
// <Callback API>
// fs.rm(path[, options], callback)

fs.rm("./dir", { force : true, recursive: true }, (err)=>{
    if (err) throw err;
});
1
2
3
4
// <Sync API>
// fs.rmSync(path[, options])

fs.rmSync("./dir", { force : true, recursive: true });

파일 및 디렉토리를 제거한다.

options 객체의 필드

  • recursive : 재귀 삭제 여부. 디렉토리 삭제시 true여야 한다. (기본값 : false)
  • force : true라면 경로가 존재하지 않더라도 예외가 발생하지 않는다. (기본값 : false)

Documentation

rmdir (비권장)

1
2
3
4
5
6
// <Callback API>
// fs.rmdir(path[, options] callback)

fs.rmdir("./dir", (err)=>{
    if (err) throw err;
});
1
2
3
4
// <Sync API>
// fs.rmdirSync(path[, options])

fs.rmdirSync("./dir");

빈 디렉토리를 제거한다. 디렉토리가 아니거나, 비어있지 않거나, 경로가 없다면 예외가 발생한다.

Documentation

파일 존재 및 상태 확인

주의할 점으로, 아래 메서드를 이용해서 파일에 접근하기전 확인하는 용도로 사용해서는 안된다. 접근성 확인 이후 타 프로세스가 해당 파일에 먼저 접근할 수 있으므로 파일에 접근하는 메서드의 예외처리를 통해 접근성 확인을 수행해야 한다.

exists

1
2
3
4
5
6
7
8
9
// <Sync API>
// fs.existsSync(path)

if (fs.existsSync("./target.txt")) {
    console.log("파일이 존재함");
}
else {
    console.log("파일이 존재하지 않음");
}

파일 및 디렉토리가 존재하는지 확인한다.

단순 경로 존재 유무 메서드는 Sync API인 existsSync() 만 사용된다. Promise-based API는 존재하지 않고 Callback API인 exists()는 deprecated 되었다.

비동기로 파일이 존재하는지 확인하려면 아래의 access()를 쓰는 것이 좋다.

Documentation

access

1
2
3
4
5
6
7
8
9
10
11
// <Callback API>
// fs.access(path[, mode], callback);

fs.access(file, fs.constants.R_OK, (err) => {
    if (err) {
        console.log("읽기 가능")
    }
    else {
        console.log("읽을 수 없음")
    }
});
1
2
3
4
5
6
7
8
9
10
// <Sync API>
// fs.accessSync(path[, mode]);

try {
    fs.accessSync(file, fs.constants.R_OK);
    console.log("읽기 가능");
}
catch (err) {
    console.log("읽을 수 없음")
}

파일의 존재, 읽기, 쓰기, 실행가능 유무를 확인한다.

파일이 mode 플래그 조건에 부합하지 않으면 예외를 리턴한다.

  • fs.constants.F_OK : 존재 유무
  • fs.constants.R_OK : 읽기 가능 유무
  • fs.constants.W_OK : 쓰기 가능 유무
  • fs.constants.X_OK : 실행 가능 유무 (Windows에서는 F_OK처럼 동작함)

mode 값은 비트마스크이므로 비트 OR을 통해 여러 모드를 검사할 수 있다. (ex. fs.constants.R_OK | fs.constants.W_OK)

Documentation

stat

1
2
3
4
5
6
7
// <Callback API>
// fs.stat(path[, options], callback);

fs.stat("hello.txt", (err, stats)=>{
    console.log(stats.isDirectory())
    console.log(stats)
})
1
2
3
4
5
6
// <Sync API>
// fs.stat(path[, options]);

const stats = fs.stat("hello.txt");
console.log(stats.isDirectory());
console.log(stats);

경로의 통계를 확인한다.

fs.Stats 필드

  • isFile() : 파일 여부
  • isDirectory() : 디렉토리 여부
  • isFIFO() : FIFO 파이프 여부
  • size : 크기 (bytes)

Documentation

파일 복사

copyFile

1
2
3
4
5
6
7
8
9
10
// <Callback API>
// fs.copyFile(src, dest[, mode], callback)


fs.copyFile('source.txt', 'destination.txt', (err) => {
    if (err) throw err;
});
fs.copyFile('source.txt', 'destination.txt', fs.constants.COPYFILE_EXCL, (err) => {
    if (err) throw err;
});
1
2
3
4
5
// <Sync API>
// fs.copyFileSync(src, dest[, mode])

fs.copyFileSync("source.txt", "destination.txt");
fs.copyFileSync("source.txt", "destination.txt", fs.constants.COPYFILE_EXCL);

단일 파일을 복사한다. 기본 동작으로 dest를 덮어쓰고, mode 인자로 fs.constants.COPYFILE_EXCL 플래그가 지정된 경우 이미 dest가 존재하면 예외가 발생한다.

Documentation

cp

1
2
3
4
5
6
// <Callback API>
// fs.cp(src, dest[, options], callback)

fs.cp('source', 'destination', { recursive: true }, (err) => {
    if (err) throw err;
});
1
2
3
4
// <Sync API>
// fs.cpSync(src, dest[, options])

fs.cpSync("/source", "/destination", { recursive: true });

하위 디렉토리와 파일을 포함한 디렉토리 구조를 복사한다.

options 객체의 필드 목록

  • recursive : 디렉토리 복사 시 사용 (기본값: false)
  • force : dest 경로가 이미 존재할 때 true라면 덮어쓰고, false라면 아무 작업도 하지 않는다. (기본값: true)
  • errorOnExists : force가 false 일 때 dest 경로가 이미 존재하면 예외를 발생시킨다. (기본값: false)
  • mode : copyFileSync()의 mode와 동일 (기본값: 0)
  • dereference : 심볼릭 링크 역참조 여부 (기본값: false)

Documentation

파일 이동

1
2
3
4
5
6
// <Callback API>
// fs.rename(oldPath, newPath, callback)

fs.rename("old.txt", "new.txt", (err)=>{
    if (err) throw err;
});
1
2
3
4
// <Sync API>
// fs.renameSync(oldPath, newPath)

fs.rename("old.txt", "new.txt");

파일 및 디렉토리의 위치를 변경한다. newPath에 파일이 존재한다면 덮어씌우고, newPath가 디렉토리라면 예외가 발생한다.

Documentation

참고

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.
바로가기