[Bash] 코딩 규칙 및 모범 사례
Naming and Styles
Tabs and Spaces
ㅁ Tab 사용금지
ㅁ 후행 공백 제거
Pipe
ㅁ inline 파이프와 display 파이프가 있는데, 파이프가 너무 짧은 경우를 제외하고 display 파이프를 사용하여 명확하게 구분하자
ㅁ display 파이프를 사용할 때 파이프( | ) 기호를 명령문 시작 부분에 넣자. (줄 끝에 넣지말자)
This is an inline pipe: "$(ls -la /foo/ | grep /bar/)"
# The following pipe is of display form: every command is on
# its own line.
_foobar="$( \
ls -la /foo/ \
| grep /bar/ \
| awk '{print $NF}')"
_generate_long_lists \
| while IFS= read -r _line; do
_do_something_fun
done
Variable names
ㅁ 변수의 범위에 따라 변수의 이름을 지정
ㅁ 글로벌 변수는 대문자 ( EX. THIS_IS_A_USERXXX )
ㅁ 그 외에 변수는 소문자, 밑줄(_)로 시직한다. 밑줄(_)의 주요 목적은 $변수가 사용될때 거리를 만들어서 코드를 쉽게 읽을 수 있다. (EX. $_this_is_a_xx)
ㅁ 함수 정의 내부의 모든 로컬 변수는 local 명령문으로 선언하자
ㅁ local 명령문에 여러 변수를 선언할 수 있지만 그렇게하면 코드를 읽을 수 없기 때문에 각 라인별로 선언하자
# The following variable can be provided by user at run time.
D_ROOT="${D_ROOT:-}"
# All variables inside `_my_def` are declared with `local` statement.
_my_def() {
local _d_tmp="/tmp/"
local _f_a=
local _f_b=
# This is good, but it's quite a mess
local _f_x= _f_y=
}
Function names
ㅁ 내부 함수의 이름은 밑줄(_)로 시작
ㅁ 밑줄(_)을 사용하여 동사와 명사를 붙이고, 낙타대문자를 사용하지 말자 (EX. ThisIsNotMyStle; use this_is_my_style )
ㅁ 두 밑줄(__)을 사용하여 다른 내부 함수에서 사용하는 경우에 사용하자
Error handling
Sending instructions
ㅁ 모든 오류는 STDERR으로 보냄
ㅁ STDOUT에 오류/경고 메시지를 보내지 말자
ㅁ echo 메시지를 직접 인쇄하는데 사용하지 말고, 대신 래퍼를 사용 (warn, err, die) 사용하자
_warn() {
echo >&2 ":: $*"
}
_die() {
echo >&2 ":: $*"
exit 1
}
ㅁ 다른 기능의 오류를 처리하지 말고, 각 함수는 자체 정의 내에서 자체 구현으로 오류 및 메시지를 처리하자
_my_def() {
_foobar_call
if [[ $? -ge 1 ]]; then
echo >&2 "_foobar_call has some error"
_error "_foobar_call has some error"
return 1
fi
}
ㅁ 위에 예에서 _my_def에 대한 오류를 처리하는데, _foorbar_call 좋은 생각이 아니다
_foobar_call() {
# do something
if [[ $? -ge 1 ]]; then
_error "$FUNCNAME has some internal error"
fi
}
_my_def() {
_foobar_call || return 1
}
Catch up with $?
ㅁ $? 마지막의 리턴 코드를 얻는데 사용되는데, 가장 좋은 방법은 변수를 로컬 변수에 저장하는 것이다
_do_something_critical
local _ret="$?"
# from now, $? is zero, because the latest statement (assignment)
# (always) returns zero.
_do_something_terrible
echo "done"
if [[ $? -ge 1 ]]; then
# Bash will never reach here. Because "echo" has returned zero.
fi
ㅁ $? 는 매우 유용하지만, 너무 믿지 말자
ㅁ set -e 와 $? 를 함께 사용하지 말자
Pipe error handling
ㅁ 파이프는 구성 요소의 리턴 코드를 PIPESTATUS 배열에 저장한다
ㅁ 변수는 파이프 뒤의 하위 파이프에서만 ONCE를 사용할 수 있다 {shell, process }
echo test | fail_command | something_else
local _ret_pipe=( "${PIPESTATUS[@]}" )
ㅁ _ret_pipe 배열의 0 이외에 것이 포함 된 경우 일부 파이프 구성 요소가 실패했지 확인하자
# Note:
# This function only works when it is invoked
# immediately after a pipe statement.
_is_good_pipe() {
echo "${PIPESTATUS[@]}" | grep -qE "^[0 ]+$"
}
_do_something | _do_something_else | _do_anything
_is_good_pipe \
|| {
echo >&2 ":: Unable to do something"
}
Automatic error handling
set -u (매개변수 확장 시 설정되어 있지 않은 변수를 오류로 간주)
ㅁ 항상 set -u 를 사용하여 선언되지 않은 변수를 사용하지 않도록 하자
ㅁ set -u 는 변수를 선언하고 빈 값으로 설정하면 어쩔 수 없다.
set -e (0이 아닌 상태값이면 증시 종료)
ㅁ 스크립트를 전체 배포 할때는 주의하자 (가능하면 사용자가 선택할 수 있는 옵션)
ㅁ set -e 옵션을 이용하여 프로그램의 에러를 검출하고 싶을 때는 리턴코드를 잘 생각하고 사용하자
ㅁ 되도록 trap을 이용하여 에러 검출하여 처리하는 로직이 좋다
set -e
_do_some_critical_check
if [[ $? -ge 1 ]]; then
echo "Oh, you will never see this line"
fi
ㅁ _do_some_critical_check에 실패, 스크립트는 종료하고 다음 코드는 예고없이 건너 뛴다
(false && true); echo not here
{ false && true; }; echo here
Techniques
Make your script a library
ㅁ 첫번째 function 사용하는데, 스크립트에 직접 지시문을 작성하는 대신 래퍼가 존재한다. 아래 방법은 좋지 않다
: do something cool
: do something great
ㅁ function 에 포함시키는 것이 좋다
_default_tasks() {
: do something cool
: do something great
}
ㅁ 마지막 줄에서 스크립트를 실행할 수 있다
case "${@:-}" in
":") echo "File included." ;;
"") _default_tasks ;;
esac
ㅁ 다른 스크립트에서 코드를 실행하지 않고도 스크립트를 쉽게 포함시킬 수 있다
ㅁ 간단한 기술을 발전시킴으로써 스크립트를 디버그하거나 스크립트 동작을 변경할 수 있는 더 많은 옵션을 갖는다
# from other script
source "/path/to_the_previous_script.sh" ":"
Quick self-doc
ㅁ grep을 다음 예제와 같이 사용하여 아름다운 자체 문서를 작성할 수 있다.
_func_1() { #public: Some quick introduction
:
}
_func_2() { #public: Some other tasks
:
}
_quick_help() {
LANG=en_US.UTF_8
grep -E '^_.+ #public' "$0" \
| sed -e 's|() { #public: |☠|g' \
| column -s"☠" -t \
| sort
}
ㅁ _quick_help를 실행하면 다음과 같다
_func_1 Some quick introduction
_func_2 Some other tasks
No excuse
ㅁ 인수 목록이 비어 있으면 아무 것도하지 않고 즉시 종료해라
ㅁ 기본 제약 조건이 설정되지 않은 경우 종료해라
Meta programming
ㅁ Bash는 다음과 같이 매우 강력한 특징을 가지고 있다.
my_func() {
echo "This is my function`"
}
echo "The definition of my_func"
declare -f my_func
# <snip>
ㅁ 왜 중요할까? (예를 들어 기능을 원격으로 전송하고 ssh를 통해 해당 기능을 제외시킴)
ㅁ ssh를 통해 많은 지시를 보내야 할 때, 스크립트를 읽을 수 있도록 도와준다 (그러나 ssh 세션에 대화형 입력 스트림 누락됨)
{
declare -f my_func # send function definition
echo "my_func" # execution instruction
} \
| ssh some_server
Removing with care
ㅁ 파일과 디렉토리를 올바르게 제거하기 힘들다
ㅁ 백업 옵션과 함께 rm을 사용하는 것을 고려해야한다
ㅁ rm 인수에 몇 가지 변수를 사용하면 불변으로 만들 수 있다
export _temporary_file=/path/to/some/file/
readonly _temporary_file
# <snip>
rm -fv "$_temporary_file"
Shell or Python/Ruby/etc
ㅁ 한가지 중요한 요인은 bash가 메모리 효율이 좋지않다. 즉 여러 개의 데이터를 가지고 있다면, 그 데이터에 일정 부분을 추출하고 싶을 때마다 데이터를 다시 로드할 수 있다
ㅁ 스크립트가 어떤 종류의 데이터라도 해석해야 할 때, 앞으로 나아가서 다른 언어로 스크립트를 다시 쓰는 것이 좋다
Good lessons
See also in LESSONS.md (https://github.com/icy/bash-coding-style/blob/master/LESSONS.md).
Resources
Authors. License
원저자는 Anh K. Huynh이며 원작은 TheSLinux의 일부였습니다.
일부 기고자들이 오류를 수정하고 스타일을 개선하는 데 도움을 받았습니다. 그들은 또한 저자입니다.
이 작업은 MIT 라이센스에 따라 릴리스됩니다.
출처: <https://github.com/icy/bash-coding-style>