디렉토리, 파일과 관련한 더 많은 정보와 예제 코드 를 담은 리눅스 교재를 배포했습니다. 아래의 페이지에서 리눅스 교재를 받아가세요.

https://reakwon.tistory.com/233

 

리눅스 프로그래밍 note 배포

티스토리에 리눅스에 관한 내용을 두서없이 여지껏 포스팅했었데요. 저도 제 포스팅을 찾기가 어렵기도 하고 티스토리에서 코드삽입을 하게 되면 이게 일자로 쭉 쓰여져있는 x같은 현상이 생겨

reakwon.tistory.com

 

디렉토리 열기, 읽기, 닫기

디렉토리를 읽는 것은 파일을 읽는것과는 다른 함수들을 사용합니다. 오늘은 관련 함수들 간단히 살펴보고 사용하는 예를 보도록 하겠습니다.

우선 관련 함수를 사용하려면 아래와 같은 헤더파일을 include해야합니다. 

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

 

1) opendir

 DIR *opendir(const char *name);

 

opendir에 디렉토리 이름을 인자로 넣어주게 되면 정상적으로 종료시 DIR 포인터에 그 디렉토리에 대한 포인터가 반환이 됩니다. 에러시 NULL이 반환되고, errno에 에러 번호가 기록이 됩니다.

 

2) readdir

struct dirent *readdir(DIR *dirp);

 

디렉토리의 내용을 읽게 되면 그 디렉토리 안의 디렉토리나 파일이 dirent 구조체 포인터로 반환되게 됩니다. 이 함수는 주로 while문과 같이 사용됩니다.  dirent의 구조체 내용은 아래와 같습니다. 만약 읽을 파일이나 디렉토리가 없으면 NULL을 반환하게 됩니다.

struct dirent {
       ino_t          d_ino;       /* Inode number */
       off_t          d_off;       /* Not an offset; see below */
       unsigned short d_reclen;    /* Length of this record */
       unsigned char  d_type;      /* Type of file; not supported
                                      by all filesystem types */
       char           d_name[256]; /* Null-terminated filename */
};

 

주석만 잘 읽어도 위의 필드들이 무엇을 의미하는지는 알 수 있겠습니다. 

 

3. closedir

int closedir(DIR *dirp);

파일을 열고난 이후 close() 함수로 닫아주듯이 디렉토리 역시 마찬가지입니다. 이 함수가 closedir이고, opendir()에서 반환받은 DIR*를 전달해주면 됩니다. 에러없이 성공적으로 디렉토리를 닫았다면 0이 반환되고, 그렇지 않으면 -1이 반환됩니다. 이 역시 errno를 통해서 에러 번호를 확인할 수 있습니다.

 

디렉토리 내용 읽기

위의 함수들만을 이용해서 디렉토리의 내용들을 볼 수 있습니다. 아래는 그러한 예의 프로그램 코드입니다.

#include <dirent.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <string.h>
void read_dirent(char *dir_name){

        DIR* dir = opendir(dir_name);
        if(dir == NULL){
                printf("디렉토리를 열수 없습니다.\n");
                return;
        }

        struct dirent *entry;
        while((entry=readdir(dir))!=NULL){
                if(strcmp(entry->d_name,".") == 0 || strcmp(entry->d_name,"..") == 0) continue;
                printf("%s\n",entry->d_name);
        }

        closedir(dir);
}
int main(int argc, char *argv[]){
        if(argc != 2){
                printf("1개의 directory path가 필요합니다. \n");
                return 1;
        }

        read_dirent(argv[1]);
}

read_dirent() 함수는 간단합니다. 먼저 디렉토리의 이름을 인자로 받고 있습니다. 1. 이름을 통해서 opendir을 통해 디렉토리를 열고, 이후 2.read_dir로 반복적으로 그 디렉토리의 내용을 읽습니다. 마지막은 3,closedir로 닫는 역할을 합니다. 

