programing

셸 스크립트의 설계 패턴 또는 베스트 프랙티스

css3 2023. 4. 15. 09:12

셸 스크립트의 설계 패턴 또는 베스트 프랙티스

셸 스크립트(sh, bash 등)의 베스트 프랙티스나 설계 패턴에 대해 설명하는 리소스를 알고 있는 사람이 있습니까?

저는 꽤 복잡한 셸 스크립트를 썼는데, 첫 번째 제안은 "하지 마세요"입니다.그 이유는 그것은 당신의 대본을 방해하거나 심지어 그것을 위험하게 만드는 작은 실수를 하기 쉽기 때문이다.

그렇다고는 해도, 당신에게 건네줄 수 있는 것은 제 개인적인 경험뿐입니다.이게 제가 보통 하는 일입니다. 과잉 살상입니다만, 매우 장황하지만 딱딱한 경향이 있습니다.

호출

스크립트가 긴 옵션과 짧은 옵션을 받아들이도록 합니다.옵션을 해석하는 명령어 getopt과 getopts가2개 있기 때문에 주의해 주세요.문제가 줄어들기 때문에 getopt를 사용합니다.

CommandLineOptions__config_file=""
CommandLineOptions__debug_level=""

getopt_results=`getopt -s bash -o c:d:: --long config_file:,debug_level:: -- "$@"`

if test $? != 0
then
    echo "unrecognized option"
    exit 1
fi

eval set -- "$getopt_results"

while true
do
    case "$1" in
        --config_file)
            CommandLineOptions__config_file="$2";
            shift 2;
            ;;
        --debug_level)
            CommandLineOptions__debug_level="$2";
            shift 2;
            ;;
        --)
            shift
            break
            ;;
        *)
            echo "$0: unparseable option $1"
            EXCEPTION=$Main__ParameterException
            EXCEPTION_MSG="unparseable option $1"
            exit 1
            ;;
    esac
done

if test "x$CommandLineOptions__config_file" == "x"
then
    echo "$0: missing config_file parameter"
    EXCEPTION=$Main__ParameterException
    EXCEPTION_MSG="missing config_file parameter"
    exit 1
fi

또 다른 중요한 점은 프로그램이 정상적으로 완료되면 항상 0을 반환하고, 문제가 발생하면 0을 반환해야 한다는 것입니다.

함수 호출

bash로 함수를 호출할 수 있습니다.콜 전에 함수를 정의하는 것만 기억하십시오.함수는 스크립트와 같으며 숫자 값만 반환할 수 있습니다.즉, 문자열 값을 반환하려면 다른 전략을 개발해야 합니다.RESULT라는 변수를 사용하여 결과를 저장하고, 함수가 정상적으로 완료되면 0을 반환하는 것이 전략입니다.또한 0과 다른 값을 반환하는 경우 예외를 발생시킬 수 있습니다. 그런 다음 두 개의 "예외 변수"(내 이름: EXCEPTURE 및 EXCEPTION_MSG)를 설정할 수 있습니다.첫 번째는 예외 유형을 포함하고 두 번째는 사람이 읽을 수 있는 메시지를 포함합니다.

함수를 호출하면 함수의 매개변수가 특수 변수 $0, $1 등에 할당됩니다.좀 더 의미 있는 이름을 붙이는 게 좋을 것 같아요.함수 내부의 변수를 로컬로 선언합니다.

function foo {
   local bar="$0"
}

오류가 발생하기 쉬운 상황

bash에서는 특별히 선언하지 않는 한 설정되지 않은 변수가 빈 문자열로 사용됩니다.잘못 입력된 변수는 보고되지 않고 비어 있는 것으로 평가되므로 오타가 발생할 경우 매우 위험합니다.사용하다

set -o nounset

이런 일이 일어나지 않게 하려고요.단, 이렇게 하면 정의되지 않은 변수를 평가할 때마다 프로그램이 중단되므로 주의하십시오.따라서 변수가 정의되어 있지 않은지 여부를 확인하는 유일한 방법은 다음과 같습니다.

