이 글은 셸 스크립트 디버깅 시리즈의 세 번째 편으로, 세 번째 디버깅 모드인 셸 추적(shell tracing)에 대해 알아봅니다. 실제 예제를 통해 셸 추적이 어떻게 동작하는지, 그리고 이를 활용해 스크립트의 문제를 찾아내는지 살펴보겠습니다.
시리즈의 앞선 두 편에서는 verbose 모드와 구문 검사(syntax checking) 모드라는 두 가지 디버깅 방법과, 각 모드에서 셸 스크립트 디버깅을 활성화하는 방법을 자세히 다루었습니다.
- Linux에서 셸 스크립트 디버깅 모드 활성화하기 – 1편
- 셸 스크립트에서 구문 검사 디버깅 수행하기 – 2편
셸 추적이란 무엇인가?
셸 추적은 말 그대로 셸 스크립트 내에서 명령이 실행되는 과정을 추적하는 기능입니다. 셸 추적을 활성화하려면 -x 디버깅 옵션을 사용합니다.
이 옵션을 지정하면 셸이 명령을 실행할 때마다 해당 명령과 인수를 터미널 화면에 그대로 출력해 줍니다.
예제 스크립트 살펴보기
다음 sys_info.sh 스크립트는 시스템의 날짜와 시간, 로그인한 사용자 수, 시스템 업타임(uptime)을 간단히 출력합니다. 다만 이 스크립트에는 찾아서 수정해야 할 오류가 포함되어 있습니다.
#!/bin/bash
#script to print brief system info
ROOT_ID="0"
DATE=`date`
NO_USERS=`who | wc -l`
UPTIME=`uptime`
check_root(){
if [ "$UID" -ne "$ROOT_ID" ]; then
echo "You are not allowed to execute this program!"
exit 1;
}
print_sys_info(){
echo "System Time : $DATE"
echo "Number of users: $NO_USERS"
echo "System Uptime : $UPTIME
}
check_root
print_sys_info
exit 0파일을 저장한 뒤 실행 권한을 부여합니다. 이 스크립트는 root 사용자만 실행할 수 있으므로 sudo 명령을 사용해 아래와 같이 실행합니다.
$ chmod +x sys_info.sh $ sudo bash -x sys_info.sh
위 출력 결과를 보면, 명령이 먼저 실행된 후 그 결과가 변수 값으로 치환된다는 것을 확인할 수 있습니다.
예를 들어 date 명령이 먼저 실행되고, 그 출력 결과가 변수 DATE의 값으로 대입된 것입니다.
구문 검사로 오류 찾기
구문 오류만 화면에 표시하도록 하려면 아래와 같이 구문 검사를 수행할 수 있습니다.
$ sudo bash -n sys_info.sh
스크립트를 자세히 살펴보면 if 문에 마무리 키워드인 fi가 빠져 있는 것을 알 수 있습니다. 이를 추가하면 스크립트는 다음과 같습니다.
#!/bin/bash
#script to print brief system info
ROOT_ID="0"
DATE=`date`
NO_USERS=`who | wc -l`
UPTIME=`uptime`
check_root(){
if [ "$UID" -ne "$ROOT_ID" ]; then
echo "You are not allowed to execute this program!"
exit 1;
fi
}
print_sys_info(){
echo "System Time : $DATE"
echo "Number of users: $NO_USERS"
echo "System Uptime : $UPTIME
}
check_root
print_sys_info
exit 0파일을 다시 저장하고 root로 구문 검사를 실행해 봅니다.
$ sudo bash -n sys_info.sh
그런데 구문 검사 결과 여전히 21번째 줄에 하나의 버그가 더 있다고 표시됩니다. 즉, 수정할 구문 오류가 아직 남아 있는 것입니다.
스크립트를 한 번 더 분석해 보면, 21번째 줄의 오류는 print_sys_info 함수 내 마지막 echo 명령에 닫는 큰따옴표 (")가 빠졌기 때문에 발생한 것임을 알 수 있습니다.
echo 명령에 닫는 큰따옴표를 추가하고 파일을 저장합니다. 수정된 스크립트는 다음과 같습니다.
#!/bin/bash
#script to print brief system info
ROOT_ID="0"
DATE=`date`
NO_USERS=`who | wc -l`
UPTIME=`uptime`
check_root(){
if [ "$UID" -ne "$ROOT_ID" ]; then
echo "You are not allowed to execute this program!"
exit 1;
fi
}
print_sys_info(){
echo "System Time : $DATE"
echo "Number of users: $NO_USERS"
echo "System Uptime : $UPTIME"
}
check_root
print_sys_info
exit 0이제 스크립트를 다시 한 번 구문 검사해 봅니다.
$ sudo bash -n sys_info.sh
스크립트가 이제 구문적으로 올바르기 때문에 위 명령은 아무런 출력도 생성하지 않습니다. 그 상태에서 스크립트 실행을 다시 한 번 추적해 보면 정상적으로 동작하는 것을 확인할 수 있습니다.
$ sudo bash -x sys_info.sh
이제 실제로 스크립트를 실행해 봅니다.
$ sudo ./sys_info.sh
셸 스크립트 실행 추적이 중요한 이유
셸 스크립트 추적은 구문 오류를 식별하는 데 도움이 될 뿐만 아니라, 무엇보다 논리 오류(logical error)를 찾아내는 데 큰 힘이 됩니다. 예를 들어 sys_info.sh 스크립트의 check_root 함수는 스크립트가 슈퍼유저(superuser)만 실행할 수 있어야 하므로, 현재 사용자가 root인지 판별하는 역할을 합니다.
check_root(){
if [ "$UID" -ne "$ROOT_ID" ]; then
echo "You are not allowed to execute this program!"
exit 1;
fi
}여기서 핵심은 if 문의 조건식인 [ "$UID" -ne "$ROOT_ID" ]입니다. 적절한 숫자 비교 연산자(이 경우 '같지 않음'을 의미하는 -ne)를 사용하지 않으면 논리 오류가 발생할 수 있습니다.
만약 실수로 -eq('같음'을 의미)를 사용했다면, root 사용자뿐 아니라 일반 시스템 사용자까지 스크립트를 실행할 수 있게 되어 논리 오류가 되는 것입니다.
check_root(){
if [ "$UID" -eq "$ROOT_ID" ]; then
echo "You are not allowed to execute this program!"
exit 1;
fi
}참고: 시리즈 초반에서 살펴본 것처럼, 셸 내장 명령인 set을 사용하면 스크립트의 특정 구간에서만 디버깅을 활성화할 수 있습니다.
따라서 아래와 같이 작성하면 함수의 실행을 추적하면서 이 논리 오류를 찾아낼 수 있습니다.
논리 오류가 있는 스크립트:
#!/bin/bash
#script to print brief system info
ROOT_ID="0"
DATE=`date`
NO_USERS=`who | wc -l`
UPTIME=`uptime`
check_root(){
if [ "$UID" -eq "$ROOT_ID" ]; then
echo "You are not allowed to execute this program!"
exit 1;
fi
}
print_sys_info(){
echo "System Time : $DATE"
echo "Number of users: $NO_USERS"
echo "System Uptime : $UPTIME"
}
#turning on and off debugging of check_root function
set -x ; check_root; set +x ;
print_sys_info
exit 0파일을 저장하고 스크립트를 실행해 보면, 아래 출력 결과와 같이 일반 시스템 사용자도 sudo 없이 스크립트를 실행할 수 있음을 확인할 수 있습니다. 그 이유는 현재 사용자의 USER_ID 값이 100으로, root의 ROOT_ID 값인 0과 같지 않기 때문입니다.
$ ./sys_info.sh
마치며
지금까지 셸 추적을 활용해 셸 스크립트의 구문 오류와 논리 오류를 찾아내고 수정하는 방법을 알아보았습니다. 이로써 셸 스크립트 디버깅 시리즈 전체(3편)가 마무리되었습니다. 이 가이드나 시리즈 전반에 대해 궁금한 점이나 피드백이 있다면 언제든 의견을 남겨 주세요.