# gcc dir_test.c
# ./a.out /etc/logrotate.d/
ufw
unattended-upgrades
cups-daemon
rsyslog
wtmp
ubuntu-advantage-tools
speech-dispatcher
apt
apport
bootlog
dpkg
ppp
alternatives
btmp

 

재귀적으로 하위 디렉토리까지 읽기

위의 코드를 조금만 바꾸고 lstat을 이용하게 된다면 재귀적으로 호출할 수도 있습니다. lstat에 대해서 모르신다면 아래 포스팅을 참고해보세요.

https://reakwon.tistory.com/40

 

[리눅스] 파일 정보 얻어오기(lstat), 사용 예제

파일 정보 얻어오기 파일에 대한 정보를 얻어올때는 lstat시스템 콜을 사용하면 됩니다. 사실 stat, lstat, fstat등 여러가지 시스템 콜이 있는데요, 가장 간단하게 사용할 수 있는 시스템 콜이기 때문

reakwon.tistory.com

 

#include <dirent.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <string.h>
#include <sys/stat.h>

void concat_path(char *dest, char* src1, char* src2){
        strcpy(dest,src1);
        strcat(dest,"/");
        strcat(dest,src2);
}
void read_dirent(char *dir_name){

        DIR* dir = opendir(dir_name);
        if(dir == NULL){
                printf("디렉토리를 열수 없습니다.\n");
                return;
        }

        struct dirent *entry;
        while((entry=readdir(dir))!=NULL){
                if(strcmp(entry->d_name,".") == 0 || strcmp(entry->d_name,"..") == 0) continue;
                struct stat buf;
                char full_path[256];

                concat_path(full_path, dir_name, entry->d_name);

                printf("%s\n",full_path);

                if(lstat(full_path, &buf) <0)
                        printf("lstat error\n");
                else{
                        if(S_ISDIR(buf.st_mode))        //디렉토리일 경우 재귀 호출
                                read_dirent(full_path);

                }

        }

        closedir(dir);
}
int main(int argc, char *argv[]){
        if(argc != 2){
                printf("1개의 directory path가 필요합니다. \n");
                return 1;
        }
        //오른쪽에 /가 들어올 수 있음. ex) /etc/ -> /etc
        if(argv[1][strlen(argv[1])-1] == '/') argv[1][strlen(argv[1])-1]='\0';

        read_dirent(argv[1]);
}

 

코드가 길어보이지만, 어렵지 않은 코드입니다. 바로 위의 코드에 비해서 1. 완전한 경로를 구할 수 있는 코드(concat_path)를 추가한것2. 재귀적으로 호출하는 부분이 전부입니다. 이렇게 구현하게 되면 아래와 같이 재귀적으로 하위 디렉토리까지 출력하게 됩니다.

# ./a.out /usr/include
...
/usr/include/linux
/usr/include/linux/nilfs2_api.h
...
/usr/include/linux/netfilter
/usr/include/linux/netfilter/xt_HMARK.h
...

 

이제 디렉토리를 다루는 방법과 lstat에 대해 이해하셨다면 ls의 a, l, i, R 옵션등을 구현해낼 수 있을 것입니다. 그것은 여러분들이 직접 구현해보세요. 어렵지 않습니다.

반응형
블로그 이미지

REAKWON

와나진짜

,

반복문 for

C언어에는 여러 반복문이 존재합니다. for문, while문, do ~ while문이 있지요. 오늘은 가장 흔하게 쓰이는 for문에 대하여 이야기해보도록 하겠습니다. 

for문에 대한 형식은 일반적으로 아래와 같은 형태입니다.

1. 초기화 식이 먼저 수행됩니다. 이는 for문 최초 1회만 수행됩니다.

2. 이후 조건식을 보고 true이며 for문 내부에 있는 코드를 수행하고, false이면 for문을 멈추고 빠져나옵니다.

3. 조건식이 참일 경우 수행되는 내부 부분입니다.

4. 이후 증감식을 수행한 후 다시 2의 조건식에 맞는지 확인합니다. 

이후 2 ~ 4의 반복입니다.

 

예 ) 1~10까지의 합