if test "x${foo:-notset}" == "xnotset"
then
    echo "foo not set"
fi

변수를 읽기 전용으로 선언할 수 있습니다.

readonly readonly_var="foo"

모듈화

다음 코드를 사용하면 "피톤과 같은" 모듈화를 실현할 수 있습니다.

set -o nounset
function getScriptAbsoluteDir {
    # @description used to get the script path
    # @param $1 the script $0 parameter
    local script_invoke_path="$1"
    local cwd=`pwd`

    # absolute path ? if so, the first character is a /
    if test "x${script_invoke_path:0:1}" = 'x/'
    then
        RESULT=`dirname "$script_invoke_path"`
    else
        RESULT=`dirname "$cwd/$script_invoke_path"`
    fi
}

script_invoke_path="$0"
script_name=`basename "$0"`
getScriptAbsoluteDir "$script_invoke_path"
script_absolute_dir=$RESULT

function import() { 
    # @description importer routine to get external functionality.
    # @description the first location searched is the script directory.
    # @description if not found, search the module in the paths contained in $SHELL_LIBRARY_PATH environment variable
    # @param $1 the .shinc file to import, without .shinc extension
    module=$1

    if test "x$module" == "x"
    then
        echo "$script_name : Unable to import unspecified module. Dying."
        exit 1
    fi

    if test "x${script_absolute_dir:-notset}" == "xnotset"
    then
        echo "$script_name : Undefined script absolute dir. Did you remove getScriptAbsoluteDir? Dying."
        exit 1
    fi

    if test "x$script_absolute_dir" == "x"
    then
        echo "$script_name : empty script path. Dying."
        exit 1
    fi

    if test -e "$script_absolute_dir/$module.shinc"
    then
        # import from script directory
        . "$script_absolute_dir/$module.shinc"
    elif test "x${SHELL_LIBRARY_PATH:-notset}" != "xnotset"
    then
        # import from the shell script library path
        # save the separator and use the ':' instead
        local saved_IFS="$IFS"
        IFS=':'
        for path in $SHELL_LIBRARY_PATH
        do
            if test -e "$path/$module.shinc"
            then
                . "$path/$module.shinc"
                return
            fi
        done
        # restore the standard separator
        IFS="$saved_IFS"
    fi
    echo "$script_name : Unable to find module $module."
    exit 1
} 

확장자가 .shinc인 파일을 다음 구문을 사용하여 Import할 수 있습니다.

"AMOschedule/Module File" 가져오기

SHEL_LIBRARY_PATH에서 검색됩니다.글로벌 네임스페이스에서 항상 Import하기 때문에 모든 함수 및 변수 앞에 적절한 프레픽스를 붙여야 합니다.그렇지 않으면 이름이 충돌할 위험이 있습니다.나는 파이썬 도트로 이중 언더스코어를 사용한다.

또, 이것을 모듈에 가장 먼저 기재해 주세요.

# avoid double inclusion
if test "${BashInclude__imported+defined}" == "defined"
then
    return 0
fi
BashInclude__imported=1

객체 지향 프로그래밍

bash에서는 오브젝트 할당의 매우 복잡한 시스템을 구축하지 않으면 오브젝트 지향 프로그래밍을 할 수 없습니다(생각했습니다).실현 가능하지만 미친 짓이다.)그러나 실제로는 "싱글톤 지향 프로그래밍"을 수행할 수 있습니다. 즉, 각 개체의 인스턴스는 1개뿐입니다.

오브젝트를 모듈로 정의합니다(모듈화 엔트리 참조).그런 다음 빈 변수(멤버 변수와 유사함), init 함수(컨스트럭터) 및 멤버 함수를 정의합니다(이 예 코드).

# avoid double inclusion
if test "${Table__imported+defined}" == "defined"
then
    return 0
fi
Table__imported=1

