Skip to content

Commit

Permalink
new post
Browse files Browse the repository at this point in the history
  • Loading branch information
rahon6000 committed Jan 29, 2024
1 parent 70dc290 commit 855dfd5
Showing 1 changed file with 215 additions and 0 deletions.
215 changes: 215 additions & 0 deletions _posts/2024-01-29-Shell_Script_Template.md
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 를 읽고 쓸 줄 알아야 한다... 라는 생각이 들었습니다.

0 comments on commit 855dfd5

Please sign in to comment.