[Bitmap Project] Command Line Argument

 

이제 끝이다! 물론 나중에 다른 업데이트를 할 수는 있겠지만 우선 여기까지가 처음 프로젝트를 계획했을때 구상한 가장 마지막 단계이다. 이번엔 main함수를 이쁘게 다듬어보자. 사용자가 쓸 수 있도록.

1. Main Function with Argument

편하다는 이유로 비표준임에도 불구하고 void main(void)를 쓰는 사람들이 꽤 있다. int main을 쓰더라도 main함수의 argument를 쓰는 일이 드물어져서인지, 편해서인지 많은 사람들이 main함수의 인자는 void로 쓴다. GUI가 대중화되면서 이제는 CLI환경을 보는 일이 드물기 때문일 것이다. 하긴 C나 C++로 만들어도 입출력을 CLI로 받지는 않을 것이다.

뭐 쨌든 Command Line Argument (이하 명령줄 인자 영어로 쓰니까 너무 길다) 의 사용법은 알고는 있었지만 나도 써볼 일이 없어서 이번에 쓴게 처음인 것 같다. 어쨌든 필자는 Win32 API나 MFC등 GUI를 구현할 수 있는 수단을 다룰 줄 모른다. flutter를 대충은 다뤄봤기에 flutter desktop과 같은 프레임워크에 native library로서 연결시켜 사용하는 방법도 있겠지만 뭐 일을 그렇게 크게 만들 생각은 없었다. 나중에 심심하면 해봐야겠다.

하지만 상용프로그램이라면 적어도 사용자에게 옵션을 줘야하지 않겠나. 물론 상용프로그램이 될 건 아니지만. 그래서 명령줄 인자를 이용해 옵션을 받아주기로 했다.

2. 명령줄 인자의 사용법

코딩도장: 80.1에 설명이 잘 나와 있다. 필자는 아래와 같은 main의 표준형에 익숙해지고자 (명령줄 인자가 필요 없더라도) 항상 아래와 같은 형태를 사용하길 고집했었다. 똥고집

int main(int argc, char *argv[]){
    // Process...
    return 0;
}

main함수의 두 인자 argcargv가 명령줄 인자 사용을 위해 필요한 요소들이다. arg는 잘 알다시피 argument이고 c는 count이다. v는 values일껄….? 아마도

우선, 예를들어 command 창 또는 powershell에,

(dir)>./bmputill -i lena.bmp lena_invert.bmp

라고 입력을 한 경우를 생각하자.

./bmputil

이야 dir경로에 있는 bmputill.exe를 실행하는 것일 것이다. 그 뒤를 따라오는

-i lena.bmp lena_invert.bmp

가 바로 명령줄 인자로 곧 main함수에서 가져다 쓸 수 있게된다. 공백으로 구분되어 각각 문자열 형태로 전달된다. (인자에 공백을 포함한 문자열을 넣고 싶으면 큰따옴표로 인자를 묶으면 된다.)

이제 bmputill이 실행되며 명령줄 인자로 받은 인자들의 정보가 argc, argv로 전달된다. argc에는 명령줄 인자의 개수, argv각 명령줄 인자를 문자열 형태로 원소로 가지는 배열이다. 헷갈릴 수 있으니 아래와 같은 사용 예시와 그 출력을 참고하여라.

printf("%d\n",argc);	// 명령줄 인자의 개수 출력:	4
puts(argv[0]);		// 1번째 인자 출력:		./bmputil
putchar(argv[1][1]);	// 1번째 인자의 2번째 글자 출력:	i

눈치 챘겠지만, 명령줄 인자로 실행파일 실행 명령까지 들어간다. 사실상 내가 cmd창에 입력한 한 줄이 공백을 delimiter로 split되어 들어간 것이나 다름없다. 그래서 argv[1]에는 “-i”가 들어 있다.

3. 규칙

(project directory)>./bmputil [option] [src file name (with extension)] [dst file name (with extension)]
option: [ ascii: -a | contrast: -c | gray: -g | invert: -i | mirror -m ]

위와 같이 입력받는 규칙을 정하였다. 보통 option 인자를 가장 먼저들 받길래 나도 따라해봤다. 또 우리가 쓸 모든 함수들이 srcdst를 공통적으로 필요하니까 뒤이어 받을 수 있도록 그냥 required로서 때려박았다.

4. 구현

#include <stdio.h>
#include "bitmap.h"