이러한 기본 형식을 지킨 1부터 10까지 모두 더한 수를 구한 예제입니다.

#include <stdio.h>

void main() {
	int i;
	int sum = 0;
	for (i = 1; i <=10; i++) {
		sum += i;
	}
	printf("1부터 10까지의 합 : %d\n", sum);
}

 

중첩 for문 

for문안에 for문이 등장하여 중복적으로 반복문을 수행할 수도 있습니다. 이를 중첩 for문, 혹은 2중 for문이라고 합니다. 원리는 간단합니다. 수행 부분이 내부 for문으로 바뀌는 거거든요. 아래는 이와 관련된 구구단의 예제입니다.

int i, j;
for (int i = 2; i <= 9; i++) {
	for (int j = 1; j <= 9; j++) {
		printf("%d * %d = %d\n", i, j, i * j);
	}
	printf("\n");
}

 

for문의 제어 - continue, break 

상황에 따라서 이번 수행부분은 건너뛰거나, 아예 for문을 멈춰야하는 상황이 발생할 수 있습니다. 이럴 경우 쓰이는 키워드가 if와 함께 continue와 break입니다.

continue : 이후 for문의 실행부분을 건너뜁니다.

break : break가 있는 for문을 종료시킵니다. 2중 for문의 경우 break가 등장하는 for문을 멈춰버립니다.

예 ) 1~10까지 짝수만 출력

for (int i = 1; i<=10; i++) {
	if (i % 2 == 1) continue;
	printf("%d \n", i);
}

 

예) 특정수와 일치하는 배열의 index 찾기

void main() {
	
	int numbers[] = { 5,4,2,1,9,11 };
	int found_index = -1;
	for (int i = 1; i<sizeof(numbers); i++) {
		if (numbers[i] == 9) {
			found_index = i;
			break;
		}
	}
	if (found_index != -1) {
		printf("9의 배열 index:%d\n", found_index);
	}
	else {
		printf("9를 찾을 수 없음\n");
	}
}

 

for문의 다양한 사용법 

for문은 일반적으로 이런식으로 사용한다는 것을 보여드렸습니다. 초기화식, 조건식, 증감식은 모두 생략이 가능합니다. 아래와 같이 다양하게 사용할 수 있습니다.

for(;;) // 무한 루프
for(i=1;;) //무한 루프
for(;i<5;i++) //초기화 구문 생략
for(;;i++)	//i가 1씩 증가하는 무한루프
for(i=1,j=1;;i++,j++)	//두가지 변수를 ,로 동시에 증감이 가능

 

for문 활용 - 별 출력

for문의 가장 대표적인 예제로는 별찍기가 있는데요. 한번 풀어보도록 합시다. 처음 몇개는 쉬우니까 우선 풀이는 생략하고 코드만 올리도록 하겠습니다.

1. 

*
**
***
****

아래는 코드입니다.

void main() {
	printf("*\n");
	printf("**\n");
	printf("***\n");
	printf("****\n");
}

장난이구요. 

void main() {
	int i, j;
	for (i = 1; i <= 4; i++) {
		for (j = 1; j <= i; j++)
			printf("*");
		printf("\n");
	}
}

 

2.

****
***
**
*
void main() {
	int i,j;
	for (i = 4; i > 0; i--) {
		for (j = 1; j <= i; j++)
			printf("*");
		printf("\n");
	}
}

 

3. 

   * 
  ***
 *****
*******

여기서부터 좀 많이 헤메실수 있어서 풀이방법도 기재해드립니다. 우선 코드분석해보시고 풀이를 봐주세요.

#include <stdio.h>

void main() {
	int i, j, height;
	scanf("%d", &height);
	for (i = 1; i <= height; i++) {
		for (j = 1; j <= height - i; j++) //공백 먼저 출력
			printf(" ");
		
		for (j = 1; j <= i * 2 - 1; j++)  //*출력
			printf("*");
		printf("\n");
	}
}

 

