반응형

# fs (파일 시스템 모듈)

 

File system | Node.js v14.18.0 Documentation

 

nodejs.org

  • 파일 시스템에 접근하는 모듈
  • 파일/폴더 생성, 삭제, 읽기, 쓰기 가능
  • 웹 브라우저에서는 제한적이었으나 노드는 권한을 가지고 있음
  • 아래는 파일을 읽는 예제들.
# readfile.js
const fs = require('fs');

fs.readFile('./readme.txt', (err, data) => {
    if (err) {
        throw err;
    }
    console.log(data);
    console.log(data.toString());
});


# readme.txt
저를 읽어 주세요.


출력 결과
<Buffer ec a0 80 eb a5 bc 20 ec 9d bd ec 96 b4 ec a3 bc ec 84 b8 ec 9a 94 2e>
저를 읽어주세요.
  • fs.promise 사용 예
const fs = require('fs').promises;

fs.readFile('./readme.txt')
    .then((data) => {
        console.log(data);
        console.log(data.toString()); 
    })
    .catch((err) => {
        throw err;
    });
    
    
출력 결과
<Buffer ec a0 80 eb a5 bc 20 ec 9d bd ec 96 b4 ec a3 bc ec 84 b8 ec 9a 94 2e>
저를 읽어주세요.
  • 아래는 파일을 생성하는 예제들.
# writefile.js
const fs = require('fs').promises;

fs.writeFile('./writeme.txt', '글이 입력됩니다.')
    .then(() => {
        return fs.readFile('./writeme.txt');
    })
    .then((data) => {
        console.log(data.toString());
    })
    .catch((err) => {
        throw err;
    });
    
    
출력 결과
writeme.txt 파일 생성과 함께 아래 문구 출력.
글이 입력됩니다.
  • 콜백이나 프로미스는 대부분의 경우 비동기라 순서대로 처리되지 않음. (매 번 순서가 다르게 실행됨.)
# async.js
const fs = require('fs');

fs.readFile('./readme.txt', (err, data) => {
    if (err) {
        throw err;
    }
    console.log('1번', data.toString());
});
fs.readFile('./readme.txt', (err, data) => {
    if (err) {
        throw err;
    }
    console.log('2번', data.toString());
});
fs.readFile('./readme.txt', (err, data) => {
    if (err) {
        throw err;
    }
    console.log('3번', data.toString());
});
fs.readFile('./readme.txt', (err, data) => {
    if (err) {
        throw err;
    }
    console.log('4번', data.toString());
});



출력 결과 (변동 됨)
2번 저를 읽어주세요.
3번 저를 읽어주세요.
1번 저를 읽어주세요.
4번 저를 읽어주세요.
  • 아래와 같이 동기 방식으로도 가능. (동시에 돌릴수 없어서 비 효율적_실행 시 다른 작업(다른 사용자)들은 기다려야함. / 딱 한번 실행 or 서버 초기화 작업 시 사용)
# sync.js
const fs = require('fs');

let data = fs.readFileSync('./readme.txt');
console.log('1번', data.toString());
data = fs.readFileSync('./readme.txt');
console.log('2번', data.toString());
data = fs.readFileSync('./readme.txt');
console.log('3번', data.toString());
data = fs.readFileSync('./readme.txt');
console.log('4번', data.toString());

출력 결과
1번 저를 읽어주세요.
2번 저를 읽어주세요.
3번 저를 읽어주세요.
4번 저를 읽어주세요.
  • 가능하면 비 동기작업을 하면서 순서를 유지하는걸 추천함. (콜백 헬 발생 가능성 존재.)
# async.js
const fs = require('fs');

fs.readFile('./readme.txt', (err, data) => {
    if (err) {
        throw err;
    }
    console.log('1번', data.toString());
    fs.readFile('./readme.txt', (err, data) => {
        if (err) {
            throw err;
        }
        console.log('2번', data.toString());
        fs.readFile('./readme.txt', (err, data) => {
            if (err) {
                throw err;
            }
            console.log('3번', data.toString());
            fs.readFile('./readme.txt', (err, data) => {
                if (err) {
                    throw err;
                }
                console.log('4번', data.toString());
            });
        });
    });
});