readonly Table__NoException=""
readonly Table__ParameterException="Table__ParameterException"
readonly Table__MySqlException="Table__MySqlException"
readonly Table__NotInitializedException="Table__NotInitializedException"
readonly Table__AlreadyInitializedException="Table__AlreadyInitializedException"

# an example for module enum constants, used in the mysql table, in this case
readonly Table__GENDER_MALE="GENDER_MALE"
readonly Table__GENDER_FEMALE="GENDER_FEMALE"

# private: prefixed with p_ (a bash variable cannot start with _)
p_Table__mysql_exec="" # will contain the executed mysql command 

p_Table__initialized=0

function Table__init {
    # @description init the module with the database parameters
    # @param $1 the mysql config file
    # @exception Table__NoException, Table__ParameterException

    EXCEPTION=""
    EXCEPTION_MSG=""
    EXCEPTION_FUNC=""
    RESULT=""

    if test $p_Table__initialized -ne 0
    then
        EXCEPTION=$Table__AlreadyInitializedException   
        EXCEPTION_MSG="module already initialized"
        EXCEPTION_FUNC="$FUNCNAME"
        return 1
    fi


    local config_file="$1"

      # yes, I am aware that I could put default parameters and other niceties, but I am lazy today
      if test "x$config_file" = "x"; then
          EXCEPTION=$Table__ParameterException
          EXCEPTION_MSG="missing parameter config file"
          EXCEPTION_FUNC="$FUNCNAME"
          return 1
      fi


    p_Table__mysql_exec="mysql --defaults-file=$config_file --silent --skip-column-names -e "

    # mark the module as initialized
    p_Table__initialized=1

    EXCEPTION=$Table__NoException
    EXCEPTION_MSG=""
    EXCEPTION_FUNC=""
    return 0

}

function Table__getName() {
    # @description gets the name of the person 
    # @param $1 the row identifier
    # @result the name
    
    EXCEPTION=""
    EXCEPTION_MSG=""
    EXCEPTION_FUNC=""
    RESULT=""
    
    if test $p_Table__initialized -eq 0
    then
        EXCEPTION=$Table__NotInitializedException
        EXCEPTION_MSG="module not initialized"
        EXCEPTION_FUNC="$FUNCNAME"
        return 1
    fi
    
    id=$1
      
      if test "x$id" = "x"; then
          EXCEPTION=$Table__ParameterException
          EXCEPTION_MSG="missing parameter identifier"
          EXCEPTION_FUNC="$FUNCNAME"
          return 1
      fi
    
    local name=`$p_Table__mysql_exec "SELECT name FROM table WHERE id = '$id'"`
      if test $? != 0 ; then
        EXCEPTION=$Table__MySqlException
        EXCEPTION_MSG="unable to perform select"
        EXCEPTION_FUNC="$FUNCNAME"
        return 1
      fi
    
    RESULT=$name
    EXCEPTION=$Table__NoException
    EXCEPTION_MSG=""
    EXCEPTION_FUNC=""
    return 0
}

신호 트래핑 및 처리

나는 이것이 예외를 포착하고 처리하는 데 유용하다는 것을 알았다.

function Main__interruptHandler() {
    # @description signal handler for SIGINT
    echo "SIGINT caught"
    exit
} 
function Main__terminationHandler() { 
    # @description signal handler for SIGTERM
    echo "SIGTERM caught"
    exit
} 
function Main__exitHandler() { 
    # @description signal handler for end of the program (clean or unclean). 
    # probably redundant call, we already call the cleanup in main.
    exit
} 
    
trap Main__interruptHandler INT
trap Main__terminationHandler TERM
trap Main__exitHandler EXIT

function Main__main() {
    # body
}

# catch signals and exit
trap exit INT TERM EXIT

Main__main "$@"

힌트 및 힌트

어떠한 이유로 동작하지 않는 경우는, 코드의 순서를 변경해 주세요.질서가 중요하며 항상 직관적인 것은 아닙니다.