우선 i는 *과 공백을 포함한 한 줄을 의미합니다. 공백과 별을 어떻게 출력하는지는 아래와 같은 규칙을 갖게 됩니다. 아래의 그림은 높이가 4인 삼각형 모양의 별을 나타내었습니다.

i가 1일 경우 ) 우선 공백이 3개 출력되고, 별이 1개 출력이 됩니다.

i가 2일 경우 ) 공백이 2개 출력되고, 별이 3개 출력됩니다.

i가 3일 경우 ) 공백이 1개, 별이 5개 출력됩니다.

i가 4일 경우 ) 공백이 0개, 별이 7개 출력됩니다.

자, 이제 일반화하게 되면, 아래와 같은 규칙을 얻게 되지요.

공백 : height - i 까지 출력

별 : i*2 -1까지 출력

그래서 위의 코드와 같이 쓸 수 있습니다.

4.

*******
 *****
  ***
   *

이거는 i를 처음부터 height값으로 놓고 --i로 거꾸로 돌리면 됩니다.

void main() {
	int i, j, height;
	scanf("%d", &height);
	for (i = height; i >= 0; i--) {
		for (j = 1; j <= height - i; j++) //공백 먼저 출력
			printf(" ");
		
		for (j = 1; j <= i * 2 - 1; j++)  //*출력
			printf("*");
		printf("\n");
	}
}

 

4.

   *
  ***
 *****
*******
 *****
  ***
   *

 

위의 두 코드를 합치면 되는데, 중간에 같은줄이 두개 나오기 때문에 밑에는 height -1로 하여 한번 빼줍시다.

	for (i = 1; i <= height; i++) {
		for (j = 1; j <= height - i; j++) //공백 먼저 출력
			printf(" ");

		for (j = 1; j <= i * 2 - 1; j++)  //*출력
			printf("*");
		printf("\n");
	}

	for (i = height-1; i >= 0; i--) {
		for (j = 1; j <= height - i; j++) //공백 먼저 출력
			printf(" ");

		for (j = 1; j <= i * 2 - 1; j++)  //*출력
			printf("*");
		printf("\n");
	}

 

이상으로 별까지 출력하는 코드로 for문을 활용해보았습니다. for문은 배열과 같이 많이 쓰이기 때문에 반드시 알아야하는 반복문입니다.

반응형
블로그 이미지

REAKWON

와나진짜

,

switch ~ case

switch ~ case 문법은 if문과 비슷하지만 약간은 다른 문법인데요. if 문은 괄호안에 조건식에 따라서 비교를 하고 조건식이 꽤나 복잡해질 수 있습니다. 이럴때 가독성이 조금 떨어지게 되지요. 하지만 switch case 문은 보다 명료하고 간단하게 조건에 따라 실행하는 case들이 나눠지기 때문에 가독성이 뛰어나다는 장점이 있죠. 이제 switch ~ case를 어떻게 사용하는가, 그리고 흔하게 할 수 있는 실수들은 무엇인가에 대해서 설명해보려고 합니다. 

1. 기본 사용법

switch case의 기본 템플릿은 아래와 같습니다.

switch : switch안에는 정수형으로 이 정수의 데이터를 가지고 case 옆의 데이터와 비교하게 됩니다.

case n : 실제 수행 부분입니다. swtich에 전달한 정수형 인자와 일치하면 그 case문을 실행하게 되는거죠.

default : case에 모두 포함되지 않을 경우 default 안쪽의 코드가 실행됩니다. default는 생략할 수도 있습니다.

 

switch(정수형){
case 1: 
//...//
break;
case 2:
//...//
break;
case 3:
//...//
break;
//...//
default:
//...//
}

 

아래는 switch ~ case를 이용한 간단한 예제입니다. 

#include <stdio.h>

void create_new_game() {
	printf("새로운 게임 만들기\n");
}

