20.5 파일시스템 접근
프로그래밍 책에서는 대개 파일시스템 접근에 관해 설명합니다.
파일을 다루는 것은 일반적인 프로그래밍에서 매우 중요한 부분이니까요.
하지만 자바스크립트는 노드가 만들어지기 전까지는 파일시스템에 접근할 수 없는 외톨이였습니다.
이 장의 예제는 프로젝트 루트가 유닉스 시스템에서 사용하는 일반적인 경로인 /home/[jdoe]/fs라고 가정합니다.
[jdoe]를 당신의 사용자 이름으로 바꾸십시오.
윈도우 시스템이라면 C:\Users[jdoe]\Documents\fs에 파일을 저장하면 됩니다.
파일을 만들 때는 fs.writeFile
을 사용합니다.
프로젝트 루트에 다음과 같이 write.js 파일을 만드십시오.
const fs = require('fs');
fs.writeFile('hello.txt', 'hello from Node!', function(err) {
if (err) return console.log('Error writing to file.');
})
write.js 파일을 저장한 디렉터리에 쓰기 권한이 있고, 읽기 전용 hello.txt 파일이 이미 존재하지 않으면 hello.txt 파일이 생성될 겁니다.
노드 애플리케이션을 실행하면 해당 애플리케이션은 자신이 실행된 현재 작업 디렉터리를 __dirname
변수로 보관합니다.
이 변수를 사용해서 write.js 파일을 다음과 같이 고쳐 쓸 수 있습니다.
const fs = require('fs');
fs.writeFile(__dirname + '/hello.txt', 'hello from Node!', function(err) {
if (err) return console.error('Error writing to file.');
})
이제 write.js는 항상 /home/[jdoe]/fs, 즉 write.js가 있는 디렉터리에 hello.txt를 만듭니다.
문자열 병합으로 __dirname
과 파일 이름을 합쳐서 파일 경로를 얻으면 운영체제에 따라 호환되지 않을 수 있습니다.
예를 들어 이 예제에서 사용한 방법은 윈도우 컴퓨터에서 동작하지 않습니다.
노드의 path
모듈에는 운영체제 독립적인 경로 이름 유틸리티가 있습니다.
이 모듈을 써서 파일 경로를 만들면 모든 운영체제에서 사용할 수 있습니다.
const fs = require('fs');
const path = require('path');
fs.writeFile(path.join(__dirname, 'hello.txt'), 'hello from Node!', function(err) {
if (err) return console.error('Error writing to file.');
})
path.join
은 운영체제에 따라 디렉터리 구분자를 알맞게 사용하므로 이 메서드를 사용하기 권합니다.
파일 컨텐츠를 읽을 때는 fs.readFile
을 사용합니다.
다음과 같이 read.js를 만드십시오.
const fs = require('fs');
const path = require('path');
fs.readFile(path.join(__dirname, 'hello.txt'), function(err, data) {
if (err) return console.error('Error reading file.');
console.log('Read file contents');
console.log(data);
})
이 예제를 실행하면 다음과 같은 이상한 결과를 보게 됩니다.
Read file contents:
<buffer 68 65 6c 6f 20 66 72 6d 4e 64 21>
이 16진수 코드를 ASCII/Unicode
로 바꾸면 hello from Node! 이긴 하지만, 편리하다고 말할 수는 없습니다.
fs.readFile
에 인코딩 정보를 제공하지 않으면 fs.readFile
은 가공되지 않은 바이너리 데이터인 버퍼를 반환합니다.
write.js에 인코딩을 명시하지는 않았지만, 기본 문자열 인코딩은 UTF-8입니다.
read.js에 UTF-8 인코딩을 지정하면 원하는 결과를 얻을 수 있습니다.
const fs = require('fs');
const path = require('path');
fs.readFile(path.join(__dirname, 'hello.txt'), {encoding: 'utf8'}, function(err, data) {
if (err) return console.error('Error reading file.');
console.log('Read file contents');
console.log(data);
})
파일 관련 함수에는 모두 동기적으로 작업하는 짝이 있으며, 이들의 이름은 Sync로 끝납니다.
write.js에도 동기적인 함수를 쓸 수 있습니다.
fs.writeFileSync(path.join(__dirname, 'hello.txt'), 'hello from Node!');
read.js도 마찬가지입니다.
const data = fs.readFileSync(path.join(__dirname, 'hello.txt'), { encoding: 'utf8' });
동기적인 함수에서는 try/catch
블록을 통해 예외 처리를 합니다.
try {
fs.writeFileSync(path.join(__dirname, 'hello.txt'), 'hello from Node!');
} catch (err) {
console.error('Error writing file.');
}
CAUTION_
동기적인 파일시스템 함수는 이해하기 쉬우므로 이걸 쓰고 싶다고 생각할 수도 있습니다.
하지만 웹 서버나 네트워크 애플리케이션에서 노드의 성능은 비동기적 실행에서 나옵니다.
웹 서버나 네트워크 애플리케이션을 만들 때는 항상 비동기적 함수를 써야 합니다.
명령줄 유틸리티를 만든다면 동기적 함수를 써도 별 문제는 없습니다.
디렉터리에 어떤 파일이 있는지 알아보려면 fs.readdir
을 사용합니다.
다음과 같이 ls.js
파일을 만드십시오.
const fs = require('fs');
fs.readdir(__dirname, function(err, files) {
if (err) return console.error('Unable to read directory contents');
console.log(`Contents of ${__dirname}:`);
console.log(files.map(f => '\t' + f).join('\n'));
})
fs 모듀에는 이외에도 여러 가지 파일시스템 함수들이 있습니다.
파일을 지울 때는 fs.unlink
, 파일을 옮기거나 이름을 바꿀 때는 fs.rename
, 파일과 디렉터리 정보를 얻을 때는 fs.stat
이외에도 많은 함수가 있습니다.
더 많은 정보를 알아보려면 노드 API 문서(https://nodejs.org/api/fs.html)를 참고하십시오.