반응형


http://blog.sdnkorea.com/blog/452


요약: 이 글은 Korn 쉘(ksh) 스크립트의 템플릿과 템플릿에 쓰인 기술들에 대해 설명합니다.

순서


소개

이 글은 ksh 스크립트를 위한 스크립트 템플릿에 관해 설명합니다. 필자는 이 스클비트 템플릿을 일상적인 작업을 위한 모든 스크립트에서 사용 하였습니다. 솔라리스를 운용하는 복수개의 시스템을 가지고 있는 모든 시스템관리자들은 머신을 관리하는 저마다의 스크립트를 가지고 있을 것입니다. 이 글에서 논의되는 프로그래밍 기술들은 이러한 관리자들에게도 매우 유용할 것입니다.

스크립트 템플릿은 CDDL, 버전 1.0 하에서 배포 됩니다; 이 글의 마지막에 스크립트를 다운로드 받을 수 있는 링크가 존재합니다.

맨위로 가기

왜 스크립트 템플릿을 사용하나요?

간단히 답변하자면: 매일매일 새로운 스크립트를 개발하는 것을 원치는 않을 것이기 때문입니다.

자주 사용되는 함수들을 모은 스크립트 템플릿을 한번 작성하고 나면 이것을 다른 스크립트에 재사용함으로써 작업이 아주 편해질 수 있습니다. perl 같은 프로그래밍 언어 대신 쉘 스크립트를 사용함으로써 단순히 스크립트를 새로운 머신에 카피해서 바로 사용할 수 있도록 할 수 있습니다.

참고: 약간의 수정 만으로 스크립트는 AIX 혹은 리눅스 같은 다른 UNIX 플랫폼에서 실행될 수 있을 것입니다. 그러나 아직 확인해 보지는 않았습니다.

맨위로 가기

템플릿에서 사용된 기술들

이 섹션은 스크립트 템플릿에서 사용된 기술들에 대해 설명합니다.

인라인 설명서

여러분들 모두 설명서(Documentation)는 필수적이라는 것을 할 것입니다. 그러나 아무도 그것을 작성하려고 하지 않습니다. 이러한 이유 때문에 필자는 스크립트 내에 주석 형태로 설명서를 포함 시켰습니다. 일반적인 주석과 문서 부분을 구분하기 위해 컬럼 1, 2번 처럼 "##" 을 prefix 로 사용했습니다. 이러한 방법을 통해서 다음의 코드를 사용하여 스크립트의 설명서를 간단히 추출할 수 있습니다:

echo " -----------------------------------------------" >&2
echo "    ${__SCRIPTNAME} ${__SCRIPT_TEMPLATE_VERSION} ">&2
echo "               Documentation" >&2
echo " -----------------------------------------------" >&2

          grep "^##" $0 | cut -c3-80 1>&2 ; die 0 ;;

그러므로 다음의 커맨드는 스크립트의 설명서를 생성합니다:

../scriptt.sh -H 2>./scriptt.txt
맨위로 가기

명명 규칙

필요에 따라 현존하는 스크립트 내의 범용 루틴을을 자유자재로 수정하고, 미리 정의된 글로벌 변수와 스크립트-특수한 변수를 구분하기 위해서 글로벌 변수의 prefix 는 __ 로 지정하였습니다. 예를 들어:

## __MUST_BE_ROOT (def.: false)
##   set to ${__TRUE} for scripts that must be executed by
##   root only
##
__MUST_BE_ROOT=${__FALSE}

## __REQUIRED_USERID (def.: none)
##   required userid to run this script (other than root);
##   use blanks to separate multiple userids
##   e.g. "oracle dba sysdba"
##   "" = no special userid required
##
__REQUIRED_USERID=""

스크립트 내에서 0 이 ok 이고 1 이 not ok 인지 혹은 그 반대인지를 알 수 없기 때문에 필자는 이러한 용도로 두개의 상수 __TRUE__FALSE 를 정의 했습니다. 이러한 상수를 0 혹은 1 대신 사용삼으로써 가독성을 높일 수 있습니다.

중복된 변수 이름의 사용을 방지하기 위해 함수의 모든 로컬 변수들은 typeset 을 이용하여 로컬 변수로 정의 됩니다.

에러를 찾기 힘든 상황을 방지 하기 위해 모든 변수들은 ${varname} 포맷으로 사용됩니다.

