IBM®
메인 컨텐츠로 가기
    Korea [국가변경]    이용약관
 
 
   
        제품    서비스 & 솔루션    고객지원 & 다운로드    회원 서비스    
메인 컨텐츠로 가기

한국 developerWorks  >  리눅스 | AIX and UNIX | 오픈 소스  >

리눅스 팁: bash 매개변수와 매개변수 확장

스크립트에서 매개변수 전달과 분석

developerWorks
문서 옵션

JavaScript가 필요한 문서 옵션은 디스플레이되지 않습니다.

토론

영어원문

영어원문


제안 및 의견
피드백

난이도 : 중급

Ian Shields, 선임 프로그래머, IBM Japan

옮긴이: 박재호 이해영 dwkorea@kr.ibm.com

2008 년 6 월 03 일

스크립트에서 매개변수를 사용하는 방법과 여타 스크립트나 내부 함수에 매개변수를 전달하는 방법이 종종 헷갈리나요? 매개변수와 옵션에 대한 기본적인 적합성 확인이나 매개변수 문자열에 대한 단순 추출과 변환 작업을 수행해야 할까요? 이번 팁에서는 매개변수 사용법과 bash 셸에서 이용할 수 있는 다양한 매개변수 확장을 설명합니다.

bash 셸은 오늘날 많은 리눅스(Linux®)와 유닉스(UNIX®) 시스템에서 제공되며, 리눅스에서는 일반적으로 기본 셸이다. 이번 팁에서는 bash 스크립트에서 매개변수, 옵션을 다루는 법과 매개변수를 점검하고 수정하기 위해 셸의 매개변수 확장을 이용하는 법을 배운다. 이번 기사는 bash에 집중하며, 모든 예제는 bash를 셸로 가진 리눅스 시스템에서 실행해봤다. 그렇지만 똑같은 확장이 ksh, ash, dash 같은 다른 많은 셸에서도 가능하며, 이 셸들이 있는 다른 유닉스 시스템이나 Cygwin과 같은 환경에서도 이용할 수 있다. 이번 팁은 이전 팁인 "Linux tip: Bash test and comparison functions"에서 다룬 도구들을 이용한다. 이번 기사의 일부 자료는, 기본적인 스크립트 작성 기술을 다수 다루고 있는 developerWorks 튜토리얼인, "LPI exam 102 prep: Shells, scripting, programming, and compiling"에서 발췌했다.

전달된 매개변수

함수와 셸 스크립트를 멋져 보이게 만드는 요인 중 하나는 똑같은 함수나 스크립트를 전달된 매개변수에 따라 다르게 동작하는 기능이다. 이번 절에서는 전달된 매개변수를 식별하고 이용하는 방법을 배운다.

함수나 스크립트 내에서는 표 1에 나오는 bash 전용 변수를 이용하여 매개변수를 나타낼 수 있다. 참조를 위해서는 다른 셸 변수와 마찬가지로 $ 기호를 앞에 붙인다.

표 1. 함수를 위한 셸 매개변수
매개변수목적
0, 1, 2, ...매개변수 0부터 시작하는 위치 매개변수. 매개변수 0은 bash를 실행한 프로그램의 이름이거나, 혹은 해당 함수가 셸 스크립트 내에서 실행하는 경우에는 그 셸 스크립트 이름이다. bash가 -c 매개변수로 실행된 경우와 같은 다른 가능성에 대한 정보는 bash 매뉴얼 페이지를 참고한다. 작은따옴표나 큰따옴표로 묶인 문자열은 단일 매개변수로 전달되며, 이 과정에서 따옴표는 없어진다. 큰따옴표의 경우에는 함수를 호출하기 전에 $HOME과 같은 모든 셸 변수를 확장한다. 공백 문자나 셸에서 의미가 특수한 다른 문자를 포함한 매개변수는 작은따옴표나 큰따옴표를 이용하여 전달해야 한다.
*매개변수 1부터 시작하는 위치 매개변수. 큰따옴표 안에서 확장되는 경우에는 하나의 낱말로 확장되며, 매개변수 사이는 전용 변수인 IFS의 첫 문자로 구분한다. 이때 IFS가 null이라면 매개변수 사이에는 어떠한 공간도 없게 된다. IFS의 기본 값은 공백 문자와 탭, 개행문자다. IFS가 설정 해제된 경우에는 기본 값과 마찬가지로 공백 문자를 구분자로 이용한다.
@매개변수 1부터 시작하는 위치 매개변수. 큰따옴표 안에서 확장되는 경우에는 매개변수 각각이 독립적인 낱말이 된다. 즉 "$@"은 "$1" "$2" ...와 같다. 매개변수에 공백 문자가 있을 가능성이 높다면 이러한 방식이 유용하다.
#매개변수 개수, 매개변수 0은 제외한다.

