원문: http://www.lug.or.kr/docs/LINUX/others/12-2.htm

쉘스크립트 (Shell Script) - (III)

임종균 / 서울대학교 컴퓨터공학과 리눅스 프로그래머
(hermes44@secsm.org)


디버깅

    이전 기사를 통해 우리는 간단한 정도의 쉘 스크립트는 작성할 수 있게 되었다. 이제는 다음 단계로 작성한 쉘 스크립트를 디버깅하는 방법에 대해서 다루도록 하겠다. 프로그램을 한 번에 에러없이 작성할 수 있는 천재 프로그래머가 아닌 이상 디버깅이란 코드의 안정성과 완벽함을 높이는 필수적인 작업이다. 또한 대부분의 경우, 코드를 작성하는 것보다도 더 많은 시간이 걸리는 일이 디버깅이다. 보통 리눅스 개발 환경에서 C로 프로그램을 작성하였다면 디버깅 도구로 gdb를 사용할 것이다. 하지만 gdb이전에 가장 좋은 디버깅 방법은 코드 곳곳에 printf를 추가하여 실행 결과를 보고 문제점을 파악하는 것일 것이다. 쉘 스크립트에도 별다른 디버거가 존재하지 않기 때문에 echo를 사용하여 디버깅하는 것이 일반적이다. 하지만 다음에서는 그 방법 이외에 사용할 수 있는 방법을 알아보도록 하겠다.

     

