Shell Script 템플릿
쉘스크립트 템플릿 분석
동기
리눅스 쉘 스크립트를 공부하다가, 쉘 스크립트에 공통적으로 쓰이는 boiler-plate 같은 게 있을것 같았습니다. 이 곳에 원문이 있고, 이 분이 한국어로 번역을 해 두었으니 전체적인 설명을 보려면 링크타고 가면 됩니다.
저는 쉘 스크립트 문법을 공부하려는 의도라서, 스크립트의 전체적인 구조나 의도는 생략하겠습니다.
Shell script template 원문
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
#!/usr/bin/env bash
set -Eeuo pipefail
trap cleanup SIGINT SIGTERM ERR EXIT
script_dir=$(cd "$(dirname "${BASH_SOURCE[0]}")" &>/dev/null && pwd -P)
usage() {
cat <<EOF
Usage: $(basename "${BASH_SOURCE[0]}") [-h] [-v] [-f] -p param_value arg1 [arg2...]
Script description here.
Available options:
-h, --help Print this help and exit
-v, --verbose Print script debug info
-f, --flag Some flag description
-p, --param Some param description
EOF
exit
}
cleanup() {
trap - SIGINT SIGTERM ERR EXIT
# script cleanup here
}
setup_colors() {
if [[ -t 2 ]] && [[ -z "${NO_COLOR-}" ]] && [[ "${TERM-}" != "dumb" ]]; then
NOFORMAT='\033[0m' RED='\033[0;31m' GREEN='\033[0;32m' ORANGE='\033[0;33m' BLUE='\033[0;34m' PURPLE='\033[0;35m' CYAN='\033[0;36m' YELLOW='\033[1;33m'
else
NOFORMAT='' RED='' GREEN='' ORANGE='' BLUE='' PURPLE='' CYAN='' YELLOW=''
fi
}
msg() {
echo >&2 -e "${1-}"
}
die() {
local msg=$1
local code=${2-1} # default exit status 1
msg "$msg"
exit "$code"
}
parse_params() {
# default values of variables set from params
flag=0
param=''
while :; do
case "${1-}" in
-h | --help) usage ;;
-v | --verbose) set -x ;;
--no-color) NO_COLOR=1 ;;
-f | --flag) flag=1 ;; # example flag
-p | --param) # example named parameter
param="${2-}"
shift
;;
-?*) die "Unknown option: $1" ;;
*) break ;;
esac
shift
done
args=("$@")
# check required params and arguments
[[ -z "${param-}" ]] && die "Missing required parameter: param"
[[ ${#args[@]} -eq 0 ]] && die "Missing script arguments"
return 0
}
parse_params "$@"
setup_colors
# script logic here
msg "${RED}Read parameters:${NOFORMAT}"
msg "- flag: ${flag}"
msg "- param: ${param}"
msg "- arguments: ${args[*]-}"
테스트
1
2
3
4
5
$ ./test.sh -p param argument
Read parameters:
- flag: 0
- param: param
- arguments: argument
사용된 문법들
#! (SheBang, 사용할 쉘 선언)
보통 인터넷 자료를 보면 #!/bin/sh
혹은 #! /bin/sh
같은 식인데, 템플릿에선 #!/usr/bin/env bash
를 사용하고 있습니다. 찾아보니 원래는 #!/bin/bash
처럼 bash 의 절대경로를 넣어줘야 하는데, #!/usr/bin/env
는 뭘까요?
사실 원래 엄밀하겐 사용할 쉘을 선언하는게 아니라, 가장 먼저 실행될 프로그램을 지정하는 거라고 해요. 찾아보면 이런 예제도 있습니다.
#!bin/rm
:
:
아이러니하게도 이렇게 쓰여진 쉘 스크립트는 자기 자신을 지우는 스크립트가 됩니다. 어쨋든 #!/usr/bin/env bash
도 env bash
명령어를 먼저 실행시킨다는 의미가 되겠네요. 쉘에서 env
명령어는 단독으로 쓰면 환경변수를 출력하지만, argument 를 주면 내 환경에서 해당 프로그램을 찾아 실행시키는 명령어가 됩니다. 이런 기능 덕분에 #!/usr/bin/env python
처럼 사용되곤 합니다.
trap
특정 시그널을 잡아채서, 지정한 명령어를 실행합니다.
trap [실행될 명령어] [잡아챌 시그널(들)]
이런 식으로 사용하고, 간단한 스크립트에선 one-liner 처럼 사용될 수 있습니다.
trap `rm -f $TEMP; exit $USER_INTERRUPT` SIGINT
… 이런 식으로요.
템플릿에선 SIGINT
, SIGTERM
, ERR
, EXIT
네가지 시그널에 대해 반응하도록 만들었는데, 필요하다면 여기서 가감해도 될 것입니다. 참고로 SIGINT
는 Ctrl+C 입력으로 종료시, SIGTERM
은 kill
명령어로 종료시 발생합니다.
/dev/null
script_dir
에 쉘 스크립트가 실행되고 있는 경로를 할당하는데, 중간에 /dev/null
이라는 파일이 있습니다. /dev
에 있으니까 장치에 관련된 파일인데, /dev/null
은 그냥 비워져 있는 파일이라고 하고, 표준 출력을 버리고 싶을 때 사용한다고 합니다.
EOM, here doc
이전 포스팅 에도 비슷한 내용이 있었습니다. 거기선 이런 식으로 작성되어있었습니다.
if [ $# != 2 ]; then cat << EOM
usage: $0 ENDPOINT INDEX
where ENDPOINT is the path to the qbox.io endpoint (or http://localhost:9200)
and INDEX is the name of the endpoint, e.g. 'sagan-production'
EOM
exit
fi
위 스크립트 같은 경우 인자를 잘못 넣어줬을 경우 해당 출력을 보여줍니다. 템플릿에선 usage()
함수를 만들어줬는데, 크게 특별한 것은 없습니다. 다만 나중에 -h
같은 옵션에서 호출되기 좋아서 템플릿의 convention 이 더 괜찮아 보이긴 합니다.
if - then - else - fi
쉘 스크립트의 if 문은 다른 언어의 if 문과 비슷하긴 한데, 좀 다른 점이 있습니다. 조건 안에 명령문이 정상종료가 되면 0 를 리턴하는데, 이걸 true
처럼 봅니다. (python 하고 어떻게 보면 반대로 작동) (())
혹은 [[]]
로 감싸면 python 같은 언어에서 하는 것처럼 사용할 수 있습니다만 이런 헷갈리는 부분은 확실히 골치아픕니다…
참고로 [[]]
에서 사용하는 옵션은 test
가 사용하는 옵션과 같습니다. (그냥 동등하다고 생각하면 됨)
”${1-}”
첫 번째 argument 를 뜻하는데, 없을 경우 아무것도 안쓴다는 뜻입니다.
원래는 "${var-default}"
같은 식으로 사용합니다.
@, *
둘 다 전부를 뜻하긴 합니다. @
는 각각 분리해서 표현되고, *
는 공백을 포함한 하나의 텍스트로 표현됩니다. 템플릿에서 $@
로 표현된 경우 arguments 를 list 로 가져옵니다 ($0
는 안 가져옴)
참고로 for loop 에 arguments 를 집어넣고 싶을 땐 단순히 in
을 생략하면 됩니다.
case 문
처음 본 case 문이라서 따로 적습니다. 아래와 같은 꼴로 사용합니다. if-fi
를 처음 봤을땐 눈치챘는데 이번엔 눈치 못챘네요…
case "$var" in
"$regex1" ) command1 ;;
"$regex2" ) command2 ;;
:
:
esac
마치며
개인적으로 python script 로 만드는 쪽이 훨씬 생산적이지 않나 생각이 들긴 하지만, 호환성 이슈 하나 때문이라도 shell script 를 읽고 쓸 줄 알아야 한다… 라는 생각이 들었습니다.