본문 바로가기

프로그래밍./Linux Programming

[Linux] 저수준 파일처리. open(), read(), write(), lseek() 간단 예제.

저수준 파일처리. open(), read(), write(), lseek(), close() 간단 예제.
저수준 파일처리. open(), read(), write(), lseek(), close() 간단 예제.
저수준 파일처리. open(), read(), write(), lseek(), close() 간단 예제.


<파일처리>
 * 표준 입출력 함수 사용
 : 모든 OS 에서 사용할 수 있는 함수

 - ANSI C 에서 제공하는 표준함수임.

 * 저수준 입출력 함수 사용
 : UNIX / LINUX 에서 제공되는 함수
 - 외부 입/출력 장치도 파일로 다룸....
 - 파일의 속성을 제어하는 함수도 제공됨.


//*****************
  파일 처리 작업 순서
-------------------

 1. 파일포인터 또는 파일지시자 변수 선언
   (표준입출력)     (저수준입출력)


 2. 작업 대상 파일을 연다.(open)
   : 파일포인터 또는 파일지시자가 대상파일의 주소 또는 정보를 참조하게 됨.

 3. 파일안의 내용 입력(read) 또는 출력(write) 처리함

 4. 작업이 끝나면 반드시 대상 파일을 닫는다.(close)

 


//********************************
 저수준 파일처리
---------------------------------
  * UNIX / LINUX 에서의 파일 입출력 : open(), read(), write(), lseek(), close() 함수


  * 리눅스 에서는 파일 뿐만 아니라 연결된 모든 장치들을 파일로 간주하고 처리하므로 파일입출력 매우 중요함.


 open() 
 #include <sys/types.h>
 #include <sys/stat.h>
 #include <fcntl.h>

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


 - pathname : 경로정보가 포함된 파일명(문자열값)

 - flags : 3 개의 상수값 중 하나 사용
  * O_RDONLY : 읽기 가능 상태로 열기
  * O_WRONLY : 쓰기 가능 상태로 열기
  * O_RDWR : 읽기/쓰기 가능 상태로 열기

 - mode : 선택 사항
  * O_CREAT : 파일이 없으면 새로 생성됨.(접근 허가를 지정하는 인자가 필요함)
  * O_APPEND : 파일 쓰기시 파일의 끝에 내용 추가.
  * O_TRUNC : 파일이 이미 존재하고, 쓰기 권한 또는 읽기/쓰기 권한으로 성공적으로 파일이 열리면,
                      파일의 크기를 0으로  만듦.




close() 
 #include <unistd.h>


 int close(int fd);
 - 리턴값 : 0    - 성공적으로 닫힘
               -1   - 파일닫기 실패 전역변수 errno 에 오류번호가 등록됨


 

read()  : 문자열 단위로 파일의 내용 읽기 함수

 #include <unistd.h>


 ssize_t read(int fd, void *buf, size_t count);
 - 실패시 -1 리턴함.



open()과 close()를 이용한 파일 유무 확인 예제.


 

실행 시에 실행명령과 찾을 파일명을 입력해야 하기 때문에,
main의 매개변수가 최소 2개 이상이어야 한다. (명령값, 매개변수)
따라서 매개변수를 제대로 입력하지 않았다면, if(argc < 2) 에서 체크해
올바른 사용법을 출력해주고 프로그램을 종료한다.

매개변수가 2개 이상이라면.
해당 매개변수의 1번이 찾고자 하는 파일명이므로 open()을 사용해 파일을 연다.
만약 -1을 반환한다면, 파일이 정상적으로 열리지 않는 것이며 이것을 파일이 없다고 판단해서
에러 메시지를 출력해주고 프로그램을 종료한다.

이상 없이 정상적으로 열였다면 해당 파일이 있다고 판단해서, 파일이 존재한다고 알려준다.
정상적으로 열였으므로, 당연히 파일을 닫는다.

open() 할 때와 close() 할 때 fd 라는 int 값이 사용되었는데,
이는 일종의 open()한 파일의 정보를 가진다고 생각하면 이해하기 쉽다.
이 정보가 있어야 close() 할 때도 이 값을 이용해서 해당 파일을 닫는다.

close()가 아니더라도 파일 처리시에 많이 사용되며, 꼭 open() 에 대한 반환 값을 받도록 하자.



[open()과 close()를 이용한 파일 유무 확인 예제 결과]


test 라는 파일은 없다고 나오며, original 이라는 파일은 찾았다고 표시 되었다.






write() : 문자열 단위로 파일에 기록하는 함수

 #include <unistd.h>

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

 

=> 파일 안 내용 비교 : diff file_copy.c target.c

write()를 이용한 파일에 내용 쓰기(새로쓰기) 예제


 

기본적으로. fd는 위에서 설명했고. mode_t 가 있는데.
요놈들은 헤더파일에 이미 정의 되어 있는 것이다. open() 시에 사용할 것들인데
open() 안에 직접 써줘도 되지만 편하게 미리 변수에 담아 둔 것이다.

flag에서 보면 쓰기만 가능하고, 파일이 없으면 생성하고,
파일이 있다면 안의 내용을 모두 삭제하고 열기 하는 옵션이 들어 있다.

따라서 원래 파일이 있던 없던, 안에 내용이 있던 없던
오로지 내가 입력할 내용(buf) 만 들어가게 된다. 추가가 아닌 새로 작성인 셈이다.