스크립트 템플릿 내에서 "실제" 코드로 메꿔져야 할 부분들은 3개의 물음표 부호 (???) 로 표시 되었습니다.

맨위로 가기

로깅

로깅은 스크립트의 기본사항중에 하나로 특히 스크립트가 cron 에 의해 비-상호작용 적인 방법으로 호출 될때에 중요 합니다. 여기에서 한가지 문제는 스크립트의 파라미터를 통해서 로그 파일 이름을 바꾸길 원할 수도 있습니다. 그러나 스클비트는 이미 파라미터가 처리되기 전에 메세지를 출력하고 있습니다. 이러한 문제점을 해결하기 위해 필자가 사용한 방법은 임시 로그 파일을 스크립트가 시작된 바로 다음에 생성하는 것입니다. 그 다음 파라미터가 ㅓ리 되면 임시 로그 파일 을 로그 파일 파라미터의 값에 따라 "실제" 로그 파일로 카피 하거나 혹은 삭제 하는 것입니다.

좀 더 쉬운 사용을 위해서 필자는 다음과 같이 다른 종류의 메세지를 로그하는 추가적인 루틴들을 정의했습니다:

  • LogMsg
  • LogOnly
  • LogInfo
  • LogWarning
  • LogError

LogInfo, LogWarning, 그리고 LogError 는 단순이 특정 표식 문자열 (INFO, WARNING, 혹은 ERROR) 을 메세지에 추가시켜 주는 것이고 LogMsg 를 사용해서 메세지를 로그 하고 출력합니다.

정보 메세지들의 다단계 레벨을 지원하기 위해 스크립트는 VERBOSE_LEVEL 변수를 구현했습니다. 이 변수는 -v 가 발견될때 마다 매번 증가 됩니다. LogInfo 는 요청된 메세지의 상세 레벨에 따라 추가적인 파라미터와 함께 호출 될 수 있습니다. 이러한 경우 현재의 VERBOSE_LEVEL 을 요청된 값과 비교합니다. LogInfoVERBOSE_LEVEL 이 요청된 메세지의 레벨 보다 더 클때에만 메세지를 출력 합니다.

