메뉴 건너뛰기


Developer > Application

C,C++ Skeleton Filter Program in C

2013.11.26 00:45

푸우 조회 수:15277

출처: http://wiki.kldp.org/wiki.php/DevelFilterCSkeleton


이 글은 C 언어를 써서 DevelFilterSkeleon에서 다룬 필터 프로그램의 빼대를 만드는 방법을 소개합니다.

Contents

 [-]
1 Skeleton Filter Program in C
1.1 Output Messages
1.2 Option Processing
1.3 Skelton Template #1

1.1 Output Messages 

필터 프로그램은 어떤 상황이 발생했을 때, 사용자에게 보고하거나, 에러를 출력할 때가 종종 발생하기 때문에, 경고 또는 에러 메시지를 내 보내는 경우가 있습니다. 필터 프로그램은 기본적으로 처리한 데이터를 표준 출력을 통해 내 보내므로, 이런 메시지들을 표준 입력으로 보낸다면, 데이터와 섞일 경우가 있기 때문에 대부분 표준 출력을 써서 메시지를 출력합니다.

대개 필터 프로그램이 따로 출력하는 메시지는 주로 경고성이나, 에러가 발생한 경우가 대부분입니다. 만약 시스템 함수를 호출해서 에러가 발생한 경우에는 전역 변수 errno에 에러 원인이 저장되게 되는데 (모든 시스템 함수들이 errno를 쓰는 것은 아닙니다.), 이 경우 strerror(3)를 써서, 원하는 에러 메시지를 얻을 수 있습니다.

에러를 쉽게 처리하기 위해 우리는 GNU 확장 함수인 error(3)를 쓸 것입니다.

#include <error.h>

void error(int STATUS, int ERRNUM, const char *FORMAT, ...);

사용법은 간단합니다. 먼저, FORMAT과 그 다음에 들어오는 printf(3) 스타일 인자를 처리해서 표준 에러로 출력합니다. 그리고 ERRNUM이 0이 아닌 경우, error(3)는 strerror(ERRNUM)을 불러서 메시지를 표준 에러로 출력합니다. 그 다음, STATUS가 0이 아닌 경우 exit(STATUS)를 호출해 줍니다.

예를 들어 제작할 필터 프로그램이 지정한 설정 파일에서 원하는 설정을 읽어들인다고 가정해 봅시다. 이 설정 파일 이름은 INIT_FILE이란 매크로에 저장이 되어 있고, 이를 fopen(3)으로 열 때, 에러가 발생했다고 가정합시다. 또, 이 필터 프로그램은 지정한 설정 파일이 존재하지 않는 경우, 경고 메시지를 출력하고, 기본 값을 써서 동작한다고 가정합시다. 이 경우 우리는 다음과 같은 코드를 만들어야 합니다:

FILE *fp;
fp = fopen(INIT_FILE, "r");
if (!fp) {
  error(0, errno, "cannot open %s", INIT_FILE);
}

그러면 나중에 이 프로그램을 실행할 때 (프로그램 이름은 "foo"라고 가정합시다), INIT_FILE이 없는 경우, 다음과 같이 경고 메시지가 출력되는 것을 알 수 있습니다:

$ foo
foo: cannot open /etc/foorc: No such file or directory
$ _


1.2 Option Processing 

가장 먼저 처리해야 할 것은 command-line argument를 받아서 처리하는 것입니다. 이는 main() 함수를, 두 개의 파라메터를 받아서 처리하게 할 수 있습니다:

#include <stdio.h>

int
main(int argc, char *argv[])
{
  int i;
  for (int i = 0; i < argc; i++)
    printf("argv[%d]: %s\n", i, argv[i]);
  return 0;
}

위 소스를 컴파일하고 실행해 보면 argc와 argv가 어떠한 역할을 하는지 쉽게 알 수 있습니다:

