Devel Filter Bash Skeleton
Skeleton Filter Program in Bash ¶이 글은 bash를 써서 DevelFilterSkeleton에서 다룬 필터 프로그램의 뼈대를 만드는 법을 소개합니다.
DevelFilterSkeleton을 읽지 않았다면 먼저 그 글을 읽으시기 바랍니다.
(주의: 이 글은 bash shell script를 쓰는 법을 자세히 소개하는 글이 아닙니다.)
1.1. Output Messages ¶필터 프로그램은 어떤 상황이 발생했을 때, 사용자에게 보고하거나, 에러를 출력할 때가 종종 발생하기 때문에, 경고 또는 에러 메시지를 내 보내는 경우가 있습니다. 필터 프로그램은 기본적으로 처리한 데이터를 표준 출력을 통해 내 보내므로, 이런 메시지들을 표준 입력으로 보낸다면, 데이터와 섞일 경우가 있기 때문에 대부분 표준 출력을 써서 메시지를 출력합니다.
DevelFilterCSkeleton에서는 시스템 함수 호출 후 일일히 전역 변수 errno의 값을 조사해서 strerror(3)를 써서 메시지를 출력했지만, shell script에서는 그럴 필요가 없습니다.
보통 프로그램이 끝나면 일반적으로 성공적으로 끝났을 때 0을, 실패했을 경우에는 0이 아닌 값을 리턴하게 됩니다. 이 값을 exit status라고 하고, 바로 전 프로그램의 exit status는 항상 변수 $?에 저장되어 있습니다. 아래에 그 예를 보입니다.
$ ls *txt
a.txt a1.txt a2.txt a3.txt readme.txt $ echo $? 0 $ ls -y ls: invalid option -- y Try `ls --help' for more information. $ echo $? 1 $ _ 프로그램의 성공 여부는 위와 같이 확인할 수 있으므로, 우리는 DevelFilterSkeleton에서 다룬 일반적인 관습에 따라 에러 메시지를 출력해야 합니다. 보통 많은 shell script 개발자들이 에러 메시지를 출력할 때, 가장 많이 쓰는 방법은 다음과 같이 하는 것입니다:
echo "error: description"
exit 1 그러나 이 방식은 필터 프로그램이 갖춰야 할 몇 가지를 어기고 있습니다. 첫째, 프로그램 이름을 출력하지 않았습니다. 둘째, echo는 표준 출력으로 받은 인자들을 출력하기 때문에, 메시지는 표준 에러로 출력한다는 관습을 어겼습니다. 그러므로 우리는 이 두 가지를 해결하는 방법에 대해 알아보려고 합니다.
먼저 항상 우리 프로그램 이름을 다음과 같은 변수에 저장해 놓기로 합시다:
program_name='foo'
그리고 나서 어떤 메시지를 출력하려고 할 때, 다음과 같이 할 수 있습니다:
echo "$program_name: description"
exit 1 아직까지는 모두 표준 출력으로 메시지가 처리되었습니다. 이제 이 에러 메시지를 표준 에러로 보내려면 다음과 같이 하면 됩니다:
echo "$program_name: description" 1>&2
exit 1 이제 이 기능을 편리하게 쓸 수 있도록 함수로 만들어 등록합니다:
function error() {
echo "$program_name" "$@" 1>&2 exit 1 } 그런데, 상황에 따라서 경고 메시지를 출력하게 하려면, 위와 같이 정의한 error()를 쓸 수 없습니다. 왜냐하면 error()는 바로 exit 1를 불러서 프로그램을 끝내버리기 때문입니다. 따라서 우리는 error()의 첫번째 인자를 exit status로 쓰기로 하고, 이 값이 0이 아닐 경우 exit를 호출하도록 합니다:
function error () {
status="$1" shift; echo "$progra_name" "$@" 1>&2 if [ 0 -ne "$status" ]; then exit "$status"; fi } 위 함수를 쓰는 예를 보이면 아래와 같습니다:
error 1 cannot open file $INPUT.
error 0 cannot open file $INIT_FILE. 첫번째 예는 "$INPUT"이라는 파일을 열 수 없다고 표준 에러로 메시지를 출력하고 프로그램을 종료합니다. 두번째 예는 "$INIT_FILE"이라는 파일을 열 수 없다고 표준 에러로 메시지를 출력하기만 합니다.
error()는 첫 번째 인자를 exit status로 쓰고 나머지 인자는 모두 표준 에러로 출력하기 때문에, 이 함수의 사용자가 실수하지 않도록, 주석도 추가하고, 변수 program_name이 정의되어 있을 때에만 "$program_name: "을 출력하도록 고치면 다음과 같습니다:
function error() {
# usage: error EXIT_STATUS MESSAGE... # Print MESSAGE to the standard error (stderr) and call `exit EXIT_STATUS' # if EXIT_STATUS is nonzero. status="$1" shift; if [ -n "$program_name" ]; then echo -n "$program_name: " fi echo "$@" 1>&2 if [ 0 -ne "$status" ]; then exit "$status" fi } 보통 능숙한 shell 프로그래머들은 위와 같이 "if 조건-문장 then 실행-문장 fi" 문장에서 조건-문장이 참일 경우, 실행-명령을 수행하라는 코드를 "조건-문장 && 실행-문장" 형태로 쓰곤 합니다. 그렇게 고쳐 보면 다음과 같습니다:
function error () {
# usage: error EXIT_STATUS MESSAGE... # Print MESSAGE to the standard error (stderr) and call `exit EXIT_STATUS' # if EXIT_STATUS is nonzero. status="$1" shift; test -n "$program_name" && echo -n "$program_name: " 1>&2 echo "$@" 1>&2 test 0 -ne "$status" && exit "$status"; } 또, error() 함수는 모든 출력이 표준 에러로 출력되기 때문에, 명령 각각에 "1>&2"라는 redirection을 쓰지 말고, 함수 자체의 표준 출력을 표준 에러로 redirection하는 것이 좀 더 깔끔합니다. 코드는 다음과 같습니다:
function error () {
# usage: error EXIT_STATUS MESSAGE... # Print MESSAGE to the standard error (stderr) and call `exit EXIT_STATUS' # if EXIT_STATUS is nonzero. status="$1" shift; test -n "$program_name" && echo -n "$program_name: " echo "$@" test 0 -ne "$status" && exit "$status"; } 1>&2 1.2. Option Processing ¶직접 처리하는 것은 귀찮아서 하지 않습니다. Bash에서 쓸 수 있는, 옵션 처리를 도와주는 명령은 두 개가 있는데, 하나는 bash 자체에서 제공하는 getopts이고, 하나는 외부 명령으로 존재하는 getopt(1)입니다.
1.2.1. Using Built-in getopts ¶Bash에 내장되어 있는 getopts는 짧은 옵션만 처리할 수 있습니다. 먼저 도움말을 출력하고 종료하는 함수인 help_and_exit()가 정의되어 있고, 버전을 출력하고 종료하는 version_and_exit()가 정의되어 있다고 하고, 아래 코드를 보기 바랍니다:
while getopts "hvo:q" opt; do
case $opt in '?') error 1 "Try \`-h' for more information" ;; 'h') help_and_exit ;; 'v') version_and_exit ;; 'o') output_filename=$OPTARG; ;; 'q') verbose_mode=0 ;; *) error 1 "Internal getopts error" ;; esac done shift `expr $OPTIND - 1` getopts는 부를 때마다 하나의 옵션을 처리합니다. 더 이상 처리할 옵션이 없으면 nonzero 값을, 있었으면 0을 리턴합니다. (따라서 while 문장을 써서 다 옵션을 처리할 때까지 반복해서 불러줍니다.) 첫번째 인자는 받아들일 옵션의 종류입니다. 여기서 한 글자가 하나의 옵션에 해당하며, 옵션이 인자를 받을 경우, 그 옵션 글자 뒤에 ':'를 붙여 줍니다. 따라서 위의 경우 처리하는 옵션은 '-h', '-v', '-q', '-o arg'입니다.
getopts의 두번째 인자는, getopts가 처리한 옵션 글자를 저장하는 변수 이름입니다. 이는 나중에 case 문에서 실제 어떤 옵션이 들어왔는지를 처리하는데에 쓰입니다. getopts가 처리할 수 없는 옵션이 들어온 경우에는, getopts의 세번째 인자에 '?'를 넣어줍니다. 위를 보면 알겠지만, 우리는 이 에러를 처리해야 합니다.
옵션이 인자를 가지고 있는 경우, 우리는 OPTARG라는 변수를 통해 그 인자를 알 수 있습니다. 또한 getopts가 모든 작업을 끝냈을 경우, 우리는 OPTIND를 통해서, 옵션이 아닌 인자의 위치를 알 수 있습니다. 위 코드에서 맨 마지막 shift에서 OPTIND가 쓰인 것을 볼 수 있습니다.
1.2.2. Using external getopt ¶getopt(1)은 대부분 리눅스 배포판에 포함되어 있습니다. (없으면 따로 설치해야 하는데, 아래를 참고하시기 바랍니다. 이 명령이 기본적으로 설치되어 있지 않는 배포판은 없을 것입니다.
#red TODO: blah... blah... 음.. 일하기 싫어서 쓰기 시작한 글인데.. 귀찮네요.. 나중에 쓰겠습니다.
1.3.1. Skeleton Source (getopts version) ¶
#!/bin/sh
# -*-sh-mode-*- # $Id: DevelFilterBashSkeleton,v 1.3 2005/07/13 04:41:16 kss Exp kss $ PATH=/usr/local/bin:/usr/bin:/bin program_name='skel' version_string='0.1' output_filename= verbose_mode=1 function error () { # usage: error EXIT_STATUS MESSAGE... # Print MESSAGE to the standard error (stderr) and call `exit EXIT_STATUS' # if EXIT-STATUS is nonzero. status="$1" shift; test -n "$program_name" && echo -n "$program_name: " echo "$@" test 0 -ne "$status" && exit "$status"; } 1>&2 function help_and_exit() { cat <<EOF usage: $program_name [OPTION...] FILE... -q quite mode -o FILE send output to file FILE. If FILE is \`-', send output to stdout. -h display this help and exit -v output version information and exit Report bugs to <cinsky at gmail dot com> EOF exit 0 } function version_and_exit() { cat <<EOF $program_name version $version_string EOF exit 0 } # The actual code begins here ------------- while getopts "hvo:q" opt; do case $opt in '?') error 1 "Try \`-h' for more information" ;; 'h') help_and_exit ;; 'v') version_and_exit ;; 'o') output_filename=$OPTARG; ;; 'q') verbose_mode=0 ;; *) error 1 "Internal getopts error" ;; esac done shift `expr $OPTIND - 1` echo "output_filename: $output_filename" echo "verbose_mode: $verbose_mode" for f in "$@"; do echo "arg = $f" done 1.3.2. Skeleton Source (getopt version) ¶
#!/bin/sh
# -*-sh-mode-*- # $Id: DevelFilterBashSkeleton,v 1.3 2005/07/13 04:41:16 kss Exp kss $ PATH=/usr/local/bin:/usr/bin:/bin program_name='skel2' version_string='0.1' output_filename= verbose_mode=1 function error() { # usage: error EXIT_STATUS MESSAGE... # Print MESSAGE to the standard error (stderr) and call `exit EXIT_STATUS' # if EXIT-STATUS is nonzero. status="$1" shift; test -n "$program_name" && echo -n "$program_name: " echo "$@" test 0 -ne "$status" && exit "$status"; } 1>&2 function help_and_exit() { cat <<EOF usage: $program_name [OPTION...] FILE... -q, --quiet quite mode -o, --output=FILE send output to file FILE. If FILE is \`-', send output to stdout. --help display this help and exit --version output version information and exit Report bugs to <cinsky at gmail dot com> EOF exit 0 } function version_and_exit() { cat <<EOF $program_name version $version_string EOF exit 0 } # The actual code begins here ------------- GETOPT=`getopt -o o:q --long help,version,quiet,output: -n "$program_name" -- "$@"` if [ $? -ne 0 ]; then error 1 "Try \`$program_name --help' for more information" fi eval set -- "$GETOPT" while true; do case "$1" in --help) help_and_exit shift ;; --version) version_and_exit shift ;; -o|--output) output_filename="$2" shift 2 ;; -q|--quiet) verbose_mode=0 shift; ;; --) shift break ;; esac done echo "output_filename: $output_filename" echo "verbose_mode: $verbose_mode" for f in "$@"; do echo "arg = $f" done 끝 --
신성국
|
"Perl is executable line noise, Python is executable pseudo-code." |











![[http]](/imgs/http.png)
나중에 쓰겠습니다.