## --------------------------------------
## LogInfo
##
## print a message to STDOUT and write it also to the log file
## only if in verbose mode
##
## usage: LogInfo [loglevel] message
##
## returns: -
##
## Notes: Output goes to STDERR, default loglevel is 0
##
LogInfo() {
  typeset __FUNCTION="LogInfo"; ${__DEBUG_CODE}

  typeset THISLEVEL=0

  if [ ${__VERBOSE_MODE} -eq ${__TRUE} ] ; then
    if [ $# -gt 1 ] ; then
      isNumber $1
      if [ $? -eq ${__TRUE} ] ; then
        THISLEVEL=$1
        shift
      fi
    fi
    [ ${__VERBOSE_LEVEL} -gt ${THISLEVEL} ] && LogMsg "INFO: $*" >&2
  fi
}

예를 들어:

LogInfo    "This message will only be printed if the
  parameter -v is entered one or more times"
LogInfo 1  "This message will only be printed if the
  parameter -v is entered two or more times"
LogInfo 2  "This message will only be printed if the
  parameter -v is entered three or more times"

이 기능은 함수 LogRuntimeInfo 에서 사용됩니다:

## __RT_VERBOSE_LEVEL - level of -v for runtime messages
##
## e.g. 1 = -v -v is necessary to print info messages of
##          the runtime system
##      2 = -v -v -v is necessary to print info messages
##          of the runtime system
##
##
__RT_VERBOSE_LEVEL=1

# ....

LogRuntimeInfo() {
  typeset __FUNCTION="LogRuntimeInfo";    ${__DEBUG_CODE}
  LogInfo "${__RT_VERBOSE_LEVEL}" "$*"
}

"런타임" 시스템의 모든 정보 메세지들(즉 템플릿 내의 기정의된 코드) 은 LogRuntimeInfo 을 통해 출력됩니다. 따라서 __RT_VERBOSE_LEVEL 값은 런타임 시스템의 정보 메세지가 출력되기 전에 몇번의 -v 가 필요한지를 판단합니다.

추가적으로 스크립트에 의해 호출되는 바이너리들의 로깅을 간단히 하기 위해 3가지 함수들이 존재합니다:

  • executeCommand
  • executeCommandAndLog
  • executeCommandAndLogSTDERR

ExecuteCommand 는 어떠한 로깅도 하지 않습니다; executeCommandAndLog 는 로그 파일에 STDOUT 과 STDERR 를 로깅합니다. 그리고 executeCommandAndLogSTDERR 는 오직 STDERR 만을 로그 파일에 저장합니다 (STDOUT 을 다른 용도로 사용해야 할때 특히 유용합니다).

맨위로 가기

런타임 초기화

메인 코드를 호출하기 전에 스크립트 템플릿은 기정의된 변수들을 초기화 하고 몇몇 공통 코드들을 실행합니다.

환경 체크

몇몇 스크립트들은 오직 특정한 하드웨어 혹은 소프트웨어 버전 에서 혹은 루트 권한에 의해서만 실행되어야 합니다. 그러므로 필자는 다음과 같은 체크 루틴을 구현했습니다:

  • 스크립트의 다른 인스턴스가 기동되고 있는가?
  • 스크립트를 실행하고 있는 유저가 루트 인가?
  • 스크립트를 실행하고 있는 유저가 xyz 인가?
  • 실행되고 있는 솔라리스의 버전이 솔라리스 x.y 혹은 더 최신 버전인가?
  • 스크립트가 올바른 타입의 하드웨어(플랫폼, 클래스, CPU) 에서 실행되고 있는가?

적절한 값들이 정의될때에만 체크가 완료 됩니다:

## __MUST_BE_ROOT (def.: false)
##   set to ${__TRUE} for scripts that must be executed by root only
##
__MUST_BE_ROOT=${__FALSE}

## __REQUIRED_USERID (def.: none)
##   required userid to run this script (other than root);
##   use blanks to separate multiple userids
##   e.g. "oracle dba sysdba"
##   "" = no special userid required
##
__REQUIRED_USERID=""

## __ONLY_ONCE (def.: false)
##   set to ${__TRUE} for scripts that cannot run more than one
##   instance at the same time
##
__ONLY_ONCE=${__FALSE}

## __REQUIRED_OS_VERSION (def.: none)
##   minimum OS version necessary, e.g. 5.10
##   "" = no special version necessary
##
__REQUIRED_OS_VERSION=""


## __REQUIRED_MACHINE_PLATFORM (def.: none)
##   required machine platform (uname -i),
##   e.g "i86pc"; use blanks to separate the
##   machine types if more than one entry, e.g "Sun Fire 3800 i86pc"
##   "" = no special machine type necessary
##
__REQUIRED_MACHINE_PLATFORM=""


## __REQUIRED_MACHINE_CLASS (def.: none)
##   required machine class (uname -m),
##   e.g "i86pc"; use blanks to separate the
##   machine types if more than one entry, e.g "sun4u i86pc"
##   "" = no special machine class necessary
##
__REQUIRED_MACHINE_CLASS=""


## __REQUIRED_MACHINE_ARC (def.: none)
##   required machine architecture (uname -p),
##   e.g "i386" ; use blanks to separate the
##   machine types if more than one entry, e.g "sparc i386"
##   "" = no special machine architecture necessary
##
__REQUIRED_MACHINE_ARC=""

# .....

  if [ ${__MUST_BE_ROOT} -eq ${__TRUE} ] ; then
    UserIsRoot || die 249 "You must be root to execute this script"
  fi

  if [ "${__REQUIRED_USERID}"x != ""x ] ; then
    pos " ${__USERID} "  " ${__REQUIRED_USERID} " &&
      die 242 "This script can only be executed by one of the users:
      ${__REQUIRED_USERID}"
  fi

  if [ ${__ONLY_ONCE} -eq ${__TRUE} ] ; then
    CreateLockFile
    if [ $? -ne 0 ] ; then
      cat <<EOF

  ERROR:

  Either another instance of this script is already running
  or the last execution of this script crashes.
  In the first case, wait until the other instance ends;
  in the second case, delete the lock file.

      ${__LOCKFILE}

  manually and restart the script.

EOF

#...
맨위로 가기

스크립트가 오직 하나의 인스턴스만 실행되도록 제한하기

스크립트가 동시에 오직 하나만 실행되도록 보장하는 것은 꽤 어려운 작업입니다. 원자적인 체크 방식을 사용하는 것이 매우 중요 합니다. 이러한 목적을 위해 필자는 ln 을 사용했습니다:

# --------------------------------------
## CreateLockFile
#
# Create the lock file (which is really a symbolic link)
# if possible
#
# usage: CreateLockFile
#
# returns: 0 - lock created
#          1 - lock already exist or error creating the lock
#
# Note: Use a symbolic link because this is always an atomic
#       operation
#
CreateLockFile() {
  typeset __FUNCTION="CreateLockFile";    ${__DEBUG_CODE}

  typeset LN_RC=""

  ln -s $0 "${__LOCKFILE}" 2>/dev/null
  LN_RC=$?

  if [ ${LN_RC} = 0 ] ; then
    __LOCKFILE_CREATED=${__TRUE}
    return 0
  else
    return 1
  fi
}
맨위로 가기

솔라리스10에서 RBAC 사용하기

스크립트의 RBAC 컨트롤을 활성화 시키기 위해 스크립트의 시작 부분에 다음과 같은 코드를 추가하였습니다:

##
## change [ 0 = 1 ] to [ 0 = 0 ] if you want the script running
## with RBAC control
## (Solaris 10 OS and newer only!)
##
## Allow the use of RBAC to control who can access this script.
## Useful for administrators without root permissions
##

if [ 0 = 1 ] ; then
  if [ "$_" != "/usr/bin/pfexec" -a -x /usr/bin/pfexec ]; then
        /usr/bin/pfexec $0 $*
        exit $?
  fi
fi

이제 만약 필자가 스크립트의 RBAC 컨트롤를 활성화시키려고 한다면 단순히 [ 0 = 1 ][ 0 = 0 ] 으로 변경하면 됩니다.

참고: 필자는 이 코드를 솔라리스10의 inetmenu 스크립트에서 인용하였습니다.

맨위로 가기

설정 파일 사용하기

필자의 모든 스크립트들은 최대한 유동적으로 만들기 위해서 설정 파일을 사용합니다. 그러나 중복 코드의 사용을 원하지 않기 때문에 하나는 스크립트 내의 변수 초기화를 위해서, 그리호 하나는 설정 파일을 처리하기 위해서 사용 합니다. 이러한 이유 떄문에 설정 파일은 ksh 의 source-in 기능을 이용해 구현되었습니다.

상세 내용:

설정 변수는 __CONFIG_PARAMETER 에 정의되어 있습니다.

## __CONFIG_PARAMETER
##   The variable __CONFIG_PARAMETER contains the configuration
##   variables
##
# The defaults for these variables are defined here. You
# can use a config file to overwrite the defaults.
#
# Use the parameter -C to create a default configuration file
#
# Note: The config file is read and interpreted via ". configfile" ->
# You can add also some code here!
#
__CONFIG_PARAMETER='

# extension for backup files

DEFAULT_BACKUP_EXTENSION=".$$.backup"

# ??? example variables for the configuration file;
# change to your need

# master server with the directories to synchronize
# The rsync daemon must run either on this host or on localhost
# If the rsync daemon runs on localhost, the master server must
# export the directories to synchronize using NFS. In this case
# the directories on the master server must be the same as on
# the rsync client
#
# overwritten by the parameter -m
  DEFAULT_MASTER_SERVER="linst2.rze.de.db.com"

# server with the rsync daemon. This is either the master server
# or localhost
#
# overwritten by the parameter -s
  DEFAULT_RSYNC_SERVER="localhost"


# only change the following variables if you know what you're doing #

## sample debug code:
## __DEBUG_CODE="  eval echo Entering the subroutine \$__FUNCTION ... "

## Note: Use an include script for more complicated debug code, e.g.
## __DEBUG_CODE=" eval . /var/tmp/mydebugcode"
##

# no further internal variables defined yet
'
# end of config parameters

단일 인용 부호(' ') 내의 문자열로 지정된 변수 이름에 대한 어떠한 평가식도 없기 때문에 특수 문자 처리에 대한 고려를 하지 않아도 됩니다. 그리고 필요하다면 설정 파일 내에서 특정 코드를 실행하도록 추가시킬 수 있습니다.

이제 스크립트 내의 변수를 초기화 하기 위해서 다음의 간단한 라인을 사용합니다:

eval "${__CONFIG_PARAMETER}"

그리고 설정 파일을 읽고 실행하는 코드는:

.. "${THIS_CONFIG_FILE}"

설정 파일을 기본 값을 이용해서 작성하기 위해서는:

cat <<EOT >"${THIS_CONFIG_FILE}"
# config file for $0
${__CONFIG_PARAMETER}
EOT
  THISRC=$?
  

이러한 기능은 스크립트가 -C 와 같이 실행될때 사용됩니다.

이게 끝입니다 -- 매우 간단하고 직관적입니다.

좀 더 깊이 있는 정보는 소스 코드 내의 ReadConfigFileWriteConfigFile 함수에 대해 공부하시기 바랍니다.

맨위로 가기

청소작업

다수의 스크립트들이 놓치고 있는 한가지는 바로 스크립트 종료시에 청소작업 입니다. 예를 들어 임시 파일 및 디렉토리의 삭제등이 있을 수 있습니다.

이러한 용도를 위해서 스크립트 템플릿은 다음의 4가지 변수를 정의 합니다:

## __LIST_OF_TMP_MOUNTS - list of mounts that should be unmounted
## at program end
##
__LIST_OF_TMP_MOUNTS=""

## __LIST_OF_TMP_DIRS - list of directories that should be removed
## at program end
##
__LIST_OF_TMP_DIRS=""

## __LIST_OF_TMP_FILES - list of files that should be removed
## at program end
##
__LIST_OF_TMP_FILES=""

## __EXITROUTINES - list of routines that should be executed before
## the script ends
##
__EXITROUTINES=""

이러한 변수들은 함수 die 에서 호출 되는 cleanup 루틴에서 평가되고 처리 됩니다.

그러므로 이러한 기능을 사용하기 위해서는 스크립트를 항상 die 함수를 이용해서 종료시켜야 합니다. 에러 상황에서도 die 함수가 호출 되도록 하기 위해서 트랩 핸들러가 사용됩니다:

# install trap handler
    trap "GENERAL_SIGNAL_HANDLER ERR   \$LINENO" ERR
    trap "GENERAL_SIGNAL_HANDLER  1    \$LINENO"  1
    trap "GENERAL_SIGNAL_HANDLER  2    \$LINENO"  2
    trap "GENERAL_SIGNAL_HANDLER  3    \$LINENO"  3
    trap "GENERAL_SIGNAL_HANDLER 15    \$LINENO" 15
    trap "GENERAL_SIGNAL_HANDLER exit  \$LINENO" EXIT

# ...


## ---------------------------------------
## die
##
## print a message and end the program
##
## usage: die returncode {message}
##
## returns: -
##
## Notes:
##
## This routine
##  - calls cleanup
##  - prints an error message if any (if returncode is not zero)
##    or the message if any (if returncode is zero)
##  - prints all warning messages again if
##    ${__PRINT_LIST_OF_WARNING_MSGS} is ${__TRUE}
##  - prints all error messages again if
##    ${__PRINT_LIST_OF_ERROR_MSGS} is ${__TRUE}
##  - prints a program end message and the program return code
##  - and ends the program
##
## If the variable ${__FORCE} is ${__TRUE} and returncode is NOT
## zero, die() will only print the error message and return
##
die() {
  typeset __FUNCTION="die";    ${__DEBUG_CODE}

# ...

# the function cleanup handles the unmounting, and the removing of
# files and directories
  cleanup

# ...
}


## ---------------------------------------
## GENERAL_SIGNAL_HANDLER
##
## general trap handler
##
## usage: called automatically (parameter $1 is the signal number;
## $2 is the line no.)
##
## returns: -
##
GENERAL_SIGNAL_HANDLER() {
  typeset __FUNCTION="GENERAL_SIGNAL_HANDLER";  ${__DEBUG_CODE}
  typeset __LINENO=$2

  LogInfo "Trap $1 caught"

  [ "${__INCLUDE_SCRIPT_RUNNING}"x != ""x ] && LogMsg
    "Trap occurred inside of the include
      script \"${__INCLUDE_SCRIPT_RUNNING}\" "

  case $1 in

    1 )
        LogWarning "HUP signal received (while in
          line ${__LINENO}.)"

        InvertSwitch __VERBOSE_MODE
        LogMsg "Switching verbose mode to
          $( ConvertToYesNo ${__VERBOSE_MODE} )"
        ;;

    2 )
        if [ ${__USER_BREAK_ALLOWED} -eq ${__TRUE} ] ; then
          die 252 "Script aborted by the user via signal
            BREAK (CTRL-C) (while in line ${__LINENO}.)"
        else
          LogRuntimeInfo "Break signal (CTRL-C) received and
            ignored (Break is disabled) (while in line ${__LINENO}.)."
        fi
        ;;

    3 )
        die 251 "QUIT signal received (while in line ${__LINENO}.)"
        ;;

   15 )
        die 253 "Script aborted by the external signal TERM
          (while in line ${__LINENO}.)"
        ;;

   "ERR" )
        LogMsg "A command ended with an error (while in
          line ${__LINENO}.)"

    ;;

   "exit" | 0 )
        if [ "${__EXIT_VIA_DIE}"x != "${__TRUE}"x ] ; then
          LogError "exit signal received while in line ${__LINENO}."
          LogWarning "You should use the function \"die\" to end
            the program"
        fi
        return
        ;;

    * ) die 254 "Unknown signal caught: $1 (while in
        line ${__LINENO}.)"
        ;;

  esac
}