.... main의 매개변수 사용할 건데 해당 조건절이 빠졌다... - -;; 실행에는 문제 없지만
예외처리를 해주지 않는다; 위의 예제에 있는 예외처리를 동일하게 사용하도록 하자.

open()으로 정상적으로 파일이 열리 지 않았다면, -1을 반환하며 if문에서 걸려서
에처 메시지를 출력하고 프로그램을 종료 한다.

정상적으로 열렸다면 if문을 실행하지 않고 다음에 있는 write를 실행.

write(대상파일, 쓸 내용, 입력할 문자 수)

open()에서 해당 파일의 정보등을 fd로 받았으며, 그래서 fd를 입력해 주면 되고
입력하고 싶은 내용은 buf에 담겨 있다. 그리고 내용이 있어도 그 내용중에서 몇 글자를 입력할지 알려줘야 한다.

우리는 buf의 모든 내용을 입력하고 싶으므로 strlen()을 이용해서 buf의 크기만큼을 입력해 주었다.

cnt에는 write() 사용시 작성된 글자수. 즉, 바이트 수가 나타난다. 따라서 내가 얼마만큼의 크기를 입력했는지 알 수 있다.

[write()를 이용한 파일에 내용 쓰기 예제 결과]


create 라는 파일에 21바이트 write 했다고 나오며,
해당 파일의 내용을 열어보면 내가 입력한 내용(buf)이 들어 있다.




write()를 이용한 파일에 내용 쓰기(이어쓰기) 예제


 

위의 새로쓰기와 동일하지만 flag의 옵션이 O_APPEND 이다.
이는 파일이 존재 할 때, 그대로 열고 가장 마지막에 내가 작성할 내용을 추가 하는 옵션이다.

위 프로그램을 반복해서 실행 한다면. 같은 내용이 계속 추가 되는 것을 알 수 있다.


[write()를 이용한 파일에 내용 쓰기(이어쓰기) 예제 결과]


 

original 이란느 파일에 123456789를 몇번 채워 놓고 append를 실해 시켜봤다.
가장 마지막 줄에 File append test file. 가 추가 된 것을 확인 할 수 있었다.







 
lseek() : 파일 지시자(file descriptor) 의 위치 재조정함수

 #include <sys/types.h>
 #include <unistd.h>


 off_t lseek(int fildes, off_t offset, int whence);
 - whence : 기준 위치 지정


 SEEK_SET : 시작위치      예) 파일의 맨 처음으로 이동하고자 한다면
  lseek(fd, 0, SEEK_SET);

SEEK_CUR
: 현재위치      예) 현재 위치에서 앞으로 10 바이트 이동
  lseek(fd, -10, SEEK_CUR);


SEEK_END : 맨 끝           예) 파일의 맨 끝으로 가고자 한다면
  lseek(fd, 0, SEEK_END);

 - 실패시 : -1 리턴되고, errno 변수에 오류번호 설정됨
  * ERADF : 잘못된 파일 지시자임
  * ESPIPE : files 가 pipe, socket 또는 FIFO과 관련되어 있을 때
  * ENIVAL : whence 가 적당한 값이 아닐 때


=> 파일안의 널 값 확인해 봄 : od 명령 사용

















 

Tip_1. lseek() 에서 현재 위치부터 -1 이동이 아닌 -2인 이유.

전체 크기 = 전체 글자 + 1  마지막 null 문자가 포함된다.
123456789(Null) 총 10글자 이며 10바이트.
          
(마지막 한칸은 편의상 그려둠) 

 1  2  3  4  5  6  7  8  9  N  
                                                                       ↑ SEEK_END 의 위치이다.

lseek()를 이용해서 파일의 끝에 커서를 옮겼으므로 SEEK_CUR 또한 동일한 위치이다.
 1  2  3  4  5  6  7  8  9  N  
                                                    ↑ SEEK_CUR.

내가 원하는 것은 9부터 출력되는 것이므로, 현재 커서를 2칸을 옮겨야 한다.
 1  2  3  4  5  6  7  8  9  N  
                                     ↑ SEEK_CUR.

lseek()는 커서를 옮기는 것과 같다고 생각하면 편하다.

 따라서 read를 사용해 1글자를 읽었다면 커서는 한 칸 이동 되는 것.
 1  2  3  4  5  6  7  8  9  N  
                                            ↑ SEEK_CUR.

그러므로 9의 이전인 8을 출력하고 싶다면 다시 2칸을 옮겨야 한다.
 1  2  3  4  5  6  7  8  9  N  
                            ↑ SEEK_CUR.




Tip_2. while문에서 조건이 size가 아닌 size-1 인 이유.

위의 내용을 이해 했겠지만, 총 10글 중 마지막 null은 출력할 필요가 없으므로.
9부터 1까지 출력이기 때문에. 마지막 널을 뺀 size-1 만큼 반복.



Tip_3. while문 밑에 마지막 print("\n"); 의 이유.

1앞에는 한줄을 내리는 내용이 없다.
역으로 출력을 하게 되면 1이 마지막이 되는데 다음줄로 넘어가지 않고
바로 프롬프트가 붙어 버린다. 321[root@....]
그래서 보기 편하기 위해서 붙여줌.

만약 9의 끝에 다음줄로 넘어가게 되어 있더라도. 역순이기 때문에
가장 첫줄에 공백줄이 나오고, 그 다음에 문자가 나오게 된다.
역시 마찬가지로 1 다음에 다음줄로 넘어가지 않는다.

[reverse 의 결과]













유용한 정보가 되셨다면 아래 손가락 버튼 한번 눌러주세요 ^-^