쉘 옵션 기능

    쉘 자체적으로 디버깅을 위해 사용되는 명령행 옵션이 있다. -v는 쉘이 스크립트를 수행하기 위해서 읽은 부분을 화면에 출력해준다. -x는 읽어들인 부분 중에서 실제 실행을 한 부분을 보여준다. 이 결과는 stderr로 출력이 되면 다음의 세 가지 방법 중 하나로 사용할 수 있다.

    ·쉘 스크립트의 첫 줄에 다음과 같이 옵션을 지정한다. 
    #!/bin/sh -xv

    ·소스를 수정하지 않고 한 번만 실행하려면 다음과 같이 스크립트를 실행한다. 
    $ sh -xv [스크립트이름]

    ·스크립트의 전체가 아닌 일부분에서만 이 기능을 사용하려면 사용하려고 하는 부분 앞에 
      다음을 추가한다. 
    set -xv

    ·이 기능을 없애기 위해서 필요한 부분 이후에 다음을 추가한다. 
    set +xv

    첫 번 기사에서 다루었던 예제를 다시 보면서 사용법을 설명하도록 하겠다.

    보면 쉽게 알 수 있듯이  prarg 스크립트는 세 인자를 입력받아서 출력하는 매우 간단한 스크립트이다. 두번째 방법을 이용하여서 실행해 보자.

     

      $ cat ./prarg 
      #!/bin/sh 
      # 
      # prarg: 인자를 출력하다. 
      #

      prog=`basename $0`

      if [ $# -eq 3 ] 
      then 
      echo “Script $prog path: $0” 
      echo “Arg1: $1” 
      echo “Arg2: $2” 
      shift 
      echo “Arg3: $2” 
      else 
      echo “Usage: $ $prog arg1 arg2 arg3” 
      exit 1 
      fi 
      $ ./prarg 1 2 3 
      Script prarg path: ./prarg 
      Arg1: 1 
      Arg2: 2 
      Arg3: 3 
      $ sh -x ./prarg 1 2 3 
      ++ basename ./prarg 
      + prog=prarg 
      + [ 3 -eq 3 ] 
      + echo Script prarg path: ./prarg 
      Script prarg path: ./prarg 
      + echo Arg1: 1 
      Arg1: 1 
      + echo Arg2: 2 
      Arg2: 2 
      + shift 
      + echo Arg3: 3 
      Arg3: 3 
      $ sh -xv ./prarg 1 2 3 
      #!/bin/sh 
      # 
      # prarg: 인자를 출력하다. 
      #

      prog=`basename $0` 
      basename $0 
      ++ basename ./prarg 
      + prog=prarg

      if [ $# -eq 3 ] 
      then 
      echo “Script $prog path: $0” 
      echo “Arg1: $1” 
      echo “Arg2: $2” 
      shift 
      echo “Arg3: $2” 
      else 
      echo “Usage: $ $prog arg1 arg2 arg3” 
      exit 1 
      fi 
      + [ 3 -eq 3 ] 
      + echo Script prarg path: ./prarg 
      Script prarg path: ./prarg 
      + echo Arg1: 1 
      Arg1: 1 
      + echo Arg2: 2 
      Arg2: 2 
      + shift 
      + echo Arg3: 3 
      Arg3: 3

     

    -x 옵션만으로 실행을 할 경우에는 스크립트에서 실행하는 부분만을 보여준다. + 은 쉘이 실행하는 부분, ++는 다른 외부 프로그램이 (이 외부 프로그램은 쉘이 실행한다.) 실행하는 부분을 의미한다. -v 옵션과 같이 실행하면 스크립트를 읽은 부분을 보여준다. if, for, while, case 등의 블럭들은 한 번에 블럭 전체를 읽어서 수행하기 때문에 위의 예제에서와 같은 결과가 나온다. 출력시에는 스크립트의 실행 결과와 디버깅을 위한 출력이 같은 화면으로 보이는 듯하지만 전자는 stdout으로, 후자는 stderr로 표시되는 것이다. 따라서 이 결과를 파일로 리다이렉션하기 위해서는 다음과 같이 실행해야 한다.

      $ sh -xv [스크립트이름]  >[파일명] 2>&1

    2>&1은 stderr(2)의 결과를 stdout(1)과 같은 곳으로 리다이렉션하는 것이다.

    또한 실행문의 결과를 검사하기 위한  -e 옵션이 있다.  -e는 -x에 의해 출력되는 실행문들의 실행 결과가  0이 아닌 값 즉, 실패를 리턴하였을 경우 쉘 스크립트를 종료한다. 하지만 while, until, if문에서 사용하는 조건식이나 &&, ||, !이 포함되있는 실행문에서는  -e가 효과 없다. 
    사용법은  -x, -v를 사용할 때와 같다.

     

      $ cat ./stdio3 
      #!/bin/sh 
      # 
      # stdio3: 표준 입력을 받아 표준 출력으로 표시한다. 
      #

      filename=`line`

      if [ -e $filename ] 
      then 
      echo $filename exists. 
      else 
      echo $filename doesn\’t exist. 
      fi 
      $ ./stdio3 
      ./stdio3: line: command not found 
      exists. 
      $ sh -x ./stdio3 
      ++ line 
      ./stdio3: line: command not found 
      + filename= 
      + [ -e ] 
      + echo exists. 
      exists. 
      $ sh -ev ./stdio3 
      ++ line 
      ./stdio3: line: command not found 
      + filename=

     

    -e 옵션을 주고 실행한 경우 if 이전의 실행문이 실패를 하여 if 가 실행되기 전에 종료함을 알 수 있다.

     

조건식

    조건식을 일반적인 컴파일 언어에서와 비슷하게 사용할 경우 생각지도 않는 에러가 발생하는 경우가 있다.

    수식 검사 조건식에서 검사하려는 변수가 존재하지 않거나 값이 설정되지 않았을 경우 에러가 발생한다. 즉, 다음과 같은 조건식에서

      if [ “$count” -eq 3 ]

    count 변수가 존재하지 않거나 값이 설정되어 있지 않으면 다음과 같이 해석이 되어

      if [ “” -eq 3 ]

    아래와 같은 에러가 발생한다. 

      [: integer expression expected before -eq

    이를 방지하기 위해서는 위의 수식을 다음과 같이 바꾼다.

      if  [ “0$count” -eq 3 ]

    이 때는 위와 같은 에러 상황일 때 0을 리턴하게 되고 count 변수값이 3일 경우에는  03을 리턴하게 되어 에러없이  조건식이 수행이 된다.

    문자열 검사 조건식에서는 검사하려는 문자열이 조건식에서 사용하는 키워드일 경우 에러가 발생한다. 즉, 그 문자열이 -eq, -ne, -gt, -r, -w, -z, -f등의 것들일 때이다. 이 경우에는 비교하려는 문자열 앞에 적당한 문자를 추가하여 앞에서와 같은 것들이 조건식에서 나오지 않도록 한다.

      if  [ “$op” = -gt ]

    를 다음과 같이 변경을 한다.

      if  [ “Z$op” = Z-gt ]

    앞의 -e 옵션을 설명할 때 보았던 것과 같이 쉘 스크립트에서 실행문이 실패한다고 해서 스크립트가 중단되지는 않는다. (중단시키려면 -e 옵션을 주어야 한다.) 하지만 중단이 된다는 잘못된 착각을 하여 스크립트를 작성하는 경우가 있다. 다음의 경우에는 그것이 치명적인 결과를 나을 수 있다.

     

      $ cat clean 
      #!/bin/sh

      tempdir=’/tmp /usr/tmp /var/tmp’

      for dir in $tempdir 
      do 
      cd $dir 
      rm -rf * 
      done

      echo “Cleanup done !”

     

    ‘cd $dir’이 실패할 경우에도 다음 실행문인 ‘rm -rf *’은 실행이 된다. cd가 실패를 한다면 생각지도 않는 디렉토리의 내용을 다 지워버리는 결과를 나을 수 있다. 이런 문제를 방지하기 위해 이와같이 다음에 치명적인 실행문이 올 경우 이전의 실행문이 실패하면 실행하지 않도록 한다. || 조건을 이용 cd 실행문을 바꾼다.

      cd $dir || exit

    이 경우 || 앞의 cd가 실패할 경우 exit가 실행되어 더 이상 진행되지 않도록 한다. cd가 성공한 경우에는 || 조건식은 항상 참이 되기 때문에 || 우측의 exit는 실행되지 않는다. 위의 예제에서는 exit 대신 continue를 써도 무방하다.

 

변수값 확인하기

    쉘 스크립트에 대화식으로 변수값을 확인할 수 있는 방법이 있다. 그런 기능을 하는 코드를 쉘 스크립트 중간에 삽입하는 방법이다.

      while echo -n “Enter a variable name you want to know; \
      just ENTER to quit: “ 
      read var 
      do 
      case “$var” in 
      “”) break ;; 
      *) eval echo \$$var ;; 
      esac 
      done

    이 트레이스(trace) 코드 이전에 사용된 변수의 값을 확인해 볼 수가 있다.

     

    $ cat trace 
    #!/bin/sh 
    # 
    # trace: 변수값을 보여주는 트레이스 코드를 사용해 본다. 
    #

    dir=`dirname $0` 
    file=`basename $0`

    count=0

    for arg in $* 
    do 
    count=`expr $count + 1`

    # Trace code 
    while echo -n “Enter a variable name you want to know; just ENTER to quit: “ 
    read var 
    do 
    case “$var” in 
    “”) break ;; 
    *) eval echo \$$var ;; 
    esac 
    done 
    done 
    $ ./trace a b cde fg h 
    Enter a variable name you want to know; just ENTER to quit: dir 
    . 
    Enter a variable name you want to know; just ENTER to quit: file 
    trace 
    Enter a variable name you want to know; just ENTER to quit: count 
    1 
    Enter a variable name you want to know; just ENTER to quit: arg 
    a 
    Enter a variable name you want to know; just ENTER to quit: [ENTER] 
    Enter a variable name you want to know; just ENTER to quit: count 
    2 
    Enter a variable name you want to know; just ENTER to quit: arg 
    b 
    Enter a variable name you want to know; just ENTER to quit: [ENTER] 
    Enter a variable name you want to know; just ENTER to quit: count 
    3 
    Enter a variable name you want to know; just ENTER to quit: arg 
    cde 
    Enter a variable name you want to know; just ENTER to quit: [ENTER] 
    Enter a variable name you want to know; just ENTER to quit: count 
    4 
    Enter a variable name you want to know; just ENTER to quit: arg 
    fg 
    Enter a variable name you want to know; just ENTER to quit: [ENTER] 
    Enter a variable name you want to know; just ENTER to quit: count 
    5 
    Enter a variable name you want to know; just ENTER to quit: arg 
    h 
    Enter a variable name you want to know; just ENTER to quit: [ENTER]

    ([ENTER]은 ENTER를 눌렀음을 의미한다.)

     

결론

    이제까지 쉘 스크립트의 작성과 디버깅에 관한 사항을 알아보았다. 물론 지금까지 다룬 내용이 쉘 스크립트의 전부는 아니다. 자잘하고 미묘한 부분들 하지만 중요한 부분들은 다루지 못했으며, 현재 많이 사용하는 bash나 tcsh의 확장 기능도 남아 있다. 이제 그 부분은 독자들에게 남겨진 몫이며 이 기사를 통해 그 시작을 할 수 있기를 바란다. 일단은 다른 사람들이 만들어 놓은 스크립트를 보고 이해하면서 코드를 수정해보고 필요한 스크립트를 하나씩 만들어 가면서 익히면 될 것이다. 그리고 작성중에 막히는 부분은 매뉴얼 페이지가 큰 도움이 될 것이다.  man bash ! 
    참고자료

    UNIX POWER TOOLS, 2ed. Jerry Peek, Tim O’Reilly, Mike Loukides. O’Reilly 
    bash 매뉴얼 페이지

'IT > language' 카테고리의 다른 글

쉘스크립트 (Shell Script) - (II)  (0) 2013.01.28
쉘스크립트 (Shell Script) - (I)  (0) 2013.01.28