tcsh를 사용할 생각도 하지 않습니다.기능을 지원하지 않고 전반적으로 끔찍합니다.

주의:내가 여기에 쓴 것을 굳이 사용해야 한다면, 너의 문제는 껍데기로 해결하기에는 너무 복잡하다는 것을 의미한다.다른 언어를 사용하다인적 요인과 유산 때문에 어쩔 수 없이 사용했어요.

Bash 뿐만 아니라 셸 스크립팅에 대한 많은 지식을 얻으려면 Advanced Bash-Scripting Guide를 참조하십시오.

다른 복잡한 언어를 살펴보라는 말은 듣지 마세요.셸 스크립팅이 고객의 요구를 충족시키는 경우는, 그것을 사용합니다.당신은 기능성을 원하죠, 화려함이 아니라.새로운 언어는 당신의 이력서에 귀중한 새로운 기술을 제공하지만, 당신이 해야 할 일이 있고 이미 셸을 알고 있다면 그것은 도움이 되지 않습니다.

앞서 설명한 바와 같이 셸 스크립팅에 대한 "베스트 프랙티스" 또는 "설계 패턴"은 많지 않습니다.다른 프로그래밍 언어처럼 용도에 따라 지침과 편견이 다릅니다.

셸 스크립트는 파일과 프로세스를 조작하도록 설계된 언어입니다.이 기능은 좋지만 범용 언어는 아니므로 셸 스크립트로 새로운 로직을 재작성하지 말고 항상 기존 유틸리티에서 로직을 접목하도록 하십시오.

일반적인 원리 말고도 나는 몇 가지 흔한스크립트의 실수를 수집했다.

사용 타이밍을 파악합니다.빠르고 지저분한 접착 명령어는 함께 사용할 수 있습니다.사소한 결정, 루프 등을 해야 하는 경우 Python, Perl 및 모듈화를 선택합니다.

껍데기의 가장 큰 문제는 종종 최종 결과가 진흙덩어리처럼 보인다는 것입니다. 4000줄의 배쉬와 성장...이제 프로젝트 전체가 거기에 달려있기 때문에 없앨 수 없습니다.물론, 그것은 아름다운 파티 40줄에서 시작되었다.

올해(2008년) OSCON에서 이 주제에 관한 훌륭한 세션이 있었습니다.http://assets.en.oreilly.com/1/event/12/Shell%20Scripting%20Craftsmanship%20Presentation%201.pdf

set -e를 사용하면 오류 발생 후 계속 진행되지 않습니다.Linux 이외의 환경에서 실행되도록 하려면 bash에 의존하지 않고 sh 호환성을 유지하십시오.

간단: 셸 스크립트 대신 python을 사용합니다.가독성이 거의 100배 향상되어 불필요한 작업을 복잡하게 하지 않고 스크립트 일부를 기능, 객체, 영속 객체(zodb), 분산 객체(pyro)로 진화시킬 수 있습니다.

몇 가지 "베스트 프랙티스"를 찾으려면 Linux distro(Debian 등)가 init-script(보통 /etc/init.d에 있음)를 어떻게 기술하는지 살펴봅니다.

대부분은 "bash-isms"가 없으며 구성 설정, 라이브러리 파일 및 소스 포맷을 적절하게 구분합니다.

제 개인 스타일은 몇 가지 기본 변수를 정의하는 마스터 셸 스크립트를 작성한 후 새로운 값을 포함할 수 있는 구성 파일을 로드(소스)하려고 시도하는 것입니다.

스크립트가 복잡해지는 경향이 있기 때문에 피하려고 합니다.(Perl은 그 목적으로 작성되었습니다.)

스크립트가 이식 가능한지 확인하려면 #!/bin/sh뿐만 아니라 #!/bin/ash, #!/bin/dash 등을 사용합니다.곧 Bash 고유의 코드를 찾을 수 있을 겁니다.

언급URL : https://stackoverflow.com/questions/78497/design-patterns-or-best-practices-for-shell-scripts