알아두면 좋은 쉘 스크립트 (+ 배치, CI/CD)
개발을 하다 보면 쉘에서 반복적인 작업들을 위해 같은 명령어들을 계속 치게 되는 자신을 발견하게 된다. 쉘 스크립트는 이런 반복적인 명령어들을 하나의 파일에 담아 파일의 실행만으로 여러 개의 커맨드를 한 번에 작동하게 한다. 쉘 스크립트를 작성할 수 있으면 여러 반복 작업과 배치성 작업 등을 간소화시켜 많은 시간을 절약할 수 있다. 쉘 스크립트 사용 방법과 몇가지 활용 예시에 대해 알아보자.
문법
쉘 스크립트도 하나의 프로그래밍 언어다. 몇 가지 특징적인 활용 방법과 기본 문법에 대해 알아보자.
Shebang (#! interpreter)
#!/bin/bash
쉘 스크립트를 접해봤다면 첫 줄에 #!
로 시작하는 코드를 보았을 것이다. 이 코드는 Shebang이라고 불리며, 맨 윗줄에 작성하여 파일을 어떤 프로그램으로 실행시킬 지를 정의한다. #!
뒤에 있는 코드는 실행시킬 프로그램의 경로를 나타낸다. #!/bin/bash
는 시스템의 /bin 디렉토리 안 bash 프로그램을 통해 파일을 실행시키라는 뜻이다.
위 방법은 절대경로를 이용한 방법인데 Shebang에는 다른 방법으로도 해석시킬 프로그램을 지정할 수 있다.
#!/usr/bin/env bash
위 코드는 env 파일을 통해 PATH에서 실행시킬 프로그램을 찾는 방식이다. 시스템마다 bash 파일의 위치가 달라 절대경로를 이용한 #!/bin/bash
방식은 작동하지 않을 수 있다. #!/usr/bin/env bash
를 이용하면 우리가 추가한 PATH에서 실행할 프로그램을 찾기 때문에 여러 시스템에 일관적으로 잘 작동한다.
참고로 Shebang을 통해서 파이썬으로도 파일을 실행시킬 수 있다. #!/usr/bin/env python
을 맨 윗 줄에 작성하고 파이썬 코드를 친 다음에 실행시키면 파이썬으로 파일이 해석되어 실행된다.
해석시킬 프로그램을 정한 후에 쉘 스크립트를 실행시키기 위해서는 파일에 실행 권한이 필요하다. 간단하게 실행을 위해 파일에 chmod 755 filename
로 권한을 주도록 하자. 실행에 관한 추가적인 내용은 뒤에 설명하도록 하겠다.
변수
변수 선언
#!/usr/bin/env bash
MYNAME="JINWOO"
myname="jinwoo"
echo "MYNAME: ${MYNAME}"
echo "myname: ${myname}"
# 띄어쓰기 있으면 오류 나서 선언 안 됨
VAR_TEST = "TEST" # 오류
변수는 여타 언어에서처럼 =
를 통해 선언한다. 변수명은 대소문자를 구별하여 다르게 선언된다. 보통 환경변수는 대문자로 다른 변수를 소문자로 선언해 환경변수와 스크립트 변수와 충돌을 피한다.
선언된 변수의 참조는 ${MYNAME}
같은 형식으로 이루어진다. $MYNAME
으로 해도 참조할 수 있다. 다만, 변수를 다른 문자와 함께 쓸 때나 변수 참조 시에 문제를 일으킬 수 있어 ${}
로 묶는 형태로 사용하는 것이 좋다.
변수 사용 오류 예시
#!/usr/bin/env bash
var="foo"
var1="bar"
# VAR에 1을 붙여 foo1 을 출력하고 싶은데 VAR1이 참조되 bar가 출력 결과로 나옴
echo "$var1"
# {}로 묶으면 foo1 출력 가능
echo "${var}1"
unset VAR
echo "${var}"
var 변수를 이용해서 foo1을 출력하고 싶은 상황을 가정해보자. var1이 선언되어 있기 때문에 echo "$var1"
을 하면 var1이 참조되어 원하는 결과가 나오지 않는다. 이런 경우를 제외하더라도 실수로 변수 뒤에 문자 하나만 잘못 써도 에러가 날 수 있으므로 ${}
방식을 사용하는 것이 좋다. 가독성 측면에도 좋은 방식이다. 생성된 변수를 제거하려면 unset
을 사용한다.
명령어 내용 변수 입력
#!/usr/bin/env bash
command_res=`ls -al`
echo "COMMAND_RES"
명령어의 결과를 변수로 지정할 수도 있다. `` 안 쪽에 커맨드를 넣으면 실행 결과를 변수에 입력한다.
명령어 표준출력, 표준오류 처리 방법 예시
#!/usr/bin/env bash
# 표준출력만을 변수로 지정, 오류 나면 오류 메세지가 나옴
command_res=`cat test.sh`
# 표준오류만을 변수로 지정, 오류 메세지 나오지 않음. /dev/null 로 표준출력을 한 번 더 리다이렉션해서 버리기 때문에 커맨드 결과가 있더라도 아무것도 지정되지 않음
command_err=`cat test.sh 2>&1 >/dev/null`
# 표준출력과 표준오류 어떤 결과가 나오든 변수로 지정
command_any=`cat test.sh 2>&1`
명령어의 경우, 결과값이 표준출력이 아닌 오류일 수 있다. 이럴 경우 리다이렉션을 통해 표준출력과 표준오류 중 원하는 값을 변수에 지정하도록 코드를 만들 수 있다. 리다이렉션에 대한 내용은 자주 사용하는 리눅스 커맨드 - 사용법 및 예시 를 참조하자.
환경변수 설정
export ENV_VAR="foo"
지금까지 사용된 변수는 쉘 스크립트 내에서 사용되는 변수다. 전체 시스템에 적용되는 변수를 만들고 싶으면 export
를 활용해 환경변수를 선언한다. 변수 선언 시에 이미 정의되어 있는 환경변수를 주의해서 선언하도록 하자.
배열
쉘 스크립트에서도 배열을 사용할 수 있다. 단순 배열과 키 값으로 이루어진 연관 배열 각각 다른 방식으로 선언이 가능하다. 연관 배열은 bash 4 버전 이상에서 작동하므로 기본으로 3 버전을 사용하는 맥OS에서는 연관 배열 사용 시 별도 설정이 필요하다.
기본 배열
#!/usr/bin/env bash
# 일반 배열 선언
declare -a my_array
# 배열 값 선언
my_array=("this" "is" "my" "array")
# 배열 전체 출력 [@] - 사용 권장
# @를 쓰면 배열의 값 각각을 개별로 인식하고 띄어쓰기로 구분해 출력함
# @를 써야 일반적으로 생각하는 방식으로 코드가 돌아감
echo "${my_array[@]}" # 실제: "this" "is" "my" "array"
# 배열 전체 출력 [*]
# *를 쓰면 배열의 모든 결과를 하나의 값으로 묶어서 취급함. 반복문을 쓸 때도 결과를 하나로 묶어버리기 때문에 순회하지 않고 한 번에 결과가 나옴
echo "${my_array[*]}" # 실제: "this is my array"
# 배열 일부 출력
echo "${my_array[2]} ${my_array[3]}"
# 배열 갯수 출력
echo "${#my_array[@]}"
# 배열 값 변경
my_array[0]="This"
# 배열 값 추가
my_array[4]="example"
# 배열 특정 값 삭제
unset my_array[3]
# 배열 복사 후 추가
new_array=($my_array ".")
먼저 기본 배열에 대해 알아보자. 기본 배열은 declare -a myarray
와 같은 형식으로 변수의 형식을 먼저 선언한다. 그 이후 ()
안에 원소들을 나열해 배열 값을 입력한다.
전체 배열을 출력하는 방법은 두 가지가 있다. echo "${my_array[@]}"
처럼 @
를 이용하면 배열의 원소값을 구별해 유지한 채로 띄어쓰기로 구분해 출력한다. echo "${my_array[*]}"
는 배열 값들을 하나로 묶어서 만들고 출력한다. 출력 결과는 같아보이지만 실제 output이 달라 완전히 다른 방식으로 작동하므로 주의를 요한다. 일반적으로 @
를 이용하는 방식을 쓰게 되는 경우가 많다.
연관 배열
#!/usr/bin/env bash
declare -A my_array
my_array=([name]=jin [gender]=male)
echo "${my_array[name]} / ${my_array[gender]}"
my_array[city]=seoul
echo "${my_array[city]}"
쉘 스크립에서도 키 값 형태로 저장되어있는 배열을 사용할 수 있다. declare -A my_array
처럼 -A
를 통해서 선언한다. ()
안에서 [name]=jin
과 같은 형태로 키 값을 입력하여 변수를 입력하고 [key]
를 이용해 값에 접근하거나 새로운 키 값을 생성할 수 있다.
만약 맥OS를 사용한다면 연관 배열을 사용하기 위해 bash 버전을 변경해야 한다. brew install bash
를 통해 bash를 받으면 5 버전의 배쉬가 컴퓨터에 설치되고 /usr/local/bin 에 심볼릭 링크 형태로 저장된다. Shebang을 이용해 #!/usr/bin/env bash
를 작성하면 PATH에서 bash를 찾기 때문에 새로 깔린 bash를 이용해서 스크립트를 해석시킬 수 있다.
참고로 PATH는 /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin 순으로 파일을 찾는다. /usr/local/bin에서 bash가 있으면 /usr/bin까지 가서 bash 파일을 찾지 않는다. brew로 받은 bash 파일은 /usr/local/bin에 있으므로 #!/usr/bin/env bash
가 적힌 스크립트 파일은 brew를 통해 받은 bash로 실행된다. 그렇게 되면 문제없이 declare -A
를 통해 연관 배열을 사용할 수 있다.
if, 비교 연산자, 논리연산자
if 기본 문법
#!/usr/bin/env bash
a="foo"
b="bar"
# 기본 문법 (elif, else는 생략 가능)
if [ "$a" == "$b" ]
then
echo "a equal b"
elif [ "$a" == "foo" ]
then
echo "a equal foo"
else
echo "a not equal b, a not equal foo"
fi
# 축약형
if [ "$a" == "$b" ]; then
echo "a equal b"
elif [ "$a" == "foo" ]; then
echo "a equal foo"
else
echo "a not equal b, a not equal foo"
fi
쉘 스크립트에서 if문은 []
사이에 조건을 넣어서 작성한다. [
와 ]
전후에는 띄어쓰기가 반드시 필요하며, 없으면 문법 오류가 난다. 특이점으로는 if
이후에 then
이 필요하며, 마칠 때 fi
로 마쳐야 한다. if [ condition ]
뒤에 한 줄을 띄어서 작성하는데 if [ "$a" == "$b" ]; then
과 같이 축약이 가능하다.
문자열 비교
# 같음: '=='
# 참고로 '='도 똑같이 쓸 수 있음. '=='는 bash에서만 통용되고 '='는 POSIX에서도 쓸 수 있어 호환성을 중시여기는 사람들은 '='을 씀
if [ "$a" == "$b" ]; then
echo "a equal b"
fi
# 같지 않음: '!='
if [ "$a" != "$b" ]; then
echo "a not equal b"
fi
# 빈 문자열 확인: '-z'
if [ -z "$a" ]; then
echo "length of a = 0"
fi
# ! 를 앞에 써주면 반대 조건을 적용함
if ! [ -z "$a" ]; then
echo "length of a > 0"
fi
문자열 비교는 위와 같은 방식으로 할 수 있다. 컨디션 앞에 !
를 써주면 if not
처럼 반대조건을 적용한다. 문자열 비교가 아니더라도 다른 상황에서도 쓸 수 있는 문법이다.
정수 비교
# [] 사용
# 같음: -eq
if [ "$a" -eq "$b" ]; then
echo "a equal b"
fi
# 다름: -ne
if [ "$a" -ne "$b" ]; then
echo "a not equal b"
fi
# 작음: -lt
if [ "$a" -lt "$b" ]; then
echo "a less than b"
fi
# 큼: -gt
if [ "$a" -gt "$b" ]; then
echo "a greater than b"
fi
# 작거나 같음: -le
if [ "$a" -le "$b" ]; then
echo "a less than or equal to b"
fi
# 크거나 같음: -ge
if [ "$a" -ge "$b" ]; then
echo "a greater than or equal to b"
fi
# (()) 사용
# 작음: -lt
if (( "$a" < "$b" )); then
echo "a less than b"
fi
# 큼: -gt
if (( "$a" > "$b" )); then
echo "a greater than b"
fi
# 작거나 같음: -le
if (( "$a" <= "$b" )); then
echo "a less than or equal to b"
fi
# 크거나 같음: -ge
if (( "$a" >= "$b" )); then
echo "a greater than or equal to b"
fi
쉘 스크립트는 문자열 데이터 외에 정수형 데이터를 다룬다. 조건문에서 정수형 비교를 위한 문법을 지원하며 []
안에서 -eq
, -lt
, -gt
등의 표현식을 사용하거나 (())
안에서 등호와 부등호를 이용해 값을 비교한다.
논리 연산
# 둘 다 참일 때: &&, -a
if [ "$a" -gt "$b" ] && [ "$a" -gt 10 ]; then
echo "a > b and a > 10"
fi
if [ "$a" -gt "$b" -a "$a" -gt 10 ]; then
echo "a > b and a > 10"
fi
# 둘 중 하나라도 참일 때: ||, -o
if [ "$a" -gt "$b" ] || [ "$a" -gt 10 ]; then
echo "a > b or a > 10"
fi
if [ "$a" -gt "$b" -o "$a" -gt 10 ]; then
echo "a > b or a > 10"
fi
&&
, ||
로 and와 or 조건을 적용할 수 있다. -a
, -o
로도 적용이 가능한데 문법이 조금 다르므로 주의해야 한다.
파일 확인
# 파일 존재 유무 판단
if [ -f log_2021.txt ]; then
echo "log file exists"
fi
# 파일이 실행 가능함
if ! [ -x prepare-commit-msg ]; then
chmod 755 prepare-commit-msg
fi
# 디렉토리 존재 유무 판단
if [ -d log ]; then
echo "log directory existed"
fi
스크립트를 사용하는 경우에는 파일을 많이 다루게 된다. 파일에 대한 간단한 확인 방법은 위와 같다.
반복문
반복문 예시
# for문
# 기본
for item in "apple" "bannana" "kiwi"
do
echo ${item}
done
# 축약형
for item in "apple" "bannana" "kiwi"; do
echo ${item}
done
# 배열 사용
FRUIT=("apple" "bannana" "kiwi")
for item in ${FRUIT[@]}; do
echo ${item}
done
# break
for item in ${FRUIT[@]}; do
if [ ${item} == "bannana" ]; then
echo "stop"
break
else
echo "${item}"
fi
done
# continue
for item in ${FRUIT[@]}; do
if [ ${item} == "bannana" ]; then
continue
else
echo "${item}"
fi
done
# C, Java like
for ((i==0; i<10; i++)); do
echo $i
done
# 파일 접근
for file in /home/jin/*; do
echo "$file"
done
# while 문
cnt=0
while (( ${cnt} < 10 )); do
echo ${cnt}
cnt=$(( ${cnt}+1 ))
done
반복문도 여러 형태로 제공된다. 기본적인 for
문과 while
문 사용이 가능하다. for
문 같은 경우, 띄어쓰기로 분리해 작성하거나 배열을 활용해 iterable한 객체를 넘겨줄 수 있다. break
, continue
문법도 지원하며 C나 java처럼 for ((i==0; i<10; i++))
같은 형식으로 반복문을 만들 수 있다. while
문도 여타 언어처럼 활용 가능하다.
함수
함수 선언 예시
# 함수 선언
function test_func() {
echo "this is test function"
}
# function을 빼도 선언됨
test_func2() {
echo "this is test function"
}
# 함수 실행
test_func
test_func2
함수는 function func_name() {}
의 형식으로 선언한다. function은 생략해도 무방하다.
파리미터 사용
param_test() {
echo "$@" # 전체 출력
echo "$#" # 갯수 출력
echo "$0" # 0번째는 파일 이름이 되서 $1부터 파라미터로 사용
echo "$1" # 첫번째 파라미터
echo "$2" # 두번째 파라미터
}
param_test "hello" "world"
파리미터를 받아 함수를 사용할 수 있다. $@
, $1
, $2
같은 형식으로 접근하며, 0번째는 파일 이름이므로 1번째부터 사용해야 한다. $#
로는 갯수를 출력한다.
지역 변수
# 전역변수 선언
name="jin"
echo ${name} # 결과: jin
var_test() {
# 같은 이름으로 지역변수 선언. local이 없으면 전역 변수를 바꿈
local name="woo"
echo ${name}
}
var_test # 결과: woo
# 지역변수를 사용했으므로 전역변수에는 영향 없음
echo ${name} # 결과: jin
다른 언어들과 마찬가지로 전역변수와 지역변수의 개념이 존재한다. 기본적으로 전역변수로 선언이 되는데 local
을 붙이면 함수 내에서는 지역변수로 선언된다.
eval
eval 사용 예시
# 문자열을 바탕으로 명령을 실행
eval "ls -al"
# 파일을 실행시킬 수도 있음 (쉘 스크립트 안에서도 중간에 써서 다른 파일 실행시킬 수 있음)
eval "./test.sh"
스크립트 사용 도중 외부 쉘 스크립트를 추가적으로 실행하는 경우도 있다. eval
을 통해 스크립트 중간에 다른 파일을 실행시키거나 명령어를 실행시킬 수 있다.
expr
expr 사용 예시
a=10
b=2
# expr 사용하면 사칙연산 수행 가능
echo "expr `$a + $b`"
echo "expr `$a - $b`"
echo "expr `$a \* $b`"
echo "expr `$a / $b`"
사칙연산을 수행하고 싶을 때는 expr
을 사용해서 `` 안에 연산 내용을 작성하면 된다.
regex
regex 사용 예시
# log로 시작하는 파일 찾기
ls | grep "^log"
# fi로 끝나는 문장 찾기
cat test.sh | grep "fi$"
쉘 스크립트도 정규식을 지원한다. 정규식의 사용법은 다른 언어와 거의 유사하다.
cron
cron 사용 예시
# [분] [시간] [일] [월] [요일] 커맨드
# 분: 0~59, 시간: 0~23, 일: 1~31, 월: 1~12, 요일: 0~6 (0이 일요일)
# 매일 23시 50분에 명령어 실행
50 23 * * * /usr/local/bin/python3.7 /home/jin/test.py >> /home/jin/temp.log 2>&1
# 1시간마다 명령어 실행
* */1 * * * /usr/local/bin/python3.7 /home/jin/test.py >> /home/jin/temp.log 2>&1
# 매 10분, 20분, 30분, 40분, 50분 마다 명령어 실행
10,20,30,40,50 * * * * /usr/local/bin/python3.7 /home/jin/test.py >> /home/jin/temp.log 2>&1
# 매주 일요일 23시에 명령어 실행
0 23 * * 0 /usr/local/bin/python3.7 /home/jin/test.py >> /home/jin/temp.log 2>&1
쉘 스크립트에서는 특정 주기로 수행하는 배치성 작업을 많이 작성한다. cron을 통해 이런 배치성 작업을 구현할 수 있다. 기본적으로 crontab -e
를 통해 실행되는 파일에 cron 문법을 적어주면 주기적으로 돌아가는 명령어들을 만들 수 있다.
대략적인 cron 문법은 위와 같다. 여러가지 크론 설정 방법은 crontab.guru 를 통해 테스트 해보자.
CentOS에서는 별도 설치가 필요하다. yum install cronie
를 통해 설치해서 사용하자.
주석
첫 라인의 Shebang을 제외하고 #
을 쓰면 주석이 된다.
실행
실행 권한 부여
$ chmod +x target_file.sh
$ chmod 755 target_file.sh
쉘 스크립트를 실행하기 위해서는 실행권한이 필요하다. chmod
를 이용해 권한을 줄 수 있다.
executing과 sourcing
# 새로운 쉘을 통해 파일을 실행하고 결과를 가져옴
$ ./test.sh
# 현재 쉘에서 파일을 실행. 스크립트 내에서 선언된 변수들이 현재 쉘에 그대로 적용됨
$ source test.sh
스크립트를 실행하는 방법은 크게 두 가지가 있다. ./test.sh
는 파일을 실행하되 현재 커맨드를 실행하는 쉘이 아닌 별도의 쉘을 만들어 파일을 작동시키고 결과를 현재 쉘로 가져온다. 반면, source test.sh
는 현재 쉘에서 파일을 실행해 결과 뿐만 아니라 쉘 스크립트에서 선언된 변수들도 쉘에 남게 된다. 흔히 .bashrc
, .bash_profile
을 수정하고 source
로 실행시키는 이유는 쉘을 껐다 다시 킬 필요 없이 현재 쉘에서 파일에 선언된 환경변수들을 바로 사용하기 위해서이다.
참고로 실행을 하기 위해 그냥 test.sh를 쓰면 안 되는 이유는 PATH에 현재 디렉토리는 포함되어있지 않기 때문이다. ./
를 써서 어느 위치에서 파일을 실행시키려 하는 지 명시해줘야 한다.
예시
배치 크롤링, 배치 데이터 업데이트
배치 크롤링, 데이터 업데이트 예시
# 15분 배치 크롤링
*/15 * * * * /usr/local/bin/python3.7 crawl.py >> /home/jin/sample_app/log/crawl_log.txt 2>&1
# 야간 정기 데이터 업데이트
50 23 * * * /usr/local/bin/python3.7 update.py >> /home/jin/sample_app/log/update_log.txt 2>&1
쉘 스크립트와 crontab을 사용해 배치 작업을 만들 수 있다. 15분마다 크롤링이 필요한 데이터를 다룬다고 할 때 */15 * * * *
처럼 cron 문법을 작성하고 crawl.py 파일을 정기적으로 실행시켜 데이터를 받아올 수 있다. 하루가 지나기 전에 데이터를 업데이트 해서 받아와야 되는 작업이 있다고 하면 두번째 케이스처럼 코드를 작성해 구현할 수 있다.
사실 웹어플리케이션들은 프레임워크와 붙여 사용할 수 있는 배치 기능들이 있어 쉘 스크립트로 배치 작업을 한다기보다는 cron 문법을 사용해 spring이나 django 프로젝트 안에 배치 코드를 작성해 관리한다. 위 케이스는 저렇게 할 수도 있구나라고 참고로 알아두자.
DB 작업
DB 백업 및 마이그레이션 예시
#!/usr/bin/env bash
cd /home/jin/sample_app/backup
cur_date=`date +"%y%m%d_%H%M"`
db_from=prd
db_to=dev
# DB 백업
mysqldump --host=localhost --user=jin --password=qwer ${db_fom} setting > backup_${db_from}_setting_${cur_date}.sql
# DB 불러오기
mysql --host=localhost --user=jin --password=qwer ${db_to} < backup_${db_from}_setting_${cur_date}.sql
프로젝트에서는 여러 서버가 나뉘어 관리되고 서로간의 데이터를 주고 받아야 할 일이 생긴다. 개인이 개발하기 위한 local 서버, 코드를 같이 합쳐보는 dev 서버, QA를 위한 staging 서버, 실제 운영을 하는 production 서버 등 프로젝트마다 복잡한 방법으로 서버가 관리되고 데이터를 주고 받는다. 매뉴얼하게 이런 여러개의 서버 간에 데이터를 옮기려고 하면 실수도 많이 나고 시간도 많이 걸린다. 이럴 때 쉘 스크립트로 작업들을 정의해놓으면 많은 시간을 절약할 수 있다.
위 예시는 운영 DB로부터 변경된 설정값들을 가져와서 개발 서버에서 개발하기 위해 DB를 옮기는 작업을 쉘 스크립트로 만든 것이다. 생각보다 이런 작업이 빈번하게 일어나 이런 식의 스크립트가 없으면 굉장히 불편하다. 물론 큰 규모의 프로젝트에서는 Jenkins 같은 CI/CD 툴과 함께 쉘 스크립트를 이용해 DB 작업을 관리한다.
CI/CD (Continous Integration/Continous Deployment)
CI/CD는 개발 중 코드의 통합, 테스트, 배포를 자동화하고 모니터링하기 위한 개념이다. 흔히 Jenkins나 CircleCI 같은 툴을 이용해서 파이프라인 형태로 작업의 순서를 정해 이런 과정을 관리한다.
CI/CD가 구현되면 파이프라인을 바탕으로 git에서 파일 내려받기, 컴파일 오류를 체크, 테스트 코드 확인, 배포 등의 작업이 이루어진다. 쉘 스크립트는 이런 과정 하나하나의 요소로서 원하는 순서대로 커맨드나 파일을 실행시키는데 사용된다.
CI/CD 파이프라인을 직접 구성해본 적이 없어 예시 코드보다는 이해에 도움이 될 글을 소개한다. 젠킨스 같은 툴을 이용하지 않고 쉘 스크립트를 사용해 CI/CD를 구축한 사례를 설명해주셨다. 지리오 서비스 구축기 (2) 쉘 스크립트로 CI/CD 구축하기 를 참고하자.
.githooks
pre-commit-msg 예시
#!/bin/bash
# This hook works for branches named such as "feature/ABC-123-description" and will add "[ABC-123]" to the commit message.
# get current branch
# `` 안에 작성된 커맨드 내용을 branchName 변수로 지정
branchName=`git rev-parse --abbrev-ref HEAD`
# search jira issue id in a pattern such a "feature/ABC-123-description"
# echo $branchName 실행 결과를 sed 커맨드로 넘김. sed로 정규식 매칭을 통해 ABC-123 추출
# 참고로 [a-z]+/([A-Z]+-[0-9]+)-.+ 부분이 패턴에 해당. 뒤에 ,\1 부분은 () 묶여있는 패턴(ABC-123) 부분을 찾겠다는 뜻
jiraId=$(echo $branchName | sed -nE 's,[a-z]+/([A-Z]+-[0-9]+)-.+,\1,p')
# only prepare commit message if pattern matched and jiraId was found
# if ! [ -z $jiraId ]: jiraId가 있을 때
# jiraId에 [] 를 씌우고 원래 메세지인 $1을 붙여 다시 출력함
# 최종 커밋 메세지는 [ABC-123] commit message
if ! [ -z $jiraId ]; then
# $1 is the name of the file containing the commit message
echo -e "[$jiraId] ""$(cat $1)" > "$1"
fi
githooks을 사용해 git의 작동을 컨트롤할 때도 쉘 스크립트가 사용된다. githooks 파일은 쉘 스크립트로 git 관련 특정 커맨드가 실행하기 전이나 후에 개입해 원하는 방향으로 커밋 메세지를 바꾸거나 커밋 하기 전에 압축을 한다거나 행위를 지정할 수 있다.
위 예시는 pre-commit-msg 파일로 브랜치 이름에서 jira 이슈 번호를 가져와 커밋 메세지 앞에 자동으로 jira 이슈 넘버를 포함시켜주는 코드다. 코드에 나온 모든 내용을 포스트에서 다루지는 않았지만 주석을 통해 대략적으로 이해해볼 수 있을 것이다.
마치며
리터러시는 중요하다고 생각한다. 무슨 언어로 개발을 하든 쉘은 다룰 수 밖에 없고 커맨드나 쉘 스크립트를 잘 이해할 수 없으면 불편한 일이 많이 생긴다. 포스트 내용이 조금이나마 쉘 스크립트를 보고 이해하는 데 도움이 되길 바란다.
References
- https://blog.gaerae.com/2015/10/what-is-the-preferred-bash-shebang.html
- https://superuser.com/questions/176783/what-is-the-difference-between-executing-a-bash-script-vs-sourcing-it
- https://gurumee92.github.io/2020/10/지리오-서비스-구축기-2-쉘-스크립트로-ci/cd-구축하기/
- https://linuxconfig.org/how-to-use-arrays-in-bash-script
- https://stackoverflow.com/questions/34984870/order-of-usr-bin-and-usr-local-bin-and-more-in-path
- https://stackoverflow.com/questions/3348443/a-confusion-about-array-versus-array-in-the-context-of-a-bash-comple
- https://tldp.org/LDP/abs/html/comparison-ops.html
- https://chocoamond.tistory.com/56
- https://hbase.tistory.com/10
- https://crontab.guru/
Leave a comment