C & C++/C

[Linux] Ubuntu c programming - file open /read /write

Razelo 2021. 9. 30. 18:57

오랜만에 우분투에서 또다시 코드를 작성하게 되었다. 예전에는 리눅스에서 뭔가 작성한다고 하면 잔뜩 겁을 먹었었는데 지난번에 어셈블리어 수업을 듣게 된 이후로는 그나마 덜한 것 같다. 그전까지는 아예 기본적인 사용법조차 몰랐고 터미널에서 길을 잃어서 한참을 헤맸던 기억이 난다. 한마디로 별것도 아닌일때문에 삽질을 좀 많이 했었다. 그래도 지금은 좀 나아진것 같다. 

 

지난번에는 Vmware WorksStation을 사용해서 작성했었는데 이번에는 Oracle VM VirtualBox를 이용해서 우분투에서 작성하게 되었다. 두 제품의 차이에 대해서는 잘 모르는데 나중에 이부분도 따로 찾아서 정리해놓으면 좋을 것 같다. 근데 사실 막상 써보면 별 차이 없긴 하다. 

 

시스템 콜을 사용해서 아주 간단한 프로그램을 작성하였다. 

copy.c라는 이름으로 한 파일(afile)에서 다른 파일(bfile)로 내용을 복사해주는 간단한 프로그램을 작성하였다. 

기본적인 코드는 다음과 같다. 

#include<stdio.h>
#include<stdlib.h>
#include<fcntl.h>
#include<unistd.h>

#define BUFSIZE 512

int main(int argc, char *argv[]){

	char buffer[BUFSIZE];
	char *fname1 = argv[1];
	char *fname2 = argv[2];
      
	int fd1;
	int fd2; 
	int nread; 

        if((fd1 = open(fname1, O_RDONLY)) == -1){ // readonly open 
                printf("open %s\n", fname1);
		exit(1);
        }
	//creat(fname2, O_RDWR|O_APPEND); // create file bfile 
	if((fd2 = open(fname2, O_RDWR|O_APPEND)) == -1){   // read and write 
		printf("open %s\n", fname2);
		exit(1);
	}

	printf("start copying \n");
	while((nread = read(fd1, buffer, BUFSIZE)) > 0){ 
		printf("\n\n%d\n\n",nread);
		write(fd2, buffer, nread);
	}

	printf("finish\n"); 
        close(fd1);
	close(fd2); 
        exit(0);
}

간단하게 설명을 하면 

vi 에디터로 작성해준 다음 (vi copy.c 라고 작성하면 된다.)

더보기

vi 에디터 사용법은 따로 찾아보는 게 좋다. 

가장 기본적인 사용법은 vi [파일명] 을 작성하고 엔터를 누르면

vi 에디터로 진입하게 되는데 여기서 i 키를 누르면 저절로 --INSERT-- 모드로 바뀌는 것을 볼 수 있을 것다. 

만약 코드를 신나게 작성하다가 저장하고 싶다면? ESC를 한번 누르고 :w 를 치면 된다. 

그리고 그냥 밖으로 나가버리고 싶다면? 마찬가지로 ESC를 한번 누르고 :q를 치면 된다. 

 

그리고 저장하면서 밖으로 나가고 싶다면? ESC를 누르고 :wq 를 치면 된다. 

 

이후에 ls 명령어를 통해 확인해보면 파일이 잘 생성된 것을 확인할 수 있다. 

파일을 제거하고 싶다면? rm 파일이름을 쳐주면 된다.

 

가끔 vi내에서 신나게 코딩을 하다가 갑자기 아무키도 먹히지 않으면서 먹통이 되는 경우가 있는데 그럴때 터미널을 끄고 다시 작업하려고 들어가면 뭔가 불길한 안내문이 뜨면서 키를 입력하라고 뜨는 경우가 있다. 

아마 "버리기 or 저장 or 그냥 편집하기" 가 있을 텐데 그냥 편집을 계속 진행하고 저장한 후에 해당 디렉토리에 들어가서 파일 보기란에서 숨긴파일 표시를 클릭하면 컴파일한 파일과 관련이 없는 파일들이 잔뜩 생성된 것을 볼 수 있을 것이다. 이걸 삭제해주지 않으면 이후에도 계속해서 설정을 선택하고나서 작성해줘야 하니 귀찮다면 그냥 그 파일들을 지워주면 된다. 

 

위의 내용이 아주 기본적인 사용법이라 vi 사용법은 좀더 찾아보는 것이 좋다. 

 

gcc copy.c로 컴파일을 해주었다. 

 

이후에 a.out실행파일이 생성되는데 ./a.out afile bfile 을 실행했을때 위의 코드가 정상적으로 실행되게끔 작성하였다. 즉 두개의 인자를 전달해주는거다. 

 

