Shell Script

|

들어가며

    쉘 스크립트란 쉘에서 사용할 수 있는 명령어들의 조합을 모아서 만든 배치(batch) 파일이다. 리눅스에서는 여러 명령어들을 파이프(pipe), 리다이렉션(redirection), 필터(filter) 등으로 연결하여 원하는 결과를 얻어 낼 수 있다. 이런 방식으로 묶여진 명령어 조합이 반복적으로 사용된다면 이를 쉘 스크립트로, 즉 단일 명령으로 만들어 쉽게 사용할 수 있다.

    스크립트라고 하는 것은 인터프리터(interpreter)에 의해 해석/실행되는 프로그램을 말한다. 어떤 종류의 인터프리터를 사용하는 가에 따라서 어떤 스크립트인가에 대한 이름이 정해진다. 쉘 스크립트, 펄(Perl) 스크립트, Tcl/Tk 스크립트 등의 이름에서 사용하는 인터프리터를 알 수 있다. 쉘 스크립트는 인터프리터로 쉘을 사용하는 스크립트를 가르킨다. 또한 어떠한 쉘을 사용하는 가에 따라서 본(bourne) 쉘 스크립트, C 쉘 스크립트, 콘(Korn) 쉘 스크립트, TC 쉘 스크립트 등으로 나뉜다.

    이 연재에서는 쉘 스크립트 중의 기본이 되는 본 쉘 스크립트에 대해서 살펴볼 것이다. 본 쉘 스크립트에서 사용하는 기능과 문법은 다른 쉘에서도 지원이 된다. 따라서 실제 사용할 때는 다른 쉘을 사용할 수도 있을 것이다. 필자는 bash 1.14.7을 사용하여 설명을 할 것이다.

     

어떤 것이 쉘 스크립트?

    우리는 알게 모르게 많은 쉘 스크립트을 사용한다. 그렇다면 어떤 것이 쉘스크립트인가? 확인을 해보는 방법은 file와 grep명령을 사용해서 찾아보는 것이다. 필자의 시스템에서의 결과는 다음과 같다.

     

      $ (cd /bin ; file * |grep “shell script”)
      false :                   Bourne shell script text
      igawk :                  Bourne shell script text
      remadmin :            Bourne shell script text
      true :                    Bourne shell script text
      $ (cd /usr/bin ; file * |grep “shell script”)
      anytopnm :           Bourne shell script text
      apropos :             Bourne shell script text
      audiocompose :    C shell script text
      audiosend :          C shell script text
      autoconf :            Bourne shell script text
      autoheader :         Bourne shell script text
      autoreconf :         Bourne shell script text
      autoupdate :         Bourne shell script text
      bash2bug :           Bourne shell script text
      bashbug :             Bourne shell script text
      batch :                Bourne shell script text
      bdftops :             Bourne shell script text
      bzless :                Bourne shell script text
      ......
      whatis :               Bourne shell script text
      wv-incconfig :      Bourne shell script text
      wv-libconfig :      Bourne shell script text
      xbm2ikon :           Bourne shell script text
      xbmcut48 :          Bourne shell script text
      xbmsize48 :         Bourne shell script text
      zcmp :                Bourne shell script text
      zdiff :                  Bourne shell script text
      zforce :               Bourne shell script text
      zgrep :                Bourne shell script text
      zipgrep :             Bourne shell script text
      zless :                 Bourne shell script text
      zmore :               Bourne shell script text
      znew :                Bourne shell script text

     

    위의 명령으로 확인을 해보면 false, true, apropos, netscape, nohup, pdf2ps, ps2pdf, whatis, zgrep, zless 등 많은 쉘 스크립트가 있음을 알 수 있다.

     

