문제

쉘 스크립트(sh, bash 등)에 대한 모범 사례나 디자인 패턴에 대해 이야기하는 리소스를 아는 사람이 있습니까?

도움이 되었습니까?

해결책

나는 매우 복잡한 쉘 스크립트를 작성했는데 첫 번째 제안은 "하지 마십시오"입니다.그 이유는 스크립트를 방해하거나 위험하게 만드는 작은 실수를 저지르기가 매우 쉽기 때문입니다.

즉, 제 개인적인 경험 외에는 여러분에게 전달해 드릴 수 있는 다른 자료가 없습니다.제가 평소에 하는 일은 과잉이지만 견고한 경향이 있습니다. 매우 말 수가 많은.

기도

스크립트가 길고 짧은 옵션을 허용하도록 만드세요.옵션을 구문 분석하는 두 가지 명령(getopt 및 getopts)이 있으므로 주의하십시오.문제가 덜 발생하려면 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이 아닌 값을 반환하는 경우 예외를 발생시킨 다음 두 개의 "예외 변수"를 설정할 수 있습니다(광산:EXCEPTION 및 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인 파일을 가져올 수 있습니다.

"AModule/ModuleFile" 가져오기

SHELL_LIBRARY_PATH에서 검색됩니다.항상 전역 네임스페이스에서 가져오므로 모든 함수와 변수에 적절한 접두사를 붙여야 합니다. 그렇지 않으면 이름이 충돌할 위험이 있습니다.나는 파이썬 점으로 이중 밑줄을 사용합니다.

또한 이것을 모듈의 첫 번째 항목으로 지정하십시오.

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

객체 지향 프로그래밍

Bash에서는 꽤 복잡한 객체 할당 시스템을 구축하지 않는 한 객체 지향 프로그래밍을 할 수 없습니다.그것은 가능하지만 미친 짓이다).그러나 실제로는 "싱글톤 지향 프로그래밍"을 수행할 수 있습니다.각 개체의 인스턴스는 하나만 있습니다.

내가 하는 일은:저는 객체를 모듈로 정의합니다(모듈화 항목 참조).그런 다음 이 예제 코드와 같이 빈 변수(멤버 변수와 유사), 초기화 함수(생성자) 및 멤버 함수를 정의합니다.

# 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 스크립팅 가이드 Bash뿐만 아니라 쉘 스크립팅에 대한 많은 지혜를 얻으시기 바랍니다.

아마도 더 복잡한 다른 언어를 보라고 말하는 사람들의 말을 듣지 마십시오.쉘 스크립팅이 귀하의 요구 사항을 충족한다면 이를 사용하십시오.당신은 화려함이 아닌 기능성을 원합니다.새로운 언어는 이력서에 귀중한 새로운 기술을 제공하지만 수행해야 할 작업이 있고 이미 쉘을 알고 있는 경우에는 도움이 되지 않습니다.

언급한 대로 쉘 스크립팅에는 "모범 사례"나 "디자인 패턴"이 많지 않습니다.다른 프로그래밍 언어와 마찬가지로 용도에 따라 지침과 편견이 다릅니다.

쉘 스크립트는 파일과 프로세스를 조작하도록 설계된 언어입니다.그에 대한 것이 좋지만 범용 언어는 아니므로 쉘 스크립트에서 새로운 논리를 재현하지 않고 항상 기존 유틸리티의 논리를 붙잡아보십시오.

그 일반적인 원칙 외에 나는 몇 가지를 수집했습니다. 일반적인 쉘 스크립트 실수.

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

쉬운:쉘 스크립트 대신 Python을 사용하십시오.필요하지 않은 것을 복잡하게 만들지 않고도 가독성이 거의 100배 증가하고 스크립트의 일부를 함수, 객체, 영구 객체(zodb), 분산 객체(pyro)로 거의 전혀 발전시키지 않고 발전시킬 수 있는 능력을 유지합니다. 추가 코드.

set -e를 사용하면 오류 발생 후 계속 진행되지 않습니다.Linux가 아닌 환경에서 실행하려면 bash에 의존하지 않고 sh 호환 가능하게 만드십시오.

언제 사용해야 하는지 알아두세요. 빠르고 더러운 접착 명령을 위해서는 괜찮습니다.몇 가지 중요하지 않은 결정, 루프 등을 내려야 한다면 Python, Perl 및 모듈화하다.

쉘의 가장 큰 문제는 종종 최종 결과가 큰 진흙 공, 4000줄의 배쉬 및 성장하는 것처럼 보인다는 것입니다.이제 전체 프로젝트가 이에 의존하기 때문에 이를 제거할 수 없습니다.물론, 40줄부터 시작했어요 아름다운 bash의.

몇 가지 "모범 사례"를 찾으려면 Linux 배포판(예:Debian) init-script를 작성합니다(보통 /etc/init.d에 있음).

대부분은 "bash-isms"가 없으며 구성 설정, 라이브러리 파일 및 소스 형식이 잘 분리되어 있습니다.

내 개인적인 스타일은 일부 기본 변수를 정의하는 마스터-셸 스크립트를 작성한 다음 새 값이 포함될 수 있는 구성 파일을 로드("소스")하는 것입니다.

나는 스크립트를 더 복잡하게 만드는 경향이 있기 때문에 함수를 피하려고 노력합니다.(펄은 그런 목적으로 만들어졌습니다.)

스크립트가 이식 가능한지 확인하려면 #!/bin/sh뿐만 아니라 #!/bin/ash, #!/bin/dash 등을 사용하여 테스트하세요.곧 Bash 관련 코드를 발견하게 될 것입니다.

또는 Joao가 말한 것과 유사한 오래된 인용문:

"펄을 사용하세요.bash를 알고 싶지만 사용하지는 않을 것입니다."

슬프게도 나는 누가 그런 말을 했는지 잊어버렸습니다.

그리고 요즘에는 Perl보다 Python을 추천하고 싶습니다.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top