사용예:

# add mounts that should be automatically be unmounted at script
# end to this variable
#
__LIST_OF_TMP_MOUNTS="${__LIST_OF_TMP_MOUNTS} /tmp/my_mountpoint.$$ "

# add directories that should be automatically removed at script
# end to this variable
#
__LIST_OF_TMP_DIRS="${__LIST_OF_TMP_DIRS} /tmp/mydir.$$ "

# add files that should be automatically removed at script end
# to this variable
__LIST_OF_TMP_FILES="${__LIST_OF_TMP_FILES} /tmp/mydir.$$/myfile.$$
  /tmp/myfile.out.$$ "

# add functions that should be called automatically at program
# end to this variable
#
__EXITROUTINES="${__EXITROUTINES} my_cleanup_function"

트랩 핸들러의 부가적인 기능들:

여러분이 보듯이 트랩 핸들러 또한 변수 __USER_BREAK_ALLOWED 를 통해서 CTRL-C 핸들링을 구현합니다. CTRL-C 를 무시하기 위해서는 단순히 __USER_BREAK_ALLOWED${__FALSE} 로 지정하면 됩니다.

아래의 코드들은 source-in 스크립트 내에서 발생하는 트랩을 핸들링 하기 위해 필요 합니다:

[ "${__INCLUDE_SCRIPT_RUNNING}"x != ""x ] && LogMsg "Trap
  occurred inside of the include
    script \"${__INCLUDE_SCRIPT_RUNNING}\" "