쉘 스크립트 작성하기

    스크립트의 특징 중의 하나는 그 내용이 텍스트 형식이기 때문에 일반 편집기로 볼 수 있다는 것이다. 위에서 찾는 스크립트 중 아무 것이나 cat, vi, emacs 등으로 보아라. 여러 명령들과 스크립트 문법의 조합으로 되어 있는 것을 확인할 수 있다.

    스크립트의 가장 첫줄은 항상 #!로 시작한다. #! 다음에는 이 스크립트를 실행할 인터프리터와 그 실행 옵션을 지정한다. 쉘 스크립트에서는 #!/bin/sh, #!/bin/csh, #!/bin/bash, #!/bin/ksh, #!/bin/tcsh와 같이 쉘의 절대 경로를 써준다. 전통적인 관습에 의하여 #!만 써줄 경우에는 #!/bin/sh로 인식을 한다.
    따라서 본 쉘 스크립트 작성시에는 첫 줄은 #!/bin/sh나 #!로 하면 된다.

    이제 앞의 예제를 바탕으로 간단한 ‘findscript’ 쉘 스크립트를 작성하여 보자. 이 스크립트는 /bin, /usr/bin에서 쉘 스크립트를 찾아 보여준다.

     

      $ cat findscript
      #!/bin/sh#
      # findscript: /bin, /usr/bin에 있는 쉘 스크립트를
      찾는다.
      #

      (cd /bin; file * | grep “shell script”)
      (cd /usr/bin; file * | grep “shell script”)
      $ chmod +x findscript.sh
      $ ./findscript.sh

 

    cat 또는 다른 편집기를 이용하여 위에서 처럼 작성을 하고, 실행 가능하도록 퍼미션을 준다. 그리고서 다른 프로그램 처럼 실행을 하면 된다. 위에서 본 것과 같이 기본적으로 쉘 스크립트는 쉘에서 사용할 수 있는 명령들의 집합이다. 여기에 덧붙여 쉘에서는 쉘 스크립트에서 사용할 수 있는 나름의 문법을 제공한다.

     