처음에 int main(int argc, char *argv[]) 이 부분을 몰라서 고민을 많이 했다. 터미널에서 파일명을 전달해야하는데 어떻게 되는걸까? 이런 옵션은 없었는데 어떻게 전달받을 수 있을까를 생각했다. 그래서 중간에는 scanf 로 받으려고 했다. 

당연히 터미널에서 끔뻑끔뻑 아무작동도 안하길래 혹시나 내가 직접 afile bfile 입력하니 그제서야 작동했다. (너무 뻔한건데 처참히 실패했다.) 그리고 생각해보니 scanf 같은 입력으로 전달하는 것은 말이 안되고 따로 main에서 입력받는 방법이 있지 않을까 생각했다. (예전에 자바 프로그래밍을 할적에도 마찬가지로 실행전에 미리 main에 파라미터를 건넬 수 있는 방법이 있다는게 떠올랐다.) 그래서 찾아보니 int argc, char *argv[] 가 있었다. c에서의 main의 유형과 관련된 블로그 글을 읽은 적이 있었는데 거기서도 이런게 있구나 하고 가볍게 넘어간 적이 있어서 기억이 나지 않았던 거였다. 

argc는 개수를 나타낸다. 전달된 파라미터의 개수를 의미하고 argv는 당연히 그 요소를 담고 있다. 

 

따라서 코드를 보면 fname1과 fname2에 argv[1]과 argv[2]를 할당한 것을 볼 수 있다. 

아 참고로 신기한 것은 argv[0]에 ./a.out도 전달된다. 나는 이게 그저 실행하는 명령어이기 때문에 이 이후부터 전달은 안될 줄 알았는데 그냥 터미널에서 입력한 모든게 argv에 할당이 된다고 보면 된다. 

 

그렇게 작성을 해주고 if문이 2개가 있는데 파일을 open해주는 동작이다. 만약 -1을 반환한다면? 제대로 열리지 않은 것이다. 그러니 exit하여서 종료시켜준다. (잘되면 정상진행하겠죵) 아 그리고 exit은 stdlib.h가있어야 사용할 수 있다. (왜안되나 싶었다... include 까먹지 말고 해주자.)

 

 이후에 while문이 있는데 코드를 보면 알겠지만 BUFSIZE만큼씩 계속 읽는다. 읽은 건 당연히 buffer변수에 할당된다. 

fd1은 파일 디스크립터인데, 쉽게 말하면 파일의 번호이다. stdin은 0, stdout은 1, stderr는 2로 할당되있고 그 이후부터는 나머지 파일들에 할당이 된다. stdin stdout stderr의 파일 디스크립터 번호는 기본적으로 알아두면 좋겠다는 직감이 들었다. 언젠가 요긴하게 쓰일 것 같다. 

 

아무튼 그렇게 read를 해주고 buffer에 read한게 저장이 되있을텐데 그 내용을 write를 통해 옆의 파일(bfile)에 넣어주는거다. 

 

그리고 마지막으로 두 개의 파일을 모두 닫아줌으로써 끝이 난다. 상당히 간단한 프로그램이다. 그런데 여기서 조금 문제가 있었다.

 

문제는 바로 while문을 돌면서 read한걸 write해주는데 정작 bfile에 가면 afile내용이 복사되지 않고 텅텅 비어있는 것이 문제였다. 무엇이 문제일까 고민을 많이 했는데 처음에는 read자체가 아예 일어나지 않는 줄 알았다. 그래서 printf로 찍어봤는데 문제없었다. 

 

그래서 어디서 문제가 있을까 많이 생각을 했는데 결국 답을 얻었다. 

지금 두번째 if문을 보면 creat를 주석으로 쳐놓은게 보일것이다. 이 부분이 핵심이었다.

내가 파일 디스크립터 fd2를 얻을 적에 mode값으로 O_RDWR | O_APPEND | O_CREAT를 준 적이 있었는데 각각 <읽고쓰기, 덧붙이기 , 새로 만들기> 이렇게 값을 준 것이다. 

하지만 지금 코드를 보면 O_CREAT가 없다. 

이 부분이 문제였던 것이다. 

해당 파일 디스크립터를 가지고 while문을 돌다보니 bfile 파일에 read한 내용이 복사되면서 동시에 또 creat되고 있던 것이다. 그러니 결국에는 최종적으로는 빈 파일이 만들어지게 되는 것이었다. 

 

그래서 O_CREAT를 없앴다. 

 

사실 afile에서 bfile로 내용을 복사하는데 bfile이 없어도 자동으로 생성해주게 하는 프로그램을 만들고 싶었는데 O_CREAT에서 막혀서 끝까지 완성하지는 못했다. 

 

아마 방법이 있을 것 같은데 오늘은 시간이 없어서 이만 저장하고 보내는게 맞겠다. 

 

다음에 O_CREAT를 넣고도 제대로 복사가 되는 방법을 찾아봐야겠다. 

 

시스템 프로그래밍 너무 재밌다. 

반응형