출력 결과
1번 저를 읽어주세요.
2번 저를 읽어주세요.
3번 저를 읽어주세요.
4번 저를 읽어주세요.
  • 콜백 헬 해결을 위해 프로미스 사용.
const fs = require('fs').promises;

async function main() {
    let data = await fs.readFile('./readme.txt');
    console.log('1번', data.toString());
    data = await fs.readFile('./readme.txt');
    console.log('2번', data.toString());
    data = await fs.readFile('./readme.txt');
    console.log('3번', data.toString());
    data = await fs.readFile('./readme.txt');
    console.log('4번', data.toString());
}
main();
  • 비동기로 하되 순서를 지키는게 순서를 지키면서 동시성도 지키는 좋은 방법이다.
  • sync는 편하지만 실제로 서버에서 사용 시 문제 발생 가능성 존재.

 

# 버퍼와 스트림 

  • 버퍼 : 일정한 크기로 모아두는 데이터. 
일정한 크기가 되면 한 번에 처리.
버퍼링 : 버퍼에 데이터가 찰 때까지 모으는 작업.
  • 스트림 : 데이터의 흐름
일정한 크기로 나눠서 여러 번에 걸쳐서 처리.
버퍼(또는 청크)의 크기를 작게 만들어서 주기적으로 데이터를 전달.
스트리밍 : 일정한 크기의 데이터를 지속적으로 전달하는 작업.
  • 노드에서는 Buffer 객체 사용.
# buffer.js
const buffer = Buffer.from('저를 버퍼로 바꿔보세요.');
console.log(buffer);
console.log(buffer.length);
console.log(buffer.toString());


출력 결과
<Buffer ec a0 80 eb a5 bc 20 eb b2 84 ed 8d bc eb a1 9c 20 eb b0 94 ea bf 94 eb b3 b4 ec 84 b8 ec 9a 94 2e>
33
저를 버퍼로 바꿔보세요.
const buffer = Buffer.from('저를 버퍼로 바꿔보세요.');
console.log(buffer);
console.log(buffer.length);
console.log(buffer.toString());

const array = [Buffer.from('띄엄 '), Buffer.from('띄엄 '), Buffer.from('띄어쓰기 ')];
console.log(Buffer.concat(array).toString());


출력 결과
<Buffer ec a0 80 eb a5 bc 20 eb b2 84 ed 8d bc eb a1 9c 20 eb b0 94 ea bf 94 eb b3 b4 ec 84 b8 ec 9a 94 2e>
33
저를 버퍼로 바꿔보세요.
띄엄 띄엄 띄어쓰기
  • 빈 버퍼 생성
console.log('-----Buffer.alloc()-----');
console.log(Buffer.alloc(5));
  • 나눠서 처리 후 최종적으로 모아주기. (Stream은 동시에 오는 것이 아닌, 순서대로 진행 됨)
# createReadStream.js
const fs = require('fs');
const readStream = fs.createReadStream('./readme3.txt');

const data = [];
readStream.on('data', (chunk) => {
    data.push(chunk);
    console.log('data', chunk, chunk.length);
});
readStream.on('end', () => {
    console.log('end : ', Buffer.concat(data).toString())
});


# readme3.txt
저는 조금씩 나눠서 전달됩니다. 나눠진 조각을 chunk라고 부릅니다.
안녕하세요. 헬로 노드 헬로 스트림 헬로 버퍼.


출력 결과
data <Buffer ec a0 80 eb 8a 94 20 ec a1 b0 ea b8 88 ec 94 a9 20 eb 82 98 eb 88 a0 ec 84 9c 20 ec a0 84 eb 8b ac eb 90 a9 eb 8b 88 eb 8b 
a4 2e 20 eb 82 98 eb 88 a0 ... 103 more bytes> 153
end :  저는 조금씩 나눠서 전달됩니다. 나눠진 조각을 chunk라고 부릅니다.
안녕하세요. 헬로 노드 헬로 스트림 헬로 버퍼.
  • 비동기는 에러처리 꼭 해줘야 함.