종료 상태 (Exit status)

    각 명령들은 실행을 올바로 마치고 정상적인 종료(성공/참)하거나, 실행 오류 또는 인터럽트에 의한 중단과 같은 이유로 비정상적으로 종료(실패/거짓)할 수 있다. 이 성공과 실패는 명령의 종료 상태값으로 알 수 있다. 보통 0은 성공을 나타내며, 0이 아닌 값은 실패를 나타낸다. 쉘에서는 이전에 실행한 명령이 성공을 하였는지 실패를 하였는지를 ? 변수값에 저장을 한다.

     

      $ who | grep root
      $ echo $?
      1
      $ who | grep hermes44
      hermes44 : 0       Sep 16 11:34
      hermes44 pts/0    Sep 16 11:36
      hermes44 pts/1    Sep 16 14:07
      $ echo $?
      0
      $ false; echo $?
      1
      $ true; echo $?
      0

     

    false 명령은 항상 1, 실패의 종료 상태를 반환하고 true 명령은 항상 0, 성공의 종료 상태를 반환한다.

    쉘 스크립트에서는 exit를 이용하여 스크립트를 종료하면서 종료 상태를 반환할 수 있다. exit만을 사용하면 쉘 스크립트를 종료하고 가장 마지막에 실행한 명령의 종료 상태를 반환하고, exit <숫자>를 사용하면 종료하고 <숫자> 값을 쉘에 반환한다.

     

      $ cat return_exit
      #!/bin/sh
      #
      #return_exit: <인자>의 값을 종료 상태로
      #반환한다.
      #

      if [ $# -eq 0 ]
      then
            exit
      else
            exit $1
      fi
      $ ./return_exit; echo $?
      0
      $ ./return_exit 8; echo $?
      8

     

      if 구문은 조건식에 따라서 분기를 한다.
         형식은 다음과 같다.

      if 조건식
      then
              명령들
      [ elif 조건식
         then
              명령들 ]
      [ else
              명령들 ]
      fi

    if의 조건식이 참이면, 즉 0을 반환하면 then 다음의 명령들을 수행하고, 거짓이면, 즉 0이 아닌 값을 반환하면 (대개 1이다) else 다음의 명령을 수행한다. elif는 if의 조건식이 거짓이 될 경우에 분기되어 다시 새로운 조건식을 검사한다. elif나 else 구문은 생략이 가능하다. fi는 if 구문의 끝은 나타낸다. (fi는 if를 거꾸로 쓴 것이다. :-))

    위의 예제 스크립트에서 if 조건문의 [ $# -eq 0 ]는 스크립트의 인자의 갯수($#)가 0과 같은지(-eq)를 검사하는 것이다. 인자가 없다면 exit를 수행하고 마지막으로 수행한 명령인 조건식의 종료 상태값 0을 반환하다. 인자가 있다면 else 구문에서 첫번째 인자 값을($1)을 종료 상태로 반환한다.

    쉘 스크립트가 수행도중에 인터럽트를 받아서 비정상적으로 종료할 수도 있다. 이런 경우에는 임시로 사용한 파일을 삭제하는 것과 같은 정리 작업을 수행할 수 없게된다. 이를 방지하기 위한 trap 구문이 있다. 형식은 다음과 같다.

      trap ‘명령들’ [시그널 번호들]

    인터럽트에 해당하는 시그널 번호를 써준다. 이 번호는 kill -l을 하면 알 수 있다.시그널 번호를 0을 쓸 경우는 쉘 스크립트가 정상적인 종료를 할 경우를 나타낸다.

    아래의 쉘 스크립트를 수행하면 loop 라는 글씨가 화면에 계속 출력될 것이다. 여기서 사용된 while은 무한 루프를 돌게 된다. 루프에 대해서는 뒤에서 보겠다. 종료를 하기 위해서 Ctrl+C(SIGINT,2)를 누르더라도 이는 첫번째 trap 구문에 의해서 메세지만을 출력한다.
    Ctrl+\(SINQUIT,3)를 누르면 스크립트는 종료를 하게된다.

     

     $ kill -1

    1) SIGHUP

    2) SIGINT

    3) SIGQUIT

    4) SIGILL

    5) SIGTRAP

    6) SIGIOT

    7) SIGBUS

    8) SIGFPE

    9) SIGKILL

    10) SIGUSR1

    11) SIGSEGV

    12) SIGUSR2

    13) SIGPIPE

    14) SIGALRM

    15) SIGTERM

    16) SIGCHLD

    17) SIGCONT

    18) SIGSTOP

    19) SIGTSTP

    20) SIGTTIN

    21) SIGTTOU

    22) SIGURG

    23) SIGXCPU

    24) SIGXFSZ

    25) SIGVTALRM

    26) SIGPROF

    27) SIGWINCH

    28) SIGIO

    29) SIGPWR

     

     

     

    $ cat trap_exit
    #!/bin/sh
    #
    #trap_exit: trap 테스트
    #

    trap ‘echo basename $0: signal catch’ 1 2 15
    trap ‘echo script exit; exit’ 3

    while :
    do
            echo loop
    done
    $ ./trap_exit

     