주의: 매개변수가 아홉 개를 넘는 경우, 열 번째를 나타내기 위해 $10을 이용할 수는 없다($10은 $1 + '0'으로 해석된다). 우선 첫 번째 매개변수($1)를 처리하거나 저장한 후, shift 명령을 이용하여 매개변수 1을 제거하고 남은 모든 매개변수를 하나씩 밀어야 한다. 즉 $10은 $9가 되며 나머지도 마찬가지다. $#의 값은 남은 매개변수의 수를 반영하도록 갱신된다. 실제에서는, 함수나 셸 스크립트의 매개변수나 명령어 치환으로 만들어진 리스트에 대한 반복 수행을 위해 대부분 for 구문을 이용하므로 이러한 제약은 거의 문제가 되지 않는다.

이제 Listing 1처럼, 전달받은 매개변수의 수와 해당 매개변수를 화면에 표시할 뿐인 간단한 함수를 정의할 수 있다.


Listing 1. 함수 매개변수
                
[ian@pinguino ~]$ testfunc () { echo "$# parameters"; echo "$@"; }
[ian@pinguino ~]$ testfunc
0 parameters

[ian@pinguino ~]$ testfunc a b c
3 parameters
a b c
[ian@pinguino ~]$ testfunc a "b c"
2 parameters
a b c

셸 스크립트는 함수와 동일한 방식으로 매개변수를 다룬다. 실제로, 다수의 작은 함수를 조합하여 스크립트를 작성하는 경우가 많을 것이다. Listing 2는 Listing 1과 같은 단순한 작업을 수행하는 셸 스크립트인 testfunc.sh의 내용과 Listing 1의 입력 중 하나를 입력으로 이용한 동작 결과다. 작성한 스크립트를 chmod +x로 실행 가능하게 만드는 걸 잊지 말자.


Listing 2. 셸 스크립트 매개변수
                
[ian@pinguino ~]$ cat testfunc.sh
#!/bin/bash
echo "$# parameters"
echo "$@";
[ian@pinguino ~]$ ./testfunc.sh a "b c"
2 parameters
a b c

표 1을 통해, 셸이 전달된 매개변수 리스트를 $*나 $@로 나타낼 수 있다는 사실과 표현 과정에서 따옴표 유무에 따라 해석에 달라진다는 사실을 알 수 있다. 앞서 나온 함수 결과에서는 $*나 "$*", $@, "$@" 중에 무엇을 이용하든 큰 차이를 볼 수 없다. 하지만 앞으로 나올 내용은, 매개변수를 분석하거나 다른 함수나 스크립트에 전달하는 경우와 같이 일이 복잡해지면 차이가 매우 중요해진다는 사실을 확인시켜 준다. Listing 3에서는 매개변수 수를 출력한 후 그 매개변수를 앞서 네 가지 방식으로 출력하는 함수를 보여준다. Listing 4는 함수 동작 상태를 보여준다. IFS 변수 기본 값은 자신의 첫 문자로 공백 문자를 이용한다. 이 때문에 Listing 4에서는 IFS 변수의 첫 문자로 '|'를 추가하여 "$*"의 확장에서 이 문자가 어디에 이용되는지 쉽게 파악할 수 있게 한다.


Listing 3. 매개변수 처리의 차이점을 조사하기 위한 함수
                
[ian@pinguino ~]$ type testfunc2
testfunc2 is a function
testfunc2 ()
{
    echo "$# parameters";
    echo Using '$*';
    for p in $*;
    do
        echo "[$p]";
    done;
    echo Using '"$*"';
    for p in "$*";
    do
        echo "[$p]";
    done;
    echo Using '$@';
    for p in $@;
    do
        echo "[$p]";
    done;
    echo Using '"$@"';
    for p in "$@";
    do
        echo "[$p]";
    done
}


Listing 4. testfunc2를 이용한 매개변수 정보 출력
                
[ian@pinguino ~]$ IFS="|${IFS}" testfunc2 abc "a bc" "1 2
> 3"
3 parameters
Using $*
[abc]
[a]
[bc]
[1]
[2]
[3]
Using "$*"
[abc|a bc|1 2
3]
Using $@
[abc]
[a]
[bc]
[1]
[2]
[3]
Using "$@"
[abc]
[a bc]
[1 2
3]

차이점을 주의 깊게 파악하자. 특히 따옴표가 있는 경우와 매개변수에 공백 문자나 개행 문자 같은 여백이 있는 경우를 따져야 한다. "$*" 확장은, [] 문자가 한 쌍만 있는, 사실상 낱말 하나임에 유의하자.




위로


옵션과 getopts