$ cc -Wall arg.c
$ ./a.out               # argc == 1
argv[0]: ./a.out
$ ./a.out hello world   # argc == 3
argv[0]: ./a.out
argv[1]: hello
argv[2]: world
$ ls *.c
arg.c  hello.c
$ ./a.out *.c           # argc == # of .c files plus 1.
argv[0]: ./a.out
argv[1]: arg.c
argv[2]: hello.c
$ _

옵션을 처리하기 위해서 getopt(3)이나 getopt_long(3) 함수를 쓰는 것이 편리합니다. 먼저 가장 흔한 '-h'와 '-v' (도움말과 버전 출력) 옵션을 만들어 봅시다.

getopt(3)와 getopt_long(3)은 옵션 처리를 매우 간단하게 해 줍니다. 먼저 대부분 UNIX 시스템은 POSIX.2에 준하는 getopt(3)를 제공하며, GNU 시스템에서는 확장 함수인 getopt_long(3)을 쓸 수 있습니다. getopt(3)가 한 글자 옵션만을 처리할 수 있는 반면, getopt_long(3)은 한 글자 옵션 뿐만 아니라 긴 옵션도 처리 가능합니다:

#include <unistd.h>

int getopt(int ARGC, char * const ARGV[], const char *OPTSTRING);

extern char *optarg;
extern int optind, opterr, optopt;

#include <getopt.h>

int getopt_long(int ARGC, char * const ARGV[], const char *OPTSTRING,
                const struct option *LONGOPTS, int *LONGINDEX);

먼저 두 함수의 세번째 파라메터인 OPTSTRING에 대해 알아 봅시다. 이 파라메터에는, 이 프로그램이 처리할 수 있는 한 글자 옵션들의 목록을 써 줍니다. 만약 옵션이 인자를 받는다면 옵션 글자 뒤에 ':'도 써 줍니다. 만약 옵션에 대한 인자를 생략할 수 있는 경우에는 ':'를 두개 써 줍니다. 예를 들어 프로그램이 처리하는 옵션이 '-h', '-v', '-q', '-o FILE' 이 네개라고 하면, OPTSTRING은 "hvqo:"가 됩니다.

이제 getopt(3)의 리턴 값에 대해 알아봅시다. getopt(3)은 프로그램에 전달된 인자를 하나씩 처리합니다. 즉, 개발자는 getopt(3)을 필요한 만큼 불러서, 프로그램에 전달된 옵션을 모두 처리해 주어야 합니다. 더 이상 처리할 옵션이 없는 경우, getopt(3)는 -1을 리턴합니다. 따라서 우리는 다음과 같은 코드를 생각할 수 있습니다:

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

  while ((ch = getopt(argc, argv, "hvqo:")) != -1) {
    ...
  }
  ...
}

getopt(3)는 자신이 처리한 옵션 글자를 리턴해 줍니다. 따라서 getopt(3)가 '-h'를 처리했다면 'h'를 리턴합니다. 따라서 우리는 getopt(3)의 결과를 switch 문장을 써서 원하는 작업을 처리해주면 됩니다. 만약 프로그램 사용자가 알 수 없는 옵션을 썼다면 getopt(3)는 '?'를 리턴합니다. 따라서 위 예제의 while 문장 안은 다음과 같이 쓸 수 있습니다:

switch (ch) {
case 'h':
  /* show help message and exit */
case 'v':
  /* show version information and exit */
case 'q':
  /* set quiet mode */
  break;
case 'o':
  /* set the output file name */
  break;
case '?':
  /* unknown option. ignored. */
  break;
default:
  /* getopt(3) returns unrecognized value. */
  abort();
}
먼저 getopt(3)가 '?'를 리턴했다면, 프로그램 사용자가 알 수 없는 옵션을 지정한 것을 뜻합니다. 이 경우 getopt(3)는 자동으로 알 수 없는 옵션이 들어왔다는 에러 메시지를 출력합니다만, 이 것으로 충분하지 않습니다. 우리는 이 경우 '-h'를 쓰면 도움말을 볼 수 있다는 것까지 출력해 줄 것입니다:

