Computer >> 컴퓨터 >  >> 프로그래밍 >> Bash 프로그래밍

셸 추적(Shell Tracing)으로 셸 스크립트 명령 실행 과정 추적하기

이 글은 셸 스크립트 디버깅 시리즈의 세 번째 편으로, 세 번째 디버깅 모드인 셸 추적(shell tracing)에 대해 알아봅니다. 실제 예제를 통해 셸 추적이 어떻게 동작하는지, 그리고 이를 활용해 스크립트의 문제를 찾아내는지 살펴보겠습니다.

시리즈의 앞선 두 편에서는 verbose 모드구문 검사(syntax checking) 모드라는 두 가지 디버깅 방법과, 각 모드에서 셸 스크립트 디버깅을 활성화하는 방법을 자세히 다루었습니다.

  1. Linux에서 셸 스크립트 디버깅 모드 활성화하기 – 1편
  2. 셸 스크립트에서 구문 검사 디버깅 수행하기 – 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편)가 마무리되었습니다. 이 가이드나 시리즈 전반에 대해 궁금한 점이나 피드백이 있다면 언제든 의견을 남겨 주세요.