전통적인 유닉스와 리눅스 명령은 전달된 매개변수 중 일부를 옵션으로 여긴다. 예전부터 옵션은 단일 문자 스위치로서 앞서 나오는 하이픈이나 마이너스로 다른 매개변수와 구분된다. 편의상 ls -lrt 명령어처럼 여러 옵션을 묶을 수도 있는데, 이는 꼼꼼하게 정보를 표시한(-l 옵션) 디렉터리 목록을 수정 시간 기준의(-t 옵션) 역순으로(-r 옵션) 열거한다.

이와 똑같은 기술을 셸 스크립트에서도 이용할 수 있으며, 내장 명령어인 getopts가 이 작업을 쉽게 해준다. 동작 방식을 파악하기 위해, Listing 5에 나오는 예제 스크립트인 testopt.sh를 살펴보자.


Listing 5. testopt.sh 스크립트
                
#!/bin/bash
echo "OPTIND starts at $OPTIND"
while getopts ":pq:" optname
  do
    case "$optname" in
      "p")
        echo "Option $optname is specified"
        ;;
      "q")
        echo "Option $optname has value $OPTARG"
        ;;
      "?")
        echo "Unknown option $OPTARG"
        ;;
      ":")
        echo "No argument value for option $OPTARG"
        ;;
      *)
      # Should not occur
        echo "Unknown error while processing options"
        ;;
    esac
    echo "OPTIND is now $OPTIND"
  done

getopts 명령어는 미리 지정된 변수를 쌍으로 이용한다. OPTIND 변수는 1로 초기화되어 있다. 이후에는 다음에 처리할 매개변수 색인을 저장한다. getopts 명령어는 옵션이 있으면 true를 반환하므로 일반적인 옵션 처리 과정에서는, 위 예제와 같이 while 루프를 case 구문과 함께 이용한다. getopts의 첫 번째 인수는 인지해야 할 옵션 문자 목록으로서, 위의 경우에는 pq다. 옵션 뒤에 나오는 콜론(:)은 해당 옵션에 설정 값이 필요하다는 사실을 지시한다. 예를 들면, tar 명령어처럼 파일 이름을 지정하기 위해 -f 옵션을 이용할 수도 있다. 위의 예에서 앞서 나오는 콜론은 getopts로 하여금 침묵하게 하며, 표준 오류 메시지를 출력하지 않도록 하는데, 이는 해당 스크립트가 자신만의 오류 처리를 제공하기 때문이다.

두 번째 매개변수(위의 예에서는 optname)는 발견한 옵션 이름을 저장할 변수 이름이다. 옵션에 설정 값이 필요하면 해당 설정 값은 존재하는 경우, OPTARG 변수에 저장된다. 침묵 모드에서는 다음에 소개하는 두 가지 오류 상황 중 하나가 발생할 수 있다.

  1. 인지할 수 없는 옵션이 발견되는 경우 optname에는 ?가, OPTARG에는 (인지할 수 없는) 해당 옵션이 들어간다.
  2. 설정 값이 필요한 옵션에 설정 값이 주어지지 않는 경우 optname에는 :가, OPTARG에는 (인수를 찾지 못한) 해당 옵션의 이름이 들어간다.

침묵 모드가 아닌 상황에서 이러한 오류가 발생하면 진단 오류 메시지를 출력하며 OPTARG는 설정 해제된다. 위의 스크립트에서는 오류를 발견하거나 가능한 해당 오류를 처리하는 데 optname의 값인 ?나 :를 이용할 수 있다.

Listing 6은 위의 단순한 스크립트를 실행한 예를 두 개 보여준다.


Listing 6. testopt.sh 스크립트 실행
                
[ian@pinguino ~]$ ./testopt.sh -p -q
OPTIND starts at 1
Option p is specified
OPTIND is now 2
No argument value for option q
OPTIND is now 3
[ian@pinguino ~]$ ./testopt.sh -p -q -r -s tuv
OPTIND starts at 1
Option p is specified
OPTIND is now 2
Option q has value -r
OPTIND is now 4
Unknown option s
OPTIND is now 5

필요하다면 평가를 위해 인수 한 무리를 getopts에 넘길 수도 있다. 이미 다른 인수에 대해 이용한 스크립트의 내부에서 새로운 인수에 대해 getopts를 호출하고자 한다면 OPTIND를 직접 1로 재설정해야 한다. 추가적인 세부 정보는 bash에 대한 매뉴얼 페이지나 인포(info) 페이지를 참고한다.




위로


매개변수 확장

지금까지 매개변수가 어떻게 함수나 스크립트에 전달되는지, 또 어떻게 옵션을 인식하는지 살펴봤다. 이제 옵션과 매개변수 처리를 시작해보자. 옵션을 처리한 후 어떤 인수가 남았는지 파악하면 좋겠다. 어쩌면 매개변수 값을 확인하거나 누락된 매개변수에 기본 값을 대입해야 할지도 모른다. 이번 절에서는 bash에서 이용할 수 있는 매개변수 확장 기능의 일부를 소개한다. 물론 더 복잡한 작업을 위해 sedawk 같은 리눅스나 유닉스 명령어가 제공하는 풍부한 기능을 이용할 수도 있다. 하지만 셸 확장도 이용할 줄 알아야 한다.