void continue_game() {
	printf("지난 게임 계속\n");
}
void game_setting() {
	printf("게임 설정\n");
}
void exit_game() {
	printf("게임 종료\n");
}
void main() {

	int selection;
	printf("1. 새게임\n");
	printf("2. 계속\n");
	printf("3. 설정\n");
	printf("4. 종료\n");

	scanf("%d", &selection);
	switch (selection) {
	case 1: 
		create_new_game();
		break;
	case 2:
		continue_game();
		break;
	case 3:
		game_setting();
		break;
	case 4:
		exit_game();
		break;
	default:
		printf("없는 옵션입니다\n");
	}
}

 

switch안에는 정수형 인자를 넣어야한다고는 했지만, 문자도 가능합니다. 문자도 내부적으로 정수로 처리하게 되니까요. 

void main() {

	char selection;
	printf("a. 새게임\n");
	printf("b. 계속\n");
	printf("c. 설정\n");
	printf("d. 종료\n");

	scanf("%c", &selection);
	switch (selection) {
	case 'a': 
		create_new_game();
		break;
	case 'b':
		continue_game();
		break;
	case 'c':
		game_setting();
		break;
	case 'd':
		exit_game();
		break;
	default:
		printf("없는 옵션입니다\n");
	}
}

 

 

단, 문자열의 경우에는 사용할 수 없습니다.

 

2. break가 없는 경우

case 마지막에 break를 항상 입력하는 것을 볼 수가 있죠? 왜 그렇게 될까요? 위 코드에서 break만 없애서 실행을 해보도록 하겠습니다.

void main() {

	char selection;
	printf("a. 새게임\n");
	printf("b. 계속\n");
	printf("c. 설정\n");
	printf("d. 종료\n");

	scanf("%c", &selection);
	switch (selection) {
	case 'a': 
		create_new_game();
	case 'b':
		continue_game();
		
	case 'c':
		game_setting();
	case 'd':
		exit_game();
	default:
		printf("없는 옵션입니다\n");
	}
}

 

저는 b를 선택하였음에도 b 이후 모든 case가 동작하게 됩니다. break라는 것은 곧 멈추라는 의미로 break가 없으면 선택 case 이후의 모든 case를 수행하게 됩니다. 

 

3. case안에서 변수 선언

case안에서 특정 변수를 선언하고 코드를 짜보도록 합시다. 코드를 아래처럼 바꿔서 실행해봅시다.

#include <stdio.h>

void create_new_game(char *new_game_name) {
	printf("새로운 게임 만들기 :%s\n",new_game_name);
}
//... 생략 ...//
void main() {

	char selection;
	printf("a. 새게임\n");
	printf("b. 계속\n");
	printf("c. 설정\n");
	printf("d. 종료\n");

	scanf("%c", &selection);
	switch (selection) {
	case 'a': 
		char new_game_name[32];
		printf("새로운 게임 이름:");
		scanf("%s", new_game_name);
		create_new_game(new_game_name);
		break;
        //... 생략 ...//
	}
}

그리고 비주얼 스튜디오를 보면 빨간줄이 딱 떠있을 겁니다.

 

"선언에는 레이블을 사용할 수 없습니다." 

그러나 그냥 실행해도 문제없이 실행됨을 알 수 있는데, 이건 컴파일러에 따라서 그냥 실행할수 있게 해주는 경우입니다. 그렇지 않은 경우 아래와 같은 오류가 발생하며 컴파일이 안되는 경우가 있죠.

다음은 리눅스에서 위와 완전히 동일한 코드를 gcc를 이용하여 컴파일한 경우입니다. 같은 에러가 발생하면서 컴파일이 안되는 경우를 볼 수가 있죠?

 error: a label can only be part of a statement and a declaration is not a statement

 

해결방법은 매우 간단합니다. 중괄호로 case를 묶으면 에러가 발생하지 않습니다. 간단하죠?

 

이상으로 너무나 사용하기 쉬운 switch ~ case 문법에 대해서 알아보았습니다. 마지막에 대한 switch ~ case에 대한 실수는 많이 발생할 수 있으니, 알아두시면 인터넷 서치없이 바로 해결 가능합니다.

반응형
블로그 이미지

REAKWON

와나진짜

,