int main(int argc, char* argv[]){
    if(argc!=4) goto INVALID;
    if(argv[1][0]!='-') goto INVALID;
    switch(argv[1][1]){
        case 'a': toASCII(argv[2],argv[3]); break;
        case 'c': contrast(argv[2],argv[3]); break;
        case 'g': gray(argv[2],argv[3]); break;
        case 'i': invert(argv[2],argv[3]); break;
        case 'm': mirror(argv[2],argv[3]); break;
        default: goto INVALID; break;
    }

    return 0;
    
INVALID:
    if(argc==2 && argv[1][0]=='-' && argv[1][1]=='e') goto EXAMPLE;
    puts("Invalid argument!");
    puts("ex>./bmputil [option] [src file name (with extension)] [dst file name (with extension)]");
    puts("option: [ ascii: -a | contrast: -c | gray: -g | invert: -i | mirror -m ]");
    return 0;
    
EXAMPLE:
    toASCII("source/lena/8.bmp","result/lena/8.txt");
    toASCII("source/lena/16_555.bmp","result/lena/16_555.txt");
    toASCII("source/lena/16_565.bmp","result/lena/16_565.txt");
    toASCII("source/lena/24.bmp","result/lena/24.txt");
    toASCII("source/lena/32.bmp","result/lena/32.txt");
    contrast("source/lena/8.bmp","result/lena/8_contrast.bmp");
    contrast("source/lena/16_555.bmp","result/lena/16_555_contrast.bmp");
    contrast("source/lena/16_565.bmp","result/lena/16_565_contrast.bmp");
    contrast("source/lena/24.bmp","result/lena/24_contrast.bmp");
    contrast("source/lena/32.bmp","result/lena/32_contrast.bmp");
    gray("source/lena/8.bmp","result/lena/8_gray.bmp");
    gray("source/lena/16_555.bmp","result/lena/16_555_gray.bmp");
    gray("source/lena/16_565.bmp","result/lena/16_565_gray.bmp");
    gray("source/lena/24.bmp","result/lena/24_gray.bmp");
    gray("source/lena/32.bmp","result/lena/32_gray.bmp");
    invert("source/lena/8.bmp","result/lena/8_invert.bmp");
    invert("source/lena/16_555.bmp","result/lena/16_555_invert.bmp");
    invert("source/lena/16_565.bmp","result/lena/16_565_invert.bmp");
    invert("source/lena/24.bmp","result/lena/24_invert.bmp");
    invert("source/lena/32.bmp","result/lena/32_invert.bmp");
    mirror("source/lena/8.bmp","result/lena/8_mirror.bmp");
    mirror("source/lena/16_555.bmp","result/lena/16_555_mirror.bmp");
    mirror("source/lena/16_565.bmp","result/lena/16_565_mirror.bmp");
    mirror("source/lena/24.bmp","result/lena/24_mirror.bmp");
    mirror("source/lena/32.bmp","result/lena/32_mirror.bmp");
    return 0;
}

항상 C언어 기초 문법을 가르치던 사람들은 나에게 goto문은 절대악인 것처럼 이야기했었다만, 이정도는 오히려 괜찮지 않나 싶다. 조건문 만으로도 해결은 됐겠지만 오히려 goto를 사용한게 더 직관적으로 보이고, 코드도 덜 복잡해서 이렇게 했다.

(사실 명령줄 인자가 주어지지 않거나, 올바르지 않을경우 안내문구와 다시 입력할 수 있는 기회를 주도록 만드려고 했으나 복잡해져서 그냥 여기서 끝냈다.)

지금처럼 main함수를 완성하기 전에는 지금 EXAMPLE 레이블에 있는 험악한 코드들을 제대로 구현했는지 확인하는 테스팅 용도로 사용했었다. 이제는 굳이 필요가 없으니 없앨까 하다가 타이핑한게 아까워서 이렇게 -e 옵션 아래에 숨겨두기로 했다.

Github-repository: BitmapProject를 desktop에 clone후 cmd에서 프로젝트 경로로 접근한 뒤

(PowerShell) ./bmputill -c source/pepper/24.bmp result/pepper/24_contrast.bmp
(CMD Prompt) bmputill -c source/pepper/24.bmp result/pepper/24_contrast.bmp

와 같이 입력하여 결과를 확인하여 보라.

This work is licensed under Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.
(The excerpted works are exceptionally subject to a licence from its source.) Attribution-ShareAlike 4.0 International (CC BY-SA 4.0)