앞서 살펴본 옵션 분석과 매개변수 분석 함수를 기반으로 스크립트 작성부터 시작하자. 작성된 testargs.sh 스크립트는 Listing 7에 있다.


Listing 7. testargs.sh 스크립트
                
#!/bin/bash

showopts () {
  while getopts ":pq:" optname
    do
      case "$optname" in
        "p")
          echo "Option $optname is specified"
          ;;
        "q")
          echo "Option $optname has value $OPTARG"
          ;;
        "?")
          echo "Unknown option $OPTARG"
          ;;
        ":")
          echo "No argument value for option $OPTARG"
          ;;
        *)
        # Should not occur
          echo "Unknown error while processing options"
          ;;
      esac
    done
  return $OPTIND
}

showargs () {
  for p in "$@"
    do
      echo "[$p]"
    done
}

optinfo=$(showopts "$@")
argstart=$?
arginfo=$(showargs "${@:$argstart}")
echo "Arguments are:"
echo "$arginfo"
echo "Options are:"
echo "$optinfo"

동작 방식을 이해하기 위해 위의 스크립트를 몇 번 실행해 보자. 그리고 나서 좀더 자세히 검토해 볼 것이다. Listing 8에 몇몇 결과 예제가 있다.


Listing 8. testargs.sh 스크립트 실행
                
[ian@pinguino ~]$ ./testargs.sh -p -q qoptval abc "def ghi"
Arguments are:
[abc]
[def ghi]
Options are:
Option p is specified
Option q has value qoptval
[ian@pinguino ~]$ ./testargs.sh -q qoptval -p -r abc "def ghi"
Arguments are:
[abc]
[def ghi]
Options are:
Option q has value qoptval
Option p is specified
Unknown option r
[ian@pinguino ~]$ ./testargs.sh "def ghi"
Arguments are:
[def ghi]
Options are:


인수가 어떻게 옵션과 구별되는지에 유념하라. showopts 함수는 앞서와 같이 옵션을 분석하는데, 자신을 호출한 구문에 OPTIND 변수 값을 반환하기 위해 return 구문을 이용한다. 호출한 프로세스는 이 값을 argstart 변수에 대입한다. 그 후, 이 값은 옵션으로 처리되지 않은 매개변수로 이루어진 원래 매개변수의 부분 집합을 골라내기 위해 이용한다. 이를 위해
${@:$argstart}
매개변수 확장을 이용한다. 이전에 Listing 2에서 살펴본 바와 같이, 내부에 여백이 있는 매개변수를 보존하기 위해 해당 표현 주위에 따옴표를 이용해야 한다는 사실을 기억하자.

스크립트와 함수에 익숙하지 않다면 다음에 유념한다.

  1. return 구문은 showopts 함수의 종료 값을 반환하는데, 이는 호출한 측에서 $?를 이용하여 얻을 수 있다. 또한 분기나 루프를 통제하기 위해 testwhile 같은 명령과 함께 함수의 반환 값을 사용할 수도 있다.
  2. bash 함수는 다음과 같이 "function"이라는 낱말을 임의로 포함할 수 있다.
    function showopts ()
    이것은 POSIX 표준은 아니며, dash 같은 셸에서는 지원되지 않는다. 이 때문에 이렇게 "function"을 쓰고 싶다면 셸 스크립트 첫머리에 #!로 시작하는 행(shebang line)을 다음과 같이 쓰면 안 된다.
    #!/bin/sh
    /bin/sh는 시스템의 기본 셸을 의미하므로 여러분이 생각하는 대로 (즉 bash처럼) 동작하지 않을 수도 있다.
  3. 위의 두 함수 내부의 echo 구문이 만들어 내는 출력과 같이, 함수의 출력은 화면에 표시되지 않고 호출한 측에서 이용할 수 있게 제공된다. 이것이 변수에 대입되지도 않고 호출 구문의 일부로 이용되지도 않는다면, 셸은 이를 화면에 표시하는 대신 실행하려고 한다.

부분 집합과 부분 문자열