이 기능을 사용하기 위해서는 includeScript scriptname. scriptname 대신 사용하여 다른 스크립트 파일을 포함 시켜야 합니다.

함수 includeScripts 는 파일을 포함하기 이전에 __INCLUDE_SCRIPT_RUNNING 변수를 설정합니다:

## ---------------------------------------
## includeScript
##
## include a script via . [scriptname]
##
## usage: includeScript [scriptname]
##
## returns: -
##
## notes:
##
includeScript() {
  typeset __FUNCTION="includeScript";    ${__DEBUG_CODE}

  if [ $# -ne 0 ] ; then

# install trap handler
    trap "GENERAL_SIGNAL_HANDLER ERR   \$LINENO" ERR
    trap "GENERAL_SIGNAL_HANDLER  1    \$LINENO"  1
    trap "GENERAL_SIGNAL_HANDLER  2    \$LINENO"  2
    trap "GENERAL_SIGNAL_HANDLER  3    \$LINENO"  3
    trap "GENERAL_SIGNAL_HANDLER 15    \$LINENO" 15
    trap "GENERAL_SIGNAL_HANDLER exit  \$LINENO" EXIT

    LogRuntimeInfo "Including the script \"$*\" ..."

# set the variable for the TRAP handlers
    [ ! -f "$1" ] && die 247 "Include script \"$1\" not found"
    __INCLUDE_SCRIPT_RUNNING="$1"

# include the script
    . $*

# reset the variable for the TRAP handlers
    __INCLUDE_SCRIPT_RUNNING=""

  fi
}
맨위로 가기

미리 정의된 파라미터들

몇몇 파라미터들은 모든 스크립트 내에서 동일합니다. 그러므로 이러한 것들은 스크립트 템플릿에 정의 하는 것이 바람직합니다. 스크립트 템플릿 내의 미리 정의된 파라미터들은 다음과 같습니다:

미리정의된 파라미터들
파라미터 의미
-v verbose 모드 활성화
-q quiet 모드 활성화
-y 모든 질문의 답을 yes 라고 가정
-n 모든 질문의 답을 no 라고 가정
-a 컬러를 사용
-O 기존재하는 파일들을 덮어 씀
-f 강제 실행
-l logfile logfile 을 로그 파일로 사용
-h 간단한 사용예를 보여준 후 종료
-h -v 자세한 사용예를 보여준 후 종료
-H STDERR 에 설명서를 출력하고 종료
-D 메인 함수를 디버그 모드에서 실행
-S n 스크립트 종료시에 에러 및 주의 메세지의 요약을 출력, n 값은:
0 - 어떠한 요약도 출력하지 않음 (기본값)
1 - 에러 메세지 요약 출력
2 - 주의 메세지 요약 출력
3- 에러 및 주의 메세지 요약 출력
-C 기본 설정파일을 생성하고 종료


파라미터 -v, -q, -y, -n, -a, -O, 와 -f 는 더하기 기호와 함께 사용될 수도 있습니다. 예를 들어 +v, +q 같은 방법으로 사용될 수 있습니다.

파라미터 -o 는 변수 __OVERWRITE_MODE 를 지정합니다. 이변수는 함수 BackupFileIfNecessary 에서 체크 됩니다. 그러므로 요청한 바에 따라 기존 파일을 변경하기 전에 BackupFileIfNecessary filename 을 호출하여 변경할 파일의 백업본이 있는지 확인합니다.

맨위로 가기

미리 정의된 변수들

스크립트 템플릿은 (필자 개인적인 의견에 따라) 자주 사용되는 변수들을 초기화 합니다:

## __SCRIPTNAME - name of the script without the path
##
typeset -r __SCRIPTNAME="${0##*/}"

## __SCRIPTDIR - path of the script (as entered by the user!)
##
__SCRIPTDIR="${0%/*}"

## __REAL_SCRIPTDIR - path of the script (real path, maybe a link)
##
__REAL_SCRIPTDIR=$( cd -P -- "$(dirname -- "$(command -v -- "$0")")"
  && pwd -P )

## __CONFIG_FILE - name of the config file
##   (use ReadConfigFile to read the config file;
##   use WriteConfigFile to write it)
##
__CONFIG_FILE="${__SCRIPTNAME%.*}.conf"

## __HOSTNAME - hostname
##
__HOSTNAME="$( uname -n )"

## __NODENAME - nodename
##
__NODENAME=${__HOSTNAME}
[ -f /etc/nodename ] && __NODENAME="$( cat /etc/nodename )"

## __OS - Operating system (e.g. SunOS)
##
__OS="$( uname -s )"

## __OS_VERSION - Operating system version (e.g 5.8)
##
__OS_VERSION="$( uname -r )"


## __ZONENAME - name of the current zone if running in Solaris 10 OS
## or newer
##
__ZONENAME="$( zonename 2>/dev/nul )"


## __OS_RELEASE - Operating system release (e.g. Generic_112233-08)
##
__OS_RELEASE="$( uname -v )"

## __MACHINE_CLASS - Machine class (e.g sun4u)
##
__MACHINE_CLASS="$( uname -m )"

## __MACHINE_PLATFORM - machine platform (e.g. SUNW,Ultra-4)
##
__MACHINE_PLATFORM="$( uname -i )"


## __MACHINE_SUBTYPE - machine type (e.g Sun Fire 3800)
##
__MACHINE_SUBTYPE=""
if [ -x /usr/platform/${__MACHINE_PLATFORM}/sbin/prtdiag ] ; then
  ( set -- $( /usr/platform/${__MACHINE_PLATFORM}/sbin/prtdiag |
    grep "System Configuration" ) ; shift 5; echo $* ) |
      read __MACHINE_SUBTYPE
fi

## __MACHINE_ARC - machine architecture (e.g. SPARC)
##
__MACHINE_ARC="$( uname -p )"

## __START_DIR - working directory when starting the script
##
__START_DIR="$( pwd )"

## __LOGON_USERID - ID of the user opening the session
##
__LOGIN_USERID=$( set -- $( who am i ) ; echo $1 )
[ "${__LOGIN_USERID}" = "" ] && __LOGIN_USERID=${LOGNAME}

## __USERID - ID of the user executing this script (e.g. xtrnaw7)
##
__USERID=${__LOGIN_USERID}
[ -x /usr/ucb/whoami ] && __USERID=$( /usr/ucb/whoami )

## __RUNLEVEL - current runlevel
##
## __RUNLEVEL=$( set -- $( who -r ) ; echo $7 )
__RUNLEVEL=$( who -r 2>/dev/null | tr -s " " | cut -f8 -d " " )

 
맨위로 가기

디버깅

스크립트 템플릿은 또한 몇몇 디버깅 설비들을 구현하였습니다. 그러나 여기서 자세한 설명은 하지 않을 것입니다 관심이 있거나 몇몇 코드를 추가 하고자 한다면 소스 코드를 직접 보시기 바랍니다. 그리고 스크립트를 -D 파라미터와 함께 호출하시기 바랍니다:

../scriptt.sh -D

맨위로 가기

스크립트 템플릿에 정의된 함수들

문자열을 다루는 함수들

REXX 같은 문자열-지향적인 언어에 비해 ksh 가 부족한 것은 문자열을 다루는 함수들입니다. 그러므로 필자는 이러한 작업을 위해서 몇몇 함수들을 작성하였습니다. 함수들은 ksh 의 내부 함수들( 패턴 매칭, typeset 등등)을 최대한 사용하여 sed 나 awk 같은 외부 바이너리의 사용을 최대한 자제하도록 하였 습니다.

모든 함수들은 문자열값을 반환하는데 값을 변수에 담아 리턴해주거나 STDOUT 에 출력합니다; 이것은 다음의 코드를 통해 수행됩니다:

if [ "$4"x != ""x ] ; then
    eval $4=\"${resultstr}\"
  else
    echo "${resultstr}"
fi

문자열을 다루는 함수들은 아래와 같이 정의되어 있습니다:

  • substr sourceStr pos length [resultStr]
  • replacestr sourceStr oldsubStr newsubStr [resultvariable]
  • pos searchstring sourcestring
  • lastpos searchstring sourcestring
  • toUppercase sourceString [resultString]
  • toLowercase sourceString [resultString]
맨위로 가기

데이타 변환 함수들

표준 ksh 스크립트에 없는 또하나의 기능들은 데이타 변환입니다. 이러한 기능을 하는 함수들이 스클비트 템플릿 내에 정의되어 있습니다:

  • isNumer number
  • ConvertToHex value
  • ConvertToOctal
  • ConvertToBinary
맨위로 가기

UID 관련 함수들

자주사용되는 또다른 함수로써 UID 와 유저 이름을 처리하는 함수들이 있습니다:

  • UserIs userid
  • GetCurrentUID
  • GetUserName UID
  • GetUID userid
맨위로 가기

FIFO 스택을 구현한 함수들

간단한 LIFO 스택을 구현한 함수들이 존재 합니다. 이것은 매우 간단한 기능으로 변수의 값을 임시적으로 저장할떄 사용 됩니다.

스택 처리를 위한 함수들은 다음과 같습니다:

  • push value1 [...] [value#]
  • pop variable1 [...] [variable#]
  • push_and_set variable new_value
  • FlushStack
  • NoOfStackElements

사용예는 다음과 같습니다:

# for debugging
   push_and_set __VERBOSE_MODE ${__TRUE}
   push_and_set __VERBOSE_LEVEL ${__RT_VERBOSE_LEVEL}
   LogInfo 0 "Setting variable $P2= \"$( eval "echo \"\$$P1\"")\" "
   pop __VERBOSE_MODE
   pop __VERBOSE_LEVEL
맨위로 가기

기타 함수들

스크립트에 정의된 다른 함수들은 유용할 수도 혹은 그렇지 않을 수도 있습니다. ./scriptt.sh -H 2>./scriptt.txt 를 사용하여 모든 구현된 함수들을 목록화 하는 설명서를 생성하시기 바랍니다.


맨위로 가기

소스 코드

이곳에서 스크립트 템플릿의 소스를 다운로드 받으실 수 있습니다. ".txt" 확장자를 빼고 저장하시기 바랍니다.

반응형
Posted by 공간사랑
,