Debugging
Syntax Check
-n
| set -o noexec
옵션은 명령을 실행하지 않으므로 syntax errors 를 체크하는 용도로 사용할 수 있습니다. 단 syntax 만 체크합니다. 명령 이름에 typo 가 있는지 또는 명령 사용에 오류가 있는지는 각 명령에 해당하는 것이므로 체크되지 않습니다.
./test.sh: line 8: syntax error near unexpected token `fi'
Typo Check
-u
| set -o nounset
옵션은 존재하지 않는 변수를 사용할 경우 에러로 간주하여 exit 합니다. 그러므로 typo check 용도로 사용할 수 있습니다. 다음의 경우 -u
옵션을 사용하지 않았다면 Workspace 디렉토리 전체가 삭제될 수 있습니다.
#!/bin/bash -u
...
TestDir=test2
...
rm -rf ~/Workspace/$Testdir # d 가 소문자
...
################## output ####################
./test.sh: line 15: Testdir: unbound variable
스크립트 실행 당시 어떤 변수가 존재할 수도 있고 아닐 수도 있을 경우 다음과 같은 매개변수 확장 기능을 이용하면 exit 되는 것을 방지할 수 있습니다.
# SOME_ENV_VAR 가 존재하지 않으면 null 을 리턴하게 되고 exit 되지 않습니다.
if [ "${SOME_ENV_VAR:-}" ]; then
echo exist
else
echo null or unset
fi
다음은 함수에 인수를 전달하지 않았을때 체크하는 방법과, 변수가 존재하지 않을때 대체값을 사용하는 방법입니다.
myfunc()
{
# 함수에 인수를 전달하지 않으면 $1 변수는 존재하지 않는 상태.
if [ -n "${1:-}" ]; then ...
# flag 변수가 존재하지 않거나 null 값일 경우 1 을 사용
res=$(( ${flag:-1} + 100 ))
}
Xtrace
Xtrace 의 가장 큰 장점은 매개변수확장, 명령치환, 산술확장이 완료된 명령문을 보여주고 실행한다는 점입니다. 그러므로 명령 실행시 변수값에 대한 정보를 알 수 있고, :
(colon 명령) 을 이용하면 인수 부분에서 필요한 연산 결과를 얻어 낼수도 있습니다. -x
| set -o xtrace
옵션으로 설정할 수 있으며 set -
은 set +o xtrace
와 같은 의미로 스크립트 일부분을 trace 할때 간단히 사용할 수 있습니다..
다음은 xtrace 를 이용해 [
명령의 'too many arguments' 오류를 trace 하는 예입니다.
### trace 에 사용된 스크립트 : xtrace.sh
#!/bin/bash
AA="foo bar"
if [ $AA = "foo bar" ]; then
echo same !!!
else
echo not same !!!
fi
### 실행결과
$ ./xtrace.sh
$ ./xtrace.sh: line 14: [: too many arguments
not same !!!
### xtrace 결과
$ bash -x ./xtrace.sh
+ AA='foo bar'
+ '[' foo bar = 'foo bar' ']' # AA 변수값이 foo bar 두개로 나온다.
./xtrace.sh: line 14: [: too many arguments
+ echo not same '!!!'
not same !!!
### 변수 $AA 를 "$AA" 로 quote 한 후 실행결과
$ bash -x ./xtrace.sh
+ AA='foo bar'
+ '[' 'foo bar' = 'foo bar' ']' # AA 변수값이 'foo bar' 하나로 나온다.
+ echo same '!!!'
same !!! # 결과도 정상적으로 나옴.
xtrace 를 할때 변수이름 앞에 $
문자가 없으면 값이 표시되지 않습니다. 이때 값을 보기 위해 echo 명령을 사용하게 되면 echo 명령도 함께 trace 가 돼서 보기가 안좋은데요. 이때 :
명령을 활용할 수 있습니다. :
도 명령이기 때문에 인수가 확장돼서 나온다는 점을 이용하는 것입니다.
#!/ bin/bash
set -x
for i in {25..28}; do
(( i = i * 123 ))
: i = $i, i / 2 = $(( i / 2 )) # ':' 명령 실행 전에 인수확장이 됨
done
set -
############## output ##############
+ for i in '{25..28}'
+ (( i = i * 123 ))
+ : i = 3075, i / 2 = 1537
+ for i in '{25..28}'
+ (( i = i * 123 ))
+ : i = 3198, i / 2 = 1599
+ for i in '{25..28}'
+ (( i = i * 123 ))
+ : i = 3321, i / 2 = 1660
+ for i in '{25..28}'
+ (( i = i * 123 ))
+ : i = 3444, i / 2 = 1722
+ set -
Xtrace 에서 stepping 을 하자
Xtrace 는 프로그래밍 언어에서 디버깅 할때처럼 break point 를 설정하거나 명령을 하나씩 stepping 할수가 없는데요. DEBUG trap 과 job control 을 이용하면 할 수 있습니다.
https://github.com/mug896/bash-stepping-xtrace
Trapping ERR
스크립트 실행시에 어떤 명령이 에러로 종료했다면 스크립트 실행을 중단해야 하지만 기본적으로 shell 은 중단없이 끝까지 진행을 합니다. 이러한 경우 스크립트 실행을 중지시키기 위해서 -e
| set -o errexit
옵션을 사용할 수 있습니다. 이 옵션은 한가지 단점이 있는데 에러가 난 명령에서 오류 메시지를 출력하지 않으면 별다른 메시지가 표시되지 않는다는 점입니다. 이럴때 ERR trap 과 BASH_SOURCE, LINENO, BASH_COMMAND, $?
변수를 이용하면 에러가 발생한 명령의 위치와 종료 상태 값을 알 수 있습니다.
오류를 발생하는 명령으로 사용되는 스크립트 : error_command.sh
#!/bin/bash
echo --------- error_command.sh --------- start
exit 3
echo --------- error_command.sh --------- end
메인 스크립트 : trap_error.sh-E
| set -o errtrace
옵션은 function 과 subshell 에서도 ERR trap 을 가능하게 합니다.
#!/bin/bash -Ee
trap 'exitcode=$?; echo ERR!!! "${BASH_SOURCE[0]}": $LINENO: exit $exitcode: "$BASH_COMMAND"' ERR
echo --------- trap_err.sh --------- start
./error_command.sh
echo --------- trap_err.sh --------- end
실행결과
$ ./trap_err.sh
--------- trap_err.sh --------- start
--------- error_command.sh --------- start
ERR!!! ./trap_err.sh: 5: exit 3: ./error_command.sh
if, while, &&, || 에서는 ERR trap 이 되지 않는다.
오류가 나는 명령이 if, while, &&, || 에서 사용될 경우는 trap 이 되지 않습니다.
# &&, || 에서 사용되는 경우
./error_command.sh || true
# if 문에서 사용되는 경우
if ./error_command.sh; then
:
else
:
fi
이럴 경우 trap 을 사용하려면 다음과 같이 할 수 있습니다.
./error_command.sh
[ $? -eq 0 ] && echo true
./error_command.sh
if [ $? -eq 0 ]; then
...
ERR trap 을 할때 문제점
산술연산 에서는 연산결과가 0 이면 false 이므로 종료 상태 값으로 1 을 리턴한다.
# let, (( )) 는 산술연산 표현식
$ i=0 # i++ 는 postfix operator 로 연산결과는 0 이고
$ (( i++ )) # 이후에 값이 증가하여 1 이 됩니다.
$ echo $? # 그러므로 종료 상태 값으로 1 을 리턴
1
--------------
$ i=0
$ let i++ # 위와 마찬가지
$ echo $?
1
--------------
# expr 도 산술연산 명령으로 1 - 1 은 0 이므로 false 에 해당합니다.
# 그러므로 expr 명령의 종료 상태 값으로 1 을 리턴합니다.
$ foo=$(expr 1 - 1)
$ echo $?
1
&& , || 에서는 ERR trap 이 되지 않지만 함수에서 마지막 명령으로 사용될 경우 trap 된다.
# 다음의 경우 test 명령이 실패했을때 && 메타문자로 인해 trap 이 되지 않는다.
test -d nosuchdir && ...
# 하지만 함수에서는 마지막 명령의 종료 상태 값을 리턴하므로 에러 trap 이 된다.
f1() { test -d nosuchdir && ... ;}
read 명령에서 -d
옵션 값으로 null 을 사용하거나 end-of-file 상태를 만났을때 에러를 리턴한다.