이번 확장의 일반적인 형태는 ${PARAMETER:OFFSET:LENGTH}이며, 여기서 LENGTH 인수는 선택적이다. 따라서 스크립트 인수의 특정한 부분 집합만을 선택하고자 한다면, 모든 항목(PARAMETER, OFFSET, LENGTH)을 설정한 형태를 이용해 인수를 몇 개 선택할지 지정할 수 있다. 예를 들면, ${@:4:3}은 네 번째 인수부터 인수 세 개, 즉 네 번째와 다섯 번째, 여섯 번째 인수를 나타낸다. $1부터 $9까지를 이용하여 직접 접근할 수 있는 범위를 벗어난 매개변수를 개별적으로 선택하는 데 이런 확장 기법을 이용할 수 있다. ${@:15:1}은 열다섯 번째 매개변수에 바로 접근할 수 있는 방법이다.

이번 확장은 $*나 $@로 표현되는 전체 매개변수뿐만 아니라 개별 매개변수에 대해서도 이용할 수 있다. 이러한 경우, 해당 매개변수는 문자열로 취급되며 숫자는 오프셋과 길이를 나타낸다. 예를 들어, 변수 x 값이 "some value"라면,
${x:3:5}다.
이는 Listing 9처럼 값이 "e val"이다.


Listing 9. 셸 매개변수 값의 부분 문자열
                
[ian@pinguino ~]$ x="some value"
[ian@pinguino ~]$ echo "${x:3:5}"
e val

길이

$#는 매개변수 개수를 나타낸다는 사실과 ${PARAMETER:OFFSET:LENGTH} 확장은 $*와 $@뿐만 아니라 개별 매개변수에도 적용할 수 있다는 사실을 이미 살펴 보았다. 따라서 비슷한 개념인 ${#PARAMETER}도 개별 매개변수의 길이를 측정하는 데 이용할 수 있다는 사실은 그다지 놀랍지 않다. Listing 10에 있는 간단한 함수인 testlength로 이를 확인한다. 스스로 파악해 보라.


Listing 10. 매개변수 길이
                