case '?':
  /* getopt(3) prints "unknown option" message automatically. */
  fprintf(stderr"Try `opt -h' for more information.\n");
  break;

다음으로, 인자를 받는 옵션인 '-o FILE'에 대해 더 알아봅시다. getopt(3)는 인자를 받는 옵션이 들어올 경우, 전역 변수 optarg에, 그 옵션 인자를 가리키는 포인터 값을 저장해 둡니다. 따라서 우리는 이 optarg에서 원하는 인자를 얻을 수 있습니다.

한가지 더, optarg는 getopt(3)를 부를 때 다른 값으로 덮어 써 질 수 있으므로, 다음과 같은 코드는 위험할 수 있습니다:

const char *filename;
...
filename = optarg;

가장 손쉬운 방법은 strdup(3) 함수를 써서 메모리를 할당한 다음, optarg가 가리키는 문자열을 복사하는 것입니다:

const char *filename;
...
filename = strdup(optarg);

마지막으로, getopt(3)를 써서 옵션 처리를 다 끝냈다면, 이제 프로그램에 전달된 (옵션이 아닌) 인자들을 처리해야 합니다. 이 때, 우리는 전역 변수 optind에 담긴 값을 씁니다. optind에는 getopt가 argv[]에 있는 옵션들을 처리하고 난 마지막 인자에 대한 index 값이 들어 있습니다. 따라서 우리는 argv[optind]부터 argv[argc - 1]까지 처리하면 됩니다. 만약 프로그램에 전달된 인자들이 없는 경우를 따로 처리하고 싶다면, optind가 argc가 같은 경우를 처리하면 됩니다. 예를 들어 위 getopt를 쓴 while 문장 다음에 아래와 같은 코드를 추가하면 됩니다.

if (optind == argc) { /* there is no argument in the command-line. */
  ...
}
else {
  int i;
  for (i = optind; i < argc; i++) {
    /* process each argv[i] here */
    ...
  }
}


1.3 Skelton Template #1 


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

#include <errno.h>
#include <error.h>


const char *program_name = "opt";
const char *version_string = "0.1";

int verbose_mode = 1;
const char *output_filename;

static void help_and_exit(void);
static void version_and_exit(void);


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

  while (1) {
    c = getopt(argc, argv, "hvo:q");
    if (c < 0)
      break;

    switch (c) {
    case 'h':
      help_and_exit();
      break;
    case 'v':
      version_and_exit();
      break;
    case 'q':
      verbose_mode = 0;
      break;
    case 'o':
      output_filename = strdup(optarg);
      break;
    case '?':
      error(00"Try `%s -h' for more information.\n", program_name);
      break;
    default:
      abort();
    }
  }

  if (optind == argc) {         /* There is no more argument in the
                                   command-line */
    /* TODO: insert code for no argument */
  }
  else {
    int i;
    for (i = optind; i < argc; i++) {
      /* Process each argv[i] here */
      printf("processing %s\n", argv[i]);
    }
  }

  return 0;
}


static void
help_and_exit(void)
{
  static const char *msgs[] = {
    "usage: opt [OPTION...] [FILES...]",
    "",
    "  -h       display this help and exit",
    "  -v       output version information and exit",
    "  -q       quite mode",
    "  -o       FILE send output to file FILE. If FILE is `-', ",
    "           send output to stdout.",
    "",
    "Report bugs to <cinsky at gmail dot com>",
  };
  int i;
  for (i = 0; i < sizeof(msgs) / sizeof(const char *); i++)
    puts(msgs[i]);
  exit(EXIT_SUCCESS);
}


static void
version_and_exit(void)
{
  printf("opt version %s\n", version_string);
  exit(EXIT_SUCCESS);
}