답변:
나는 매우 복잡한 쉘 스크립트를 작성했고 나의 첫번째 제안은 "하지 말라"이다. 그 이유는 스크립트를 방해하거나 위험하게 만드는 작은 실수를하기가 상당히 쉽다는 것입니다.
즉, 나는 당신에게 전달할 다른 자원이 없지만 내 개인적인 경험이 있습니다. 여기에 내가 일반적으로하는 일은 과잉이지만 매우 장황 하지만 견고 합니다.
기도
스크립트가 길고 짧은 옵션을 허용하도록하십시오. 옵션을 구문 분석하는 명령 인 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"
모듈화
다음 코드를 사용하면 "python like"모듈화가 가능합니다.
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에서는 매우 복잡한 객체 할당 시스템을 구축하지 않으면 객체 지향 프로그래밍을 수행 할 수 없습니다 (생각해 보았지만 미쳤습니다). 그러나 실제로는 "싱글 톤 지향 프로그래밍"을 수행 할 수 있습니다. 각 객체의 인스턴스는 하나뿐입니다.
내가하는 일은 : 객체를 모듈로 정의합니다 (모듈화 항목 참조). 그런 다음이 예제 코드와 같이 빈 vars (멤버 변수와 유사) 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로 작업하는 것도 고려하지 마십시오. 함수를 지원하지 않으며 일반적으로 끔찍합니다.
도움이 되길 바랍니다. 여기에 쓴 것들을 사용해야한다면, 문제가 너무 복잡해서 쉘로 해결할 수 없다는 의미입니다. 다른 언어를 사용하십시오. 나는 인적 요소와 유산으로 인해 그것을 사용해야했습니다.
getopt
대 getopts
? getopts
이식성이 뛰어나 모든 POSIX 셸에서 작동합니다. 특히 질문은 구체적으로 모범 사례를 작성하는 대신 쉘 모범 사례 이므로 가능한 경우 여러 쉘을 지원하기 위해 POSIX 준수를 지원합니다.
Bash 뿐만 아니라 쉘 스크립팅에 대한 많은 지혜를 얻으려면 Advanced Bash-Scripting Guide 를 살펴보십시오 .
다른 복잡한 언어를 보라고 말하는 사람들의 말을 듣지 마십시오. 쉘 스크립팅이 요구 사항을 충족시키는 경우이를 사용하십시오. 당신은 기발함이 아닌 기능성을 원합니다. 새로운 언어는 이력서에 귀중한 새로운 기술을 제공하지만,해야 할 일이 있고 이미 껍질을 알고 있다면 도움이되지 않습니다.
언급 한 바와 같이, 쉘 스크립팅에 대한 "모범 사례"또는 "디자인 패턴"은 많지 않습니다. 용도에 따라 다른 프로그래밍 언어와 마찬가지로 지침과 바이어스가 다릅니다.
쉘 스크립트는 파일과 프로세스를 조작하도록 설계된 언어입니다. 그것이 좋은 점이지만 범용 언어는 아니기 때문에 항상 셸 스크립트에서 새로운 논리를 다시 작성하지 말고 기존 유틸리티의 논리를 붙이십시오.
그 일반적인 원칙 이외의 일반적인 쉘 스크립트 실수를 수집했습니다 .
올해 OSCON (2008)에서이 주제에 관한 훌륭한 세션이있었습니다 : http://assets.en.oreilly.com/1/event/12/Shell%20Scripting%20Craftsmanship%20Presentation%201.pdf
쉬움 : 쉘 스크립트 대신 파이썬을 사용하십시오. 불필요한 것을 복잡하게하지 않고 거의 100 배 가량의 가독성을 얻을 수 있으며, 스크립트의 일부를 거의없이 함수, 객체, 영속 객체 (zodb), 분산 객체 (pyro)로 진화시키는 기능을 보존 할 수 있습니다. 추가 코드.
"모범 사례"를 찾으려면 Linux 배포판 (예 : 데비안)이 init 스크립트를 작성하는 방법 (보통 /etc/init.d에 있음)을 살펴보십시오.
대부분은 "bash-isms"가 없으며 구성 설정, 라이브러리 파일 및 소스 형식이 잘 구분되어 있습니다.
내 개인 스타일은 일부 기본 변수를 정의하는 마스터 셸 스크립트를 작성한 다음 새 값을 포함 할 수있는 구성 파일을로드 ( "소스")하려고합니다.
스크립트를 더 복잡하게 만드는 경향이 있기 때문에 함수를 피하려고합니다. (펄은 그 목적을 위해 만들어졌습니다.)
스크립트를 이식 가능하게하려면 #! / bin / sh뿐만 아니라 #! / bin / ash, #! / bin / dash 등을 사용하여 테스트하십시오. Bash 특정 코드는 곧 발견 될 것입니다.