셸 스크립트의 설계 패턴 또는 베스트 프랙티스
셸 스크립트(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
'programing' 카테고리의 다른 글
Excel VB에서 현재 셀 가져오기 (0) | 2023.04.15 |
---|---|
브라우저에서 응용 프로그램을 실행하는 방법 (0) | 2023.04.15 |
WPF 사용자 컨트롤을 창까지 넓히려면 어떻게 해야 합니까? (0) | 2023.04.15 |
git 로그 또는 git diff 종료 방법 (0) | 2023.04.15 |
DBCC CHECKIDENT가 ID를 0으로 설정하다 (0) | 2023.04.15 |