시작하기에 앞서
이 글에서 다루는 셸 스크립트는 <디버깅을 통해 배우는 리눅스 커널의 구조와 원리> 2장에 소개된 것입니다. 이 스크립트가 제대로 동작하기 위해서는 책에 설명된 환경이 갖춰져 있어야 합니다. 여기서는 커널 빌드 과정이 아닌 커널 빌드를 위한 셸 스크립트에 어떤 내용이 담겨있는지를 분석하는 글을 참고하셔서 읽어주시기를 바랍니다.
먼저 분석하고자 하는 예제 스크립트를 살펴본 다음에 셸 스크립트 문법을 딱 필요한 만큼만 알아보겠습니다. 그리고서 예제 스크립트를 한 줄씩 해석해보도록 하겠습니다.
커널 빌드를 위한 셸 스크립트
책에 소개된 셸 스크립트는 아래와 같습니다. 저자가 공유한 예제 스크립트는 이곳을 클릭하시면 확인하실 수 있습니다.
#!/bin/bash
echo "configure build output path"
KERNEL_TOP_PATH="$( cd "$( dirname "$0" )" ; pwd -P )"
OUTPUT="$KERNEL_TOP_PATH/out"
echo "$OUTPUT"
KERNEL=kernel7
BUILD_LOG="$KERNEL_TOP_PATH/rpi_build_log.txt"
echo "move kernel source"
cd linux
echo "make defconfig"
make O=$OUTPUT bcm2709_defconfig
echo "kernel build"
make O=$OUTPUT zImage modules dtbs -j4 2>&1 | tee $BUILD_LOG
셸 스크립트 기본 문법
셸 스크립트는 다른 프로그래밍 언어와 같이 문법이 있습니다. 그러니 제대로 이해하려면 시간을 들여서 공부해야 합니다. 이 글에서는 셸 스크립트 문법을 모두 설명하지 않을 것입니다. 대신 위에 적힌 스크립트를 이해하기에 필요한 만큼만 살펴보도록 하겠습니다.
셸 스크립트 시작
#!/bin/bash
셸 스크립트 맨 첫 줄에는 #!/bin/bash
라고 적습니다. 이 문구는 지금부터 셸 스크립트가 시작된다는 것을 알리는 역할을 합니다.
#!
기호 뒤의 경로는 셸 스크립트를 해석할 인터프리터 실행 경로입니다. 일반적인 셸 스크립트는 #!/bin/sh
라고만 적습니다. 하지만 bash 셸에 특화된 셸 스크립트라면 #!/bin/bash
라고 적어야 합니다. zsh 셸을 사용한다면 #!/bin/zsh
라고 적으면 되겠지요.
출력
echo
명령은 뒤따라오는 값을 출력합니다. echo "configure build output path"
라고 쓰면 터미널에 configure build output path 라고 써지는 것이지요. 문자열은 쌍따온표(")로 묶어줘야 합니다.
변수
변수 이름은 영문, 숫자, 밑줄 기호(언더바)로 만들 수 있습니다. 변수는 대부분의 다른 프로그래밍 언어와 마찬가지로 대, 소문자를 구별합니다. 그리고 변수 이름에 숫자를 포함할 수 있지만 숫자로 시작할 수는 없습니다.
변수에 값을 넣을 때에는 =
기호를 사용하면 됩니다. 중요한 것은 =
기호 앞뒤에 공백이 있으면 안됩니다.
NAME = "Superman" # 이렇게 하면 안됨
NAME="Superman" # = 기호 앞뒤에 공백 없이 사용하기
변수에 들어있는 값을 꺼내 쓰기위해서는 변수 앞에 $
기호를 붙여주면 됩니다. $
기호를 사용하지 않으면 변수명이 그대로 출력되니 주의하세요.
echo $NAME # "Superman" 출력됨
echo NAME # NAME 출력됨
셸 스크립트 실행
셸 스크립트는 텍스트 파일이지만 이를 사용하려면 실행파일로 만들어야 합니다. 아래에 적힌 명령 한 줄은 test.sh
파일에 실행 파일 속성을 더하는 일을 합니다.
chmod +x test.sh
스크립트 분석
첫 줄부터 차근히 살펴보겠습니다.
#!/bin/bash
지금부터 셸 스크립트가 시작됨을 알립니다. bash 셸이 해석하도록 선언하였습니다.
echo "configure build output path"
터미널에 "configure build output path" 라고 출력합니다.
KERNEL_TOP_PATH="$( cd "$( dirname "$0" )" ; pwd -P )"
이 스크립트에서 가장 어려운 부분입니다. KERNEL_TOP_PATH
라는 변수에 셸 스크립트가 위치한 절대 경로를 저장하고 있습니다. 큰따옴표가 총 3쌍이 있는데 가장 바깥쪽부터 분석해보겠습니다.
"$( cd "$( dirname "$0" )" ; pwd -P )"
아직 뭔지 모르겠지만 이 값이 KERNEL_TOP_PATH
에 저장됩니다. 큰따옴표를 한 겹 벗겨보겠습니다.
$( cd "$( dirname "$0" )" ; pwd -P )
그러면 만나게 되는 것이 $()
로 둘러쌓인 덩어리네요. 인터프리터는 $()
로 감싸진 것을 하나의 값으로 해석합니다. 그럼 이것을 한 겹 더 벗겨봅니다.
cd "$( dirname "$0" )" ; pwd -P
bash나 zsh 같은 셸을 사용해보신 분들이라면 이제야 익숙한 표현들이 눈에 들어오실 겁니다. 현재 경로를 변경하는 cd
명령도 보이고 현재 경로를 출력하는 pwd
명령도 보이네요. 여기서는 한 줄로 표현되어 있지만 ;
기호를 기준으로 두 줄로 볼 수 있습니다.
cd "$( dirname "$0" )"
pwd -P
훨씬 보기 편하네요. 여기서 하는 일은 cd
명령으로 어디론가 경로를 바꾸고 pwd
명령으로 이동한 경로를 출력하는 일을 하고 있습니다. pwd
명령 뒤에 붙은 -P
는 현재 경로를 절대경로로 표시하라는 옵션입니다. 자, 그럼 cd
명령으로 이동하려는 경로는 어디일까요? 큰따옴표로 묶인 내용을 꺼내 보겠습니다.
$( dirname "$0" )
또다시 $()
로 묶인 덩어리가 나타났네요. 이것은 값이라고 해석하라는 뜻이라고 말씀드렸습니다. 한 번 더 내용을 꺼내 보겠습니다.
dirname "$0"
dirname
은 명령어입니다. 어떤 역할을 하느냐면 경로에서 상위 디렉터리 이름을 뽑아내는 일을 합니다. 예를 들어 아래처럼 dirname
명령 뒤에 경로를 입력하면,
dirname /home/jakupsil/test.sh
이런 값이 출력됩니다.
/home/jakupsil
참고로 dirname
은 주어지는 경로값에서 상위 디렉터리 이름을 뽑아내기만 하지, 실제로 그 디렉터리가 있는지 확인하지는 않습니다. 그럼 마지막 남은 궁금증 하나를 풀어보겠습니다. 앞에서 적었던 스크립트를 다시 적겠습니다.
dirname "$0"
여기서 "$0"
가 과연 무엇인가가 궁금합니다. 이것은 셸 스크립트에서 사용하는 특수한 변수 중의 하나입니다. 셸 스크립트를 실행할 때 함께 입력되는 인수가 있을 텐데 그 인수를 $번호
형식으로 불러와 쓸 수 있습니다. 0번째 인수는 셸 스크립트의 경로를 포함한 파일명입니다. 예를 들어보죠.
./test.sh hello world
위처럼 셸 스크립트를 실행시켰다면 스크립트 내부에서 $0
는 ./test.sh
가 되고 $1
은 hello
, $2
는 world
가 됩니다. 이해되셨나요?
그럼 우리는 복잡해 보이는 스크립트 한 줄을 다음처럼 해석할 수 있게 되었습니다.
KERNEL_TOP_PATH="$( cd "$( dirname "$0" )" ; pwd -P )"
$0
에 담긴 이 스크립트 파일명과 경로를 가져온다.dirname
명령으로 상위 디렉터리 이름을 뽑아낸다.cd
명령으로 현재 디렉터리를 입력된 경로로 옮긴다.pwd -P
명령으로 현재 디렉터리를 절대경로로 출력한다.- 그 값을
KERNEL_TOP_PATH
에 저장한다.
자 그럼 이어서 스크립트를 해석해보겠습니다.
OUTPUT="$KERNEL_TOP_PATH/out"
echo "$OUTPUT"
OUTPUT
이라는 변수를 만들어 앞서 만든 KERNEL_TOP_PATH
변수 뒤에 /out
이라는 문자열을 덧붙여 저장합니다. 그리고 확인을 위해 터미널에 OUTPUT
변수값을 출력합니다.
KERNEL=kernel7
KERNEL
이라는 변수에 kernel7
이라는 값을 넣습니다.
BUILD_LOG="$KERNEL_TOP_PATH/rpi_build_log.txt"
앞서 만든 KERNEL_TOP_PATH
에다가 /rpi_build_log.txt
라는 문자열을 붙여서 BUILD_LOG
라는 변수에 저장합니다.
echo "move kernel source"
cd linux
move kernel source
라는 문자열을 터미널에 출력합니다. 그리고 현재 디렉터리에 아래에 있는 linux
디렉터리로 이동합니다.
echo "make defconfig"
make O=$OUTPUT bcm2709_defconfig
make defconfig
라는 문자열을 터미널에 출력합니다. 이어서 make
로 커널 빌드에 필요한 설정 파일를 만듭니다. 이때 앞서 만든 OUTPUT
변수에 저장된 값을 넘겨주고 있네요.
echo "kernel build"
kernel build
라는 문자열을 터미널에 출력합니다.
make O=$OUTPUT zImage modules dtbs -j4 2>&1 | tee $BUILD_LOG
마지막으로 커널 빌드를 시작합니다. 이 한 줄에는 두 가지 명령이 있습니다. 하나는 커널을 빌드하기 위한 make
입니다. 다른 하나는 표준 출력으로 출력되는 내용을 모니터에 표시하고 동시에 파일로 저장하는 tee
명령입니다.
이 줄을 조금 더 자세히 들여다보겠습니다. 2>&1
라는 알 수 없는 문구가 있습니다. 이것의 의미는 make
가 출력하는 표준 에러(2)를 표준 출력(1)으로 넘긴다는 의미입니다. 이것을 리다이렉션이라고 합니다.
make
와 tee
명령 사이에 |
기호가 있습니다. 이것은 기호를 기준으로 왼쪽에 있는 명령이 내보내는 출력을 오른쪽 명령으로 전달한다는 의미입니다. 이것을 파이프라인이라고 합니다.
마지막으로 tee
명령은 넘겨받은 출력 내용을 모니터에 표시하고 BUILD_LOG
변수에 담긴 경로에 파일을 저장합니다.
위 내용을 요약하면 다음과 같습니다.
make
명령으로 커널을 빌드한다.- 빌드되는 동안 출력되는 표준 에러는 표준 출력으로 재지정한다.
- 빌드 과정에서 생기는 표준 출력을
|
기호 오른편으로 넘겨준다. tee
명령이 넘겨받은 표준 출력값을 모니터에 표시하고$BUILD_LOG
에 저장한다.
마무리
처음에 이 스크립트와 마주쳤을 때 어떻게 읽어야 하나 감을 잡지 못해 조금 당황했었습니다. 하지만 한 줄씩 뜯어보며 공부해보니 다행히도 몹시 어렵지는 않았습니다. 지금 이 글을 읽고 있는 독자분들도 같은 생각이었으면 좋겠습니다. 글을 마무리하기 전에 셸 스크립트를 공부하기에 좋을 링크 몇 개를 소개하겠습니다. 이 글에서는 셸 스크립트 문법을 아주 일부만 소개했기 때문에 더 공부하고 싶은 분들에게 도움이 될 거로 생각합니다.
혹시 잘못된 내용을 발견하셨거나 궁금한 것이 생기면 댓글로 의견 남겨주시면 감사하겠습니다.
'연구 노트 > 리눅스' 카테고리의 다른 글
WSL 환경에 vcpkg로 GTK 라이브러리 빌드할 때 의존성 문제 해결하기 (0) | 2022.09.13 |
---|