const fs = require('fs');
const readStream = fs.createReadStream('./readme3.txt', { highWaterMark : 16 });

const data = [];
readStream.on('data', (chunk) => {
    data.push(chunk);
    console.log('data', chunk, chunk.length);
});
readStream.on('end', () => {
    console.log('end : ', Buffer.concat(data).toString())
});
readStream.on('error', (err) => {
    console.log('error : ', err)
});



출력 결과
data <Buffer ec a0 80 eb 8a 94 20 ec a1 b0 ea b8 88 ec 94 a9> 16
data <Buffer 20 eb 82 98 eb 88 a0 ec 84 9c 20 ec a0 84 eb 8b> 16
data <Buffer ac eb 90 a9 eb 8b 88 eb 8b a4 2e 20 eb 82 98 eb> 16
data <Buffer 88 a0 ec a7 84 20 ec a1 b0 ea b0 81 ec 9d 84 20> 16
data <Buffer 63 68 75 6e 6b eb 9d bc ea b3 a0 20 eb b6 80 eb> 16
data <Buffer a6 85 eb 8b 88 eb 8b a4 2e 0d 0a ec 95 88 eb 85> 16
data <Buffer 95 ed 95 98 ec 84 b8 ec 9a 94 2e 20 ed 97 ac eb> 16
data <Buffer a1 9c 20 eb 85 b8 eb 93 9c 20 ed 97 ac eb a1 9c> 16
data <Buffer 20 ec 8a a4 ed 8a b8 eb a6 bc 20 ed 97 ac eb a1> 16
data <Buffer 9c 20 eb b2 84 ed 8d bc 2e> 9
end :  저는 조금씩 나눠서 전달됩니다. 나눠진 조각을 chunk라고 부릅니다.
안녕하세요. 헬로 노드 헬로 스트림 헬로 버퍼.
  • 스트림 방식을 사용하게 되면 메모리를 아낄 수 있어서 좋음.
  • 쓰기 할때도 메모리 효율적.
# createWriteStream.js
const fs = require('fs');

const writeStream = fs.createWriteStream('./writeme2.txt');
writeStream.on('finish', () => {
    console.log('파일 쓰기 완료');
});

writeStream.write('이 글을 씁니다.\n');
writeStream.write('한 번 더 씁니다.');
writeStream.end();

## pipe와 스트림 메모리 효율 확인

  • pipe 이용 파일 복사
const fs = require('fs');

const readStream = fs.createReadStream('./readme3.txt', { highwaterMark : 16 });
const writeStream = fs.createWriteStream('./writeme3.txt');
readStream.pipe(writeStream);
  • pipe 이용 압축 스트리밍
const fs = require('fs');
const zlib = require('zlib');

const readStream = fs.createReadStream('./readme3.txt', { highwaterMark : 16 });
const zlibStream = zlib.createGzip();
const writeStream = fs.createWriteStream('./writeme3.txt');
readStream.pipe(zlibStream).pipe(writeStream);
  • 큰 파일 만드는 예.
const fs = require('fs');
const file = fs.createWriteStream('./big.txt');

for (let i = 0; i <= 10_000_000; i++) {
    file.write('안녕하세요. 엄청나게 큰 파일을 만들어 볼 것입니다. 각오 단단히 하세요!\n');
}
file.end();
  • 큰 파일 만들어서 메모리 비교 (버퍼 방식 / 스트림 방식 비교)
# buffer-memory.js
const fs = require('fs');

console.log('before : ', process.memoryUsage().rss);

const data1 = fs.readFileSync('./big.txt');
fs.writeFileSync('./big2.txt', data1);
console.log('buffer : ', process.memoryUsage().rss);


출력 결과
before :  19705856
buffer :  1020694528




