파일입출력


리눅스 상에서 파일에 대한 입출력을 소개합니다. c언어에서의 라이브러리가 아닌 시스템 콜에는 open, read, write가 있는데요. 이것을 잘 포장하여 만든것이 fopen, fread, fwrite라는 함수랍니다. 이제 우리는 원시적인 시스템 콜을 가지고 파일에 대한 입출력을 하려고 합니다.


open


int open(const char *pathname, int flags);

int open(const char *pathname, int flag, mode_t mode);


open은 두개의 시스템 콜이 있습니다. 


pathname : 파일의 경로와 이름입니다. 절대경로의 파일명을 주어도 되고 상대경로의 파일명을 주어도 됩니다.

flags : 파일을 어떻게 열지를 결정합니다. 읽기 전용으로 열때는 O_RDONLY, 쓰기 전용으로 열때는 O_WRONLY, 읽기 쓰기로 열고 싶을때는 O_RDWR을 사용합니다.




이 밖에도 자주쓰이는 flag들에 대해서는 아래를 참고하세요.


flag 

설명 

O_CREAT 

 파일이 없으면 생성합니다.

O_EXCL

 O_CREAT과 같이 사용되며 파일이 이미 존재하면 에러나 파일을 여는데에 실패합니다.

O_TRUNC

 파일이 이미 존재한다면 이미 존재하는 파일의 내용을 무시하여 엽니다.

 O_APPEND

파일에 내용을 추가할 수 있는 옵션입니다. 

 O_NONBLOCK

O_NDELAY

파일을 블록모드가 아닌 비블록모드로 엽니다. 기본적으로 파일은 블록모드입니다. 입력이 없을때는 입력할때까지 기다린다고 이해하시면 됩니다. 


여러개의 flag들을 사용하고 싶을때는 | (OR) 연산을 사용합니다. 예를 들어 읽기 쓰기 모드로 열고 싶은데 파일이 없으면 생성하는 flag는 O_RDWR| O_CREAT로 사용하면 됩니다.


mode : 파일을 생성할때의 권한을 주는 옵션입니다.

만약 0777을 주게 된다면 모두 읽기, 쓰기, 실행 권한을 주게 되는 것이죠. 또한 가독성을 높이기 위해 심볼릭 상수도 제공하고 있습니다. 심볼릭 상수를 사용할때 역시 | 연산으로 권한을 줄 수 있습니다.


반환값 : 성공적으로 파일을 열게되면 파일 디스크립터를 반환합니다. 그렇지 않으면 음수를 반환하죠.



read

ssize_t read(int fd, void* buf, size_t len);


fd : 파일 디스크립터입니다. open은 정상적으로 파일을 열면 그 파일에 파일디스크립터를 반환하죠? 그 파일디스크립터를 써주면 됩니다. 하지만 표준 입력과 표준 출력, 표준 에러는 각각 순서대로 0, 1, 2가 되므로 표준 입력으로 읽어들일때는 0을 써주면 됩니다.

buf : 파일에서 읽어들일 버퍼를 말하고 있습니다. 어떤 자료형으로 읽어올지 모르므로 void*로 매개변수로 받습니다.

len : 얼마만큼 읽어올지를 결정합니다.

반환값 : 정상적으로 파일에 대한 내용을 읽어온다면 읽은 바이트수를 반홥합니다. 즉 len의 값을 반환하죠. 그렇지 않다면 0을 반환합니다.

 



write

파일에 내용을 기록합니다. 사용방법은 read와 거의 비슷합니다.


size_t write(int fd, const void *buf, size_t nbytes);


fd : 파일 디스크립터랍니다. read의 파일디스크립터를 받는 것처럼 사용합니다.


buf : 버퍼에 쓸 내용을 말합니다.


nbytes : 실제 버퍼에서 얼마만큼의 길이를 파일에 쓸것인지를 결정합니다.


반환값 : 올바로 write에 성공했다면 쓰여진 bytes수, 그렇지 않다면 -1을 반환합니다.



파일 쓰기, 읽기

이제 이것을 바탕으로 표준 입력으로 내용을 입력받아 파일에 내용을 기록할 겁니다. 그리고 난 후에는 쓴 파일에서 그 내용을 읽어 표준출력으로 출력할 겁니다.


여기에 그 코드가 있습니다.



#include <stdio.h>
#include <fcntl.h>
#include <stdlin.h>
#define BUF_SIZE 1024

