forked from mmistakes/minimal-mistakes
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
1 changed file
with
215 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,215 @@ | ||
--- | ||
layout: single | ||
title: "Shell Script 템플릿" | ||
date: 2024-01-29 19:30:00 +0900 | ||
toc: true | ||
toc_sticky: true | ||
tags: Shell-script | ||
categories: #dev, side project, code problem | ||
- dev | ||
header: | ||
teaser: "" | ||
--- | ||
쉘스크립트 템플릿 분석 | ||
|
||
|
||
--- | ||
|
||
# 동기 | ||
|
||
리눅스 쉘 스크립트를 공부하다가, 쉘 스크립트에 공통적으로 쓰이는 boiler-plate 같은 게 있을것 같았습니다. [이 곳](https://betterdev.blog/minimal-safe-bash-script-template/)에 원문이 있고, [이 분](https://velog.io/@roeniss/%EA%B0%84%EA%B2%B0%ED%95%98%EA%B3%A0-%EC%95%88%EC%A0%84%ED%95%9C-Bash-%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%ED%85%9C%ED%94%8C%EB%A6%BF)이 한국어로 번역을 해 두었으니 전체적인 설명을 보려면 링크타고 가면 됩니다. | ||
|
||
저는 쉘 스크립트 문법을 공부하려는 의도라서, 스크립트의 전체적인 구조나 의도는 생략하겠습니다. | ||
|
||
# Shell script template 원문 | ||
{% highlight bash linenos %} | ||
#!/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[*]-}" | ||
{% endhighlight%} | ||
|
||
테스트 | ||
|
||
{% highlight bash linenos %} | ||
$ ./test.sh -p param argument | ||
Read parameters: | ||
- flag: 0 | ||
- param: param | ||
- arguments: argument | ||
{% endhighlight%} | ||
|
||
# 사용된 문법들 | ||
|
||
## #! (SheBang, 사용할 쉘 선언) | ||
|
||
보통 인터넷 자료를 보면 `#!/bin/sh` 혹은 `#! /bin/sh` 같은 식인데, 템플릿에선 `#!/usr/bin/env bash` 를 사용하고 있습니다. 찾아보니 원래는 `#!/bin/bash` 처럼 bash 의 절대경로를 넣어줘야 하는데, `#!/usr/bin/env ` 는 뭘까요? | ||
|
||
--- | ||
|
||
사실 원래 엄밀하겐 사용할 쉘을 선언하는게 아니라, 가장 먼저 실행될 프로그램을 지정하는 거라고 해요. 찾아보면 이런 예제도 있습니다. | ||
|
||
```bash | ||
#!bin/rm | ||
|
||
: | ||
: | ||
``` | ||
|
||
아이러니하게도 이렇게 쓰여진 쉘 스크립트는 자기 자신을 지우는 스크립트가 됩니다. 어쨋든 `#!/usr/bin/env bash` 도 `env bash` 명령어를 먼저 실행시킨다는 의미가 되겠네요. 쉘에서 `env` 명령어는 단독으로 쓰면 환경변수를 출력하지만, argument 를 주면 내 환경에서 해당 프로그램을 찾아 실행시키는 명령어가 됩니다. 이런 기능 덕분에 `#!/usr/bin/env python` 처럼 사용되곤 합니다. | ||
|
||
## trap | ||
|
||
특정 시그널을 잡아채서, 지정한 명령어를 실행합니다. | ||
|
||
`trap [실행될 명령어] [잡아챌 시그널(들)]` | ||
|
||
이런 식으로 사용하고, 간단한 스크립트에선 one-liner 처럼 사용될 수 있습니다. | ||
|
||
```bash | ||
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 | ||
|
||
이전 [포스팅](/dev/Open_source_Sagan_shScript) 에도 비슷한 내용이 있었습니다. 거기선 이런 식으로 작성되어있었습니다. | ||
|
||
```bash | ||
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` 를 처음 봤을땐 눈치챘는데 이번엔 눈치 못챘네요... | ||
|
||
```bash | ||
case "$var" in | ||
"$regex1" ) command1 ;; | ||
"$regex2" ) command2 ;; | ||
: | ||
: | ||
esac | ||
``` | ||
# 마치며 | ||
개인적으로 python script 로 만드는 쪽이 훨씬 생산적이지 않나 생각이 들긴 하지만, 호환성 이슈 하나 때문이라도 shell script 를 읽고 쓸 줄 알아야 한다... 라는 생각이 들었습니다. | ||