[ian@pinguino ~]$ testlength () { for p in "$@"; do echo ${#p};done }
[ian@pinguino ~]$ testlength 1 abc "def ghi"
1
3
7

패턴 일치

매개변수 확장에는 또한 파일 이름 확장이나 패턴 일치를 이용한 파일 이름 탐색(globbing)에서 이용하는 내용과 동일한 와일드 카드 기능인 몇몇 패턴 일치 기능도 있다. 하지만 grep에서 이용하는 정규 표현 일치는 아니라는 사실에 주의한다.

표 2. 패턴 일치를 이용한 셸 확장
확장효과
${PARAMETER#WORD}셸은 파일 이름 확장 방식으로 WORD를 확장해, PARAMETER를 확장한 값의 처음에서부터 가장 짧은 일치 패턴을 찾고, 발견한 경우 이를 제거한다. '@'나 '$'를 이용하는 경우, 목록에 속한 개별 매개변수에서 해당 패턴을 제거한다.
${PARAMETER##WORD}처음부터 시작해서 가장 긴 일치 패턴을 제거한다. 가장 짧은 패턴이 아니다.
${PARAMETER%WORD}셸은 파일 이름 확장 방식으로 WORD를 확장해, PARAMETER를 확장한 값의 끝부터 시작해 가장 짧은 일치 패턴을 찾고, 발견한 경우 이를 제거한다. '@'나 '$'를 이용하는 경우, 목록에 속한 개별 매개변수에서 해당 패턴을 제거한다.
${PARAMETER%%WORD}끝에서부터 가장 긴 일치 패턴을 제거한다. 가장 짧은 패턴이 아니다.
${PARAMETER/PATTERN/STRING}셸은 파일 이름 확장 방식으로 PATTERN을 확장해, PARAMETER를 확장한 값에서 가장 긴 일치 패턴을 찾아 치환한다. PARAMETER를 확장한 값의 처음부터 찾으려면 PATTERN 앞에 #을, 끝부터 찾으려면 %를 붙인다. STRING에 값이 없는 경우, PATTERN 다음의 /는 없어도 되며, 일치되는 내용은 삭제된다. '@'나 '$'를 이용하는 경우, 목록에 속한 개별 매개변수에서 해당 패턴을 치환한다.
${PARAMETER//PATTERN/STRING}첫 번째뿐만 아니라 모든 일치에 대해 치환 작업을 수행한다.

Listing 11에서 몇몇 기본적인 패턴 일치 확장 사용법을 볼 수 있다.


Listing 11. 패턴 일치 예제
                
[ian@pinguino ~]$ x="a1 b1 c2 d2"
[ian@pinguino ~]$ echo ${x#*1}
b1 c2 d2
[ian@pinguino ~]$ echo ${x##*1}
c2 d2
[ian@pinguino ~]$ echo ${x%1*}
a1 b
[ian@pinguino ~]$ echo ${x%%1*}
a
[ian@pinguino ~]$ echo ${x/1/3}
a3 b1 c2 d2
[ian@pinguino ~]$ echo ${x//1/3}
a3 b3 c2 d2
[ian@pinguino ~]$ echo ${x//?1/z3}
z3 z3 c2 d2




위로


모아 모아서

남은 몇 가지 항목을 살펴보기에 앞서, 실제 매개변수 처리 사례를 한번 살펴보자. 저자는 리눅스 시스템에 대한 developerWorks 저자 패키지(참고자료 참조)를 bash 스크립트를 이용하여 구축했다. 필요한 다양한 파일은 developerworks/library라는 라이브러리의 하위 디렉터리에 보관한다. 가장 최근 릴리스는 5.7이었으므로 스키마 파일은 developerworks/library/schema/5.7에, XSL 파일은 developerworks/library/xsl/5.7에, 예제 템플릿은 developerworks/library/schema/5.7/templates에 있다. 당연히 버전(이번 경우에는 5.7)을 알려주는 매개변수 하나만 있어도 해당 스크립트는 모든 파일 경로를 구성할 수 있다. 따라서 스크립트에는 -v 매개변수가 있으며, 이 매개변수에는 반드시 설정 값이 따라와야 한다. 이 매개변수에 대한 확인은 나중에 실행하는데, 경로를 구성한 후 [ -d "$pathname" ]을 이용하여 존재 여부를 점검한다.

이 방법은 완성된 구조에 적합하지만, 개발 과정에서는 파일이 다른 디렉터리에 보관된다.

  • developerworks/library/schema/5.8/archive/test-5.8/merge-0430
  • developerworks/library/xsl/5.8/archive/test-5.8/merge-0430 and
  • developerworks/library/schema/5.8/archive/test-5.8/merge-0430/templates-0430

여기서 버전은 5.8이며, 0430은 가장 최근 시험 버전의 월과 일을 나타낸다.

이런 문제를 해결하기 위해 매개변수 -p를 추가했는데, archive/test-5.8/merge-0430과 같은 추가 경로 정보를 포함한다. 그런데 슬래시(/)로 시작해야 한다는 제약이나 슬래시로 끝나면 안 된다는 제약을 누군가 깜빡할 수도 있고, 윈도 사용자가 슬래시 대신 역슬래시(\)를 사용할 수도 있으므로 이를 스크립트에서 처리하기로 결정했다. 또한, 임시 디렉터리를 나타내는 경로에 날짜가 두 번 들어가므로 어떻게든 해당 날짜 정보(이번 경우에는 -0430)를 따로 저장해야 했다.

앞서 나온 요건에 따라 매개변수 두 개를 처리하고 주어진 부분 경로의 오류를 수정하는 코드가 Listing 12에 있다. -v 옵션 설정 값은 ssversion 변수에 저장되고, 오류가 수정된 -p 변수는 pathsuffix에, 하이픈으로 시작하는 날짜 정보는 datesuffix에 저장된다. 각 단계에 대한 설명은 주석에 있다. 이 짧은 스크립트에서도 길이와 부분 문자열, 패턴 일치, 패턴 치환을 포함한 여러 매개변수 확장을 발견하게 될 것이다.


Listing 12. developerWorks 저자 패키지 구축에 대한 매개변수 분석
                
while getopts ":v:p:" optval "$@"
  do
    case $optval in
      "v")
        ssversion="$OPTARG"
      ;;
      "p")
        # 역슬래시를 정슬래시로 변환한다
        pathsuffix="${OPTARG//\\//}"
        # 앞에 따라나오는 /나 뒤에 따라나오는 /가 없음을 확인한다
        [ ${pathsuffix:0:1} != "/" ] && pathsuffix="/$pathsuffix"
        pathsuffix=${pathsuffix%/}
        # 마지막 하이픈과 따라나오는 문자열을 날려버린다
        dateprefix=${pathsuffix%-*}
        # 하이픈과 따라나오는 문자열을 얻기 위해 남아있는 문자열 길이를 사용한다
        [ "$dateprefix" != "$pathsuffix" ] && datesuffix="${pathsuffix:${#dateprefix}}"
        ;;
      *)
        errormsg="Unknown parameter or option error with option - $OPTARG"
        ;;
    esac
  done

리눅스, 어쩌면 일반적인 프로그래밍과 마찬가지로, 위에서 소개한 스크립트가 주어진 문제에 대한 유일한 해답은 아니다. 하지만 여러분이 지금껏 익힌 확장 방법에 대한 좀 더 실용적인 이용 방법을 보여주고 있다.




위로


기본 값

이전 절에서는 ssversion이나 pathsuffix와 같은 변수에 옵션 값을 어떻게 대입하는지 보았다. 이 경우, 버전 정보가 대입되지 않은 경우는 발견도 나중이고, 제품 빌드에서는 추가 경로도 없기 때문에 문제가 되지 않는다. 지정되지 않은 매개변수에 기본 값을 대입해야만 한다면 어떻게 될까? 이러한 작업은 표 3에서 볼 수 있는 셸 확장을 통해 도움받을 수 있다.

표 3. 기본 값과 관련된 셸 확장
확장효과
${PARAMETER:-WORD}PARAMETER가 설정 해제되었거나 존재하지 않는다면, 셸은 WORD를 확장해 그 결과로 치환한다. PARAMETER 값은 변경되지 않는다.
`${PARAMETER:=WORD}PARAMETER가 설정 해제되었거나 존재하지 않는다면, 셸은 WORD를 확장해 그 결과를 PARAMETER에 대입한다. 이 값은 동시에 치환된다. 위치 매개변수나 특수 매개변수에는 이런 식으로 값을 대입할 수 없다.
${PARAMETER:?WORD}PARAMETER가 설정 해제되었거나 존재하지 않는다면, 셸은 WORD를 확장해 그 결과를 표준 에러 출력으로 내보낸다. WORD가 없는 경우에는 메시지가 대신 출력된다. 대화식 모드가 아닌 셸은 종료한다.
${PARAMETER:+WORD}PARAMETER가 설정 해제되었거나 존재하지 않는다면 아무 일도 없다. 그렇지 않은 경우, 셸은 WORD를 확장해 그 결과로 치환한다.

Listing 13은 위에서 소개한 확장 예와 각각에 대한 차이점을 보여준다.


Listing 13. 존재하지 않거나 설정 해제된 변수를 치환하기
                
[ian@pinguino ~]$ unset x;y="abc def"; echo "/${x:-'XYZ'}/${y:-'XYZ'}/$x/$y/"
/'XYZ'/abc def//abc def/
[ian@pinguino ~]$ unset x;y="abc def"; echo "/${x:='XYZ'}/${y:='XYZ'}/$x/$y/"
/'XYZ'/abc def/'XYZ'/abc def/
[[ian@pinguino ~]$ ( unset x;y="abc def"; echo "/${x:?'XYZ'}/${y:?'XYZ'}/$x/$y/" )\
>  >so.txt 2>se.txt
[ian@pinguino ~]$ cat so.txt
[ian@pinguino ~]$ cat se.txt
-bash: x: XYZ
[[ian@pinguino ~]$ unset x;y="abc def"; echo "/${x:+'XYZ'}/${y:+'XYZ'}/$x/$y/"
//'XYZ'//abc def/




위로


매개변수 전달

매개변수 전달에는 다소 미묘한 내용이 있어 주의를 기울이지 않으면 실수하기 쉽다. 여러분은 이미 따옴표의 중요성과 그것이 $*와 $@의 사용법에 미치는 영향을 알고 있다. 그런데 다음 경우를 생각해보라. 현재 작업 디렉터리의 모든 파일이나 현재 작업 디렉터리에 영향을 끼치는 스크립트나 함수가 필요하다고 하자. 이러한 예로서, Listing 14에 나오는 ll-1.sh, ll-2.sh 스크립트를 살펴보자.


Listing 14. 기본적인 스크립트 둘
                
#!/bin/bash
# ll-1.sh
for f in "$@"
  do
    ll-2.sh "$f"
  done

#!/bin/bash
ls -l "$@"

ll-1.sh 스크립트는 자신의 매개변수 각각을 차례로 ll-2.sh 스크립트로 전달만 하며, ll-2.sh는 전달된 매개변수 각각에 대해 꼼꼼하게 정보를 표시한 디렉터리 목록을 작성한다. 시험용 디렉터리에는 "file1"과 "file 2"라는, 두 개의 빈(empty) 파일이 있다. 스크립트 실행 결과는 Listing 15와 같다.


Listing 15. 스크립트 실행하기 - 1
                
[ian@pinguino test]$ ll-1.sh *
-rw-rw-r-- 1 ian ian 0 May 16 15:15 file1
-rw-rw-r-- 1 ian ian 0 May 16 15:15 file 2

여기까지는 괜찮다. 그런데 * 매개변수 활용을 빠뜨리면 스크립트는 어떤 동작도 하지 않는다. ls 명령어의 예처럼 자동으로 현재 작업 디렉터리 내용에 대해 동작하지는 않는다. 이에 대한 수정은 ll-1.sh에서 이러한 상황을 추가로 점검하게 하고, ll-1.sh에 아무것도 전달되지 않은 경우 ll-2.sh 입력으로 ls 명령어 출력을 이용하게 수정한다. Listing 16에서 나름대로 만든 해답을 볼 수 있다.


Listing 16. 변경된 버전의 ll-1.sh
                
#!/bin/bash
# ll-1.sh - revision 1
for f in "$@"
  do
    ll-2.sh "$f"
  done
[ $# -eq 0 ] && for f in "$(ls)"
  do
    ll-2.sh "$f"
  done

"file 2"를 제대로 다루었음을 확인하기 위해 ls 명령어 결과에 신중하게 붙인 따옴표를 주목하라. Listing 17은 새로운 ll-1.sh를 *와 함께, 그리고 아무것도 없이 실행한 결과다.


Listing 17. 스크립트 실행하기 - 2
                
[ian@pinguino test]$ ll-1.sh *
-rw-rw-r-- 1 ian ian 0 May 16 15:15 file1
-rw-rw-r-- 1 ian ian 0 May 16 15:15 file 2
[ian@pinguino test]$ ll-1.sh
ls: file1
file 2: No such file or directory

놀랐는가? 매개변수를 전달하는 경우, 매개변수가 특히 명령어 출력 결과라면 상황은 미묘해진다. 이 문제를 풀 실마리는 오류 메시지에 있는데, 파일 이름이 개행 문자로 독립되어 있다. 이 문제를 해결하는 방법은 많지만, 기본적으로 Listing 18과 같이 내장 명령어인 read를 이용하는 방법이 있다. 여러분이 직접 시험해보라.


Listing 18. 내장 명령어 read를 이용한 스크립트 구현
                
#!/bin/bash
# ll-1.sh - revision 2
for f in "$@"
  do
    ll-2.sh "$f"
  done
[ $# -eq 0 ] && ls | while read f
  do
    ll-2.sh "$f"
  done

세부사항에 주의를 기울이고 엉뚱한 입력 시험이 여러분이 만든 스크립트를 더욱 완전하게 한다. 행운이 함께 하길!




위로


좀더 알고 싶다면

리눅스에서 bash 스크립트에 대해 더 알고 싶다면, "LPI exam 102 prep: Shells, scripting, programming, and compiling" 튜토리얼을 읽어보자. 이번 기사 일부분도 여기서 발췌했다. 매개변수 값과 같은 텍스트를 분석하는 데 이용할 수 있는 다른 명령어를 알고 싶다면, "LPI exam 101 prep: GNU and UNIX commands" 튜토리얼을 참고하자. 아래에 소개한 참고자료에서 다른 내용도 발견할 것이다. 마지막으로, 이 기사를 평가해주는 센스를 잊지 마시라.



참고자료

교육

제품 및 기술 얻기
  • SEK for Linux 주문: DB2®, Lotus®, Rational®, Tivoli®, WebSphere®와 같은 최신 리눅스용 IBM 평가판 소프트웨어가 담긴 두 장짜리 DVD 세트를 주문하자.

  • IBM 평가판 소프트웨어: developerWorks에서 직접 내려받아 다음 번 리눅스 개발 프로젝트에 활용하자.


토론


필자소개

Ian Shields 사진

Ian Shields는 developerWorks 리눅스 영역을 위한 리눅스 프로젝트 다수를 수행하고 있다. Shields는 노스 캐롤라이나 주 소재 IBM 리서치 트라이앵글 파크에서 선임 프로그래머로 일한다. Shields는 1973년 시스템 엔지니어로 오스트레일리아, 캔베라에 있는 IBM 사무실에 들어갔으며 캐나다 몬트리얼과 노스 캐롤라이나 주 RTP에서 통신 시스템과 배포 컴퓨팅 부문에서 일해왔다. Shields는 특허 여러 건을 획득했으며, 논문 여러 건을 발표했다. Shields는 순수 수학과 철학 학사 학위를 오스트레일리안 국립 대학에서 받았다. 노스 캐롤라이나 주립 대학에서 컴퓨터 과학 분야를 대상으로 석사와 박사 학위를 받았다. 전자편지 주소는 ishields@us.ibm.com이다.




기사에 대한 평가


보다 나은 서비스를 제공하기 위함이오니 잠시 짬을 내어 이 양식을 제출하여 주십시오.



 


 


 


이 문서 북마킹 하기

mar.gar.in mar.gar.in naver naver eolin eolin del.icio.us del.icio.us





위로


Linux is a trademark of Linus Torvalds in the United States, other countries, or both. Java and all Java-based trademarks are trademarks of Sun Microsystems, Inc. in the United States, other countries, or both. UNIX is a registered trademark of The Open Group in the United States and other countries. DB2, Lotus, Rational, Tivoli, and WebSphere are trademarks of IBM Corporation in the United States, other countries, or both. 기타 회사, 제품, 및 서비스명은 다른 상표나 서비스 마크일 수 있습니다.

developerWorks 콘텐트를 다른 사이트에 전재하기:
developerWorks 콘텐트에 대한 저작권은 IBM에 있습니다. IBM의 서면 허가나 원본 저자의 허락이 없이는 전재를 금합니다. 저희 콘텐트를 전재하시려면 IBM developerWorks 담당자 에게 문의하십시오.
    IBM 소개 개인정보 보호정책 문의