int main(){
int fd,n;
        char buf[BUF_SIZE];
        fd=open("file.txt",O_CREAT|O_RDWR|O_TRUNC);
        if(fd<0){
                printf("file open error\n");
                exit(1);
        }
        n=read(0,buf,BUF_SIZE);
        n=write(fd,buf,n);
        if(n<0){
                printf("file write error\n");
                exit(1);
        }
        lseek(fd,0,SEEK_SET);

        n=read(fd,buf,BUF_SIZE);
        if(n==0){
                printf("this file is  empty\n");
                exit(1);
        }
        n=write(1,buf,n);
        if(n<0){
                printf("file write error\n");
                exit(1);
        }
        close(fd);

} 


fd=open("file.txt",O_CREAT|O_RDWR|O_TRUNC);

우선 파일을 열어야겠죠? open 시스템콜을 이용합니다. 파일명은 file.txt이고, 파일이 없으면 만들고 읽기쓰기 전용으로 엽니다. 그리고 파일의 내용이 있다면 그 내용을 잘라내고 씁니다.


n=read(0,buf,BUF_SIZE);
n=write(fd,buf,n);

파일을 여는데에 성공했다면 표준입력(키보드)로 데이터를 읽어옵니다.


그 이후 file.txt에 그 내용을 기록합니다.


lseek(fd,0,SEEK_SET);

기록을 했다면 파일 포인터는 파일의 맨끝을 가리키고 있겠네요. 그러니 lseek이란 함수를 통해서 파일 포인터의 위치를 가장 처음으로 위치시킵니다. 

lseek에 대해서 간단히 설명하자면 파일이 어디부터 읽고 쓰는지에 대한 위치를 변경하는 함수라고 기억하시면 됩니다. 

파일 디스크립터 fd를 갖고 처음(SEEK_SET), 현재 위치(SEEK_CUR), 끝 (SEEK_END)을 일단 정하고 난 후에 상대적인 값을 통해 파일 포인터의 위치를 결정합니다.





n=read(fd,buf,BUF_SIZE);
if(n==0){
        printf("this file is  empty\n");
        exit(1);
}
n=write(1,buf,n);

이제 read를 통해서 file.txt에서 내용을 읽고 표준 출력으로 출력하는 프로그램입니다.


어때요? 이해 되셨나요?


이제 컴파일하고 실행시킵시다. 그리고 file.txt라는 파일에 기록이 됐는지 확인해봅시다.


[root@localhost ~] # gcc file.c

[root@localhost ~] # ./a.out

Hello, world!

Hello, world!

[root@localhost ~] # cat file.txt

Hello, world!


파일에도 기록이 잘 되어있고 출력도 잘 된것을 확인할 수 있습니다. 어렵지 않죠!?

블로그 이미지

사용자 REAKWON

와나진짜

댓글을 달아 주세요

  • msms772@naver.com 2021.05.09 13:09  댓글주소  수정/삭제  댓글쓰기

    안녕하세요. 이제 막 리눅스를 배우고있는 새내기입니다 ㅠㅠ
    과제에서 기존의 코드가 있고, copy.out으로 이름을 지정, 자기 자신의 코드를 복사해서 copy2.out으로 복사하는 과제가 있는데
    키보드를 통한 표준입력이 아닌 기존의 코드를 복사하는 방법이 따로 있을까요..?

    • 사용자 REAKWON 2021.05.09 13:14 신고  댓글주소  수정/삭제

      copy.out을 copy2.out으로 내용을 복사하는 프로그램을 말씀하시는건가요?
      그렇다면 copy.out을 읽기전용RD_ONLY 으로 copy2.out을 쓰기전용WR_ONLY으로 여시고 copy.out을 read하고 난 이후에 copy2.out에 write하시면 됩니다

    • msms772@naver.com 2021.05.09 17:23  댓글주소  수정/삭제

      헉...갑갑한 마음에 댓글 달아봤는데 5분만에 댓글 달아주셨네요.
      현재상황: 일단 말씀하신대로 해보니 이게 왜 되지?? 하면서 코드 문제분석중입니다;;
      감사합니다!

  • rudwns708@naver.com 2021.07.25 09:12  댓글주소  수정/삭제  댓글쓰기

    버퍼 크기가 넘어가는 파일은 어떻게 읽어오죠 ㅠㅠ?