명령행 인자 (Command-line argument)

    쉘 스크립트에서는 최대 9개의 명령행 인자를 받을 수 있다. 그 값은 $1~$9에 저장된다. (최신 쉘에서는 그 이상의 인자를 받을 수도 있다. ${10}과 같은 형식이다.) $0에는 실행한 쉘 스크립트의 경로가 저장된다. 또한 위에서 잠깐 보았듯이 $#에는 인자의 개수가 저장이 된다.

    스크립트 중에 shift를 사용하였음을 주목하라. shift는 $1 인자를 없애고 각 인자번호를 1씩 줄인다. 즉 $2는 $1, $3은 $2, $4은 $3과 같은 식으로 된다.

     

      #!/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
      fi
      $ ./prog es 34
      Usage: $ prarg arg1 arg2 arg3
      $ ./prog 28 ksl 9
      Script prarg path: ./prarg
      Arg1: 28
      Arg2: ksl
      Arg3: 9

     

    그래서 shift한 이후에는 세번째 인자가 $2가 되는 것이다. 이때 $# 값도 1준다.

    모든 인자는 “$@”, $*에 저장이 된다. “$@”는 명령행에서 사용한 따옴표가 그래도 적용되지만, $*에서는 그 따옴표가 적용되지 않는다. 따옴표에 유무에 따라서 쉘에서 사용하는 특수문자들 - 공백, 탭, \, ‘, “, ` 등 - 해석할 것인지의 여부가 결정이 된다. 이는 쉘을 사용하는 것에 관한 문제이기 때문에 더이상 자세히는 다루지 않겠다.

    $$에는 현재 쉘 스크립트의 프로세스 ID가 저장이 된다. 이는 명령행 인자는 아니지만 임시 파일을 만들 때와 같은 경우에 유융하게 사용된다.

 

루프 (Loop)

    쉘 스크립트에서 사용할 수 있는 루프는 세 가지가 있다.

      while  조건식
      do
                명령들
      done

    while 루프는 조건식이 참인 동안 do~done 사이의 명령을 수행한다.

     

      $ cat arg-while
      #!/bin/sh
      #
      # arg-while: 모든 인자를 출력한다.
      #

      echo Argument number: $#
      while [ $# -gt 0 ]
      do
            echo $1
            shift
      done
      $ ./arg-whilel 1 2 3 4
      Argument number: 4
      1
      2
      3
      4

     

      until   조건식
      do
                명령들
      done

    until 루프는 조건식이 참이 될 때까지 do~done 사이의 명령을 수행한다.

     

      $ cat arg-until
      #!/bin/sh
      #
      # arg-until: 모든 인자를 출력한다.
      #

      echo Argument number: $#
      until [ $# -eq 0 ]
      do
            echo $1
            shift
      done
      $ ./arg-until 1 2 3 4
      Argument number: 4
      1
      2
      3
      4

     

      for 변수 in [문자열 목록]
      do
            명령들
      done

     

      $ cat arg-for
      #
      !/bin/sh
      #
      # arg-for: @ 인자앞의 인자를 출력한다.
      #
      echo Argument number: $#
      for arg
      do
      if [ $arg = @ ]
      then
      break
      fi
      echo $arg
      done
      $ ./arg-for 1 2 @ 3 2 4 6
      Argument number: 7
      1
      2

     

    for 루프는 문자열 목록의 값들이 for 변수에 지정되어 do~done 사이의 명령을 수행한다. in 부분을 생략하면 명령행 인자가 사용된다. break를 사용하면 루프를 빠져나온다.

    루프는 아닌지만 C에서의 switch와 비슷한 역할을 하는 case구문이 있다.
    case의 형식은 다음과 같다.

      case 변수 in
      패턴1) 명령들 ;;
      패턴2) 명령들 ;;
      *) 명령들 ;;
      esac

    변수값을 패턴1부터 비교해가면서 해당하는 패턴이 있으면 그에 해당하는 명령들을 수행한다. ;;은 패턴에 해당하는 명령의 끝을 나타내고 esac은 (case를 거꾸로 쓴 것이다.) case 구문의 끝을 나타낸다. 해당되는 패턴이 없다면 * 패턴의 명령을 수행하게 된다. 패턴에는 쉘에서 사용할 수 있는 와일드 카드(*,?), 정규표현식, 문자열, 변수가 올 수 있다.

     

      $ cat check-flag
      #!/bin/sh
      #
      # check_flag: 인자를 검사하여 flag를 켠다.
      #

      aflag=0
      bflag=0

      for arg
      do
                case $arg in
                 -a) aflag=1 ;;
                 -b) bflag=1 ;;
             *) echo “Usage: `basename $0` [-a] [-b]” 1>&2
                exit 1 ;;
             esac
      done

      echo aflag=$aflag bflag=$bflag
      $ ./check-flag
      aflag=0 bflag=0
      $ ./check-flag -a
      aflag=1 bflag=0
      $ ./check-flag -a -b
      aflag=1 bflag=1 

     

     조건식

    if, while, until 구문에서는 조건식이 사용된다. 조건식은 참과 거짓의 결과를 내는 구문이다. 참일 경우 조건식은 0을 반환하고, 거짓일 경우에는 1을 반환한다. 조건식의 형식은 다음과 같다.

    test 식
    [ 식 ]
    :

    식은 파일식, 문자열식, 수식의 세 종류로 크게 나눌 수 있다. :는 항상 참임을 나타낸다. while, until에서 무한 루프를 만들기 위해 사용된다.

    파일식은 어떤 파일의 속성을 검사하는 것으로 다음과 같은 종류가 있다.

    -b 파일 : 파일이 블럭 장치 파일이면 참
    -c 파일 : 파일이 문자 장치 파일이면 참
    -d 파일 : 파일이 디렉토리이면 참
    -e 파일 : 파일이 존재하면 참
    -f 파일 : 파일이 정규 파일이면 참
    -L 파일 : 파일이 심볼릭 링크이면 참
    -p 파일 : 파일이 네임드(named) 파이프이면 참
    -S 파일 : 파일이 소켓이면 참
    -r 파일 : 파일이 읽기 가능이면 참
    -s 파일 : 파일의 크기가 0보다 크면 참
    -w 파일 : 파일이 쓰기 가능이면 참
    -x 파일 : 파일이 실행 가능이면 참
    파일1 -nt 파일2 : 파일1이 파일2보다 새로운 파일이면 참
    파일1 -ot 파일2 : 파일1이 파일2보다 오래된 파일이면 참
    파일1 -ef 파일2 : 파일1과 파일2가 같은 파일이면 참

    문자열식은 문자열에 대한 비교를 한다.

    -z 문자열 : 문자열의 길이가 0이면 참
    -n 문자열 : 문자일의 길이가 0이 아니면 참
    문자열1 = 문자열2 : 문자열1과 문자열2가 같으면 참
    문자열1 != 문자열2 : 문자열1과 문자열2가 다르면 참

    수식은 숫자값을 비교한다. 양의 정수, 음의 정수, 0, 변수값이 올 수 있다.

    값1 -eq 값2 : 값1 = 값2
    값1 -ne 값2 : 값1 != 값2
    값1 -lt 값2 : 값1 < 값2
    값1 -le 값2 : 값1 <= 값2
    값1 -gt 값2 : 값1 > 값2
    값1 -ge 값2 : 값1 >= 값2

    이외에도 다음과 같이 식 전체에 대한 연산이 가능하다.

    ! 식 : 식에 대한 부정(not)
    식1 -a 식2 : 식1과 식2에 대한 논리곱(and)
    식1 -o 식2 : 식1과 식2에 대한 논리합(or)

 

결론

    이번 기사에서는 쉘 스크립트에 대한 기본적인 사항들을 살펴보았다. 작성, 실행 방법과 기본적인 문법만으로도 간단히 개인적으로 유용하게 사용할 수 있는 스크립트를 작성할 수 있을 것이다. 다음 기사에는 각 세부 사항에 대한 자세한 설명과 기법에 대해서 알아도록 하자.

    <참고자료>
    ·UNIX POWER TOOLS, 2ed. Jerry Peek,
        Tim O’Reilly, Mike Loukides. O’Reilly
    ·bash 매뉴얼 페이지

'개발/활용정보 > Linux' 카테고리의 다른 글

grep 사용법] 리눅스/유닉스/Cygwin 문자열 찾기 find 명령  (0) 2012.01.10
bootchart  (0) 2011.12.29
리눅스 명령어 목록  (0) 2011.10.10
linux 브릿지 설정 brctl  (0) 2011.10.10
ip forwarding, sniffing, nat  (0) 2011.10.07
And