# stream-memory.js
const fs = require('fs');

console.log('before : ', process.memoryUsage().rss);

const readStream = fs.readFileSync('./big.txt');
const writeStream = fs.createWriteStream('./big3.txt');
readStream.pipe(writeStream);
readStream.on('end', () => {
    console.log('stream : ', process.memoryUsage().rss);
});


출력 결과
before :  19677184
stream :  29220864

## 파일 및 폴더 생성

const fs = require('fs').promises;
const constants = require('fs').constants;

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('폴더 만들기 성공');
        return fs.open('./folder/file.js', 'w');
    })
    .then((fd) => {
        console.log('빈 파일 만들기 성공 ', fd);
        fs.rename('./folder/file.js', './folder/newfile.js');
    })
    .catch((err) => {
        console.error(err);
    });
  • fs.access(경로, 옵션, 콜백) : 폴더나 파일에 접근할 수  있는지 체크함. 두 번째 인자로 상수들을 넣음. F_OK는 파일 존재 여부 / R_OK는 읽기 권한 여부 / W_OK는 쓰기 권한 여부를 체크한다. 파일/폴더나 권한이 없다면 에러가 발생, 파일/폴더가 없을 때의 에러 코드는 ENOENT로 설정.
  • fs.mkdir(경로, 콜백) : 폴더를 만든느 메서드. 이미 폴더가 있다면 에러가 발생하므로 먼저 access() 메서드를 호출해서 확인하는 것이 중요.
  • fs.open(경로, 옵션, 콜백) : 파일의 아이디(fd 변수)를 가져오는 메서드. 파일이 없다면 파일을 생성한 뒤 그 아이디를 가져온다. 가져온 아이디를 사용해 fs.read()나 fs.write()로 읽거나 쓸 수 있다. 두 번째 인자로 어떤 동작을 할 것인지 설정할 수 있다. 쓰려면 w, 읽으려면 r, 기존 파일에 추가하려면 a로 설정. 예제에서는 w로 설정했으므로 파일이 없을 때 새로 만들 수 있었음. r이었다면 에러가 발생하였을 것임.
  • fs.rename(기존 경로, 새 경로, 콜백) : 파일의 이름을 바꾸는 메서드. 기존 파일의 위치와 새로운 파일 위치를 적어주면 된다. 반드시 같은 폴더를 지정할 필요는 없으며 잘라내기 같은 기능을 할 수도 있다.

## 폴더 내용 확인 및 삭제

  • fs.readdir(경로, 콜백) : 폴더 안의 내용물을 확인할 수 있다. 배열 안에 내부 파일과 폴더명이 나온다.
  • fs.unlink(경로, 콜백) : 파일을 지울 수 있다. 파일이 없다면 에러가 발생하므로 먼저 파일이 있는지를 꼭 확인해야 한다.
  • fs.rmdir(경로, 콜백) : 폴더를 지울 수 있다. 폴더 안에 파일이 있다면 에러가 발생하므로 먼저 내부 파일을 모두 지우고 호출해야 한다.
const fs = require('fs').promises;

fs.readdir('./folder')
    .then((dir) => {
        console.log('폴더 내용 확인 ', dir);
        return fs.unlink('./folder/newFile.js');
    })
    .then(() => {
        console.log('파일 삭제 성공');
        return fs.rmdir('./folder');
    })
    .then(() => {
        console.log('폴더 삭제 성공');
    })
    .catch((err) => {
        console.error(err);
    });

## 파일을 복사하는 방법

const fs = require('fs').promises;

fs.copyFile('readme4.txt', 'writeme4.txt')
    .then(() => {
        console.log('복사 완료');
    })
    .catch((error) => {
        console.error(error);
    })

## 파일을 감시하는 방법(변경 사항 발생 시 이벤트 호출)

  • target.txt 파일 바뀔 때 이벤트 호출하는 예.
const fs = require('fs');

fs.watch('./target.txt', (eventType, filename) => {
    console.log(eventType, filename);
});
반응형

+ Recent posts