Shell Script: bash 기본과 실전 패턴
Shell Pipeline, shell-pipeline, Shell Script, bash script, 쉘 스크립트, shell scripting, bash
정의
Shell Script 는 shell (bash, zsh, sh) 커맨드를 파일로 저장하여 자동화하는 스크립트. DevOps, CI/CD, sysadmin 필수.
안전한 시작
#!/usr/bin/env bash
set -euo pipefail
IFS=$'\n\t'
-e: 명령 실패 시 즉시 종료-u: 정의 안 된 변수 참조 시 오류-o pipefail: pipe 안의 실패도 캐치IFS: 안전한 word splitting (공백/탭/개행 문자만)
실행 흐름
flowchart TD
Start["스크립트 시작"] --> Init["shebang + set -euo pipefail"]
Init --> Parse["인자 파싱"]
Parse --> Main["main 로직 실행"]
Main --> Err{"오류?"}
Err -->|"예 (exit code != 0)"| Trap["trap cleanup 실행"]
Err -->|아니오| End["exit 0"]
Trap --> Clean["임시 파일 제거, 로그 기록"]
Clean --> End
변수
name="Alice"
echo "Hello, $name"
echo "Hello, ${name}"
# 기본값
echo "${port:-8080}" # $port 없으면 8080
# 없으면 대입
: "${port:=8080}"
# 필수값 강제
echo "${API_KEY:?API_KEY is required}"
# 배열
arr=(a b c)
echo "${arr[0]}"
echo "${arr[@]}"
echo "${#arr[@]}" # 길이
# 연관 배열 (bash 4+)
declare -A map
map["host"]="localhost"
map["port"]="5432"
echo "${map[host]}"
조건과 반복
if [[ -f "$file" ]]; then
echo "exists"
fi
if [[ "$env" == "prod" ]]; then
set_prod_vars
elif [[ "$env" == "dev" ]]; then
set_dev_vars
fi
for f in *.log; do
echo "$f"
done
while read -r line; do
echo "$line"
done < input.txt
# C-style for
for ((i=0; i<10; i++)); do
echo "$i"
done
파일 테스트 연산자
| 연산자 | 의미 |
|---|---|
-f $f | 파일 존재 (regular file) |
-d $d | 디렉토리 존재 |
-e $p | 경로 존재 (파일/디렉토리 모두) |
-r $f | 읽기 가능 |
-z $s | 빈 문자열 |
-n $s | 비어있지 않은 문자열 |
$a -lt $b | 정수 비교 (less than) |
함수
greet() {
local name="$1"
echo "Hello, $name"
}
greet "World"
# exit code 로 불리언 역할
is_healthy() {
curl -sf "http://localhost:8080/health" > /dev/null
}
if is_healthy; then
echo "서비스 정상"
fi
에러 처리
trap 'cleanup' EXIT ERR INT TERM
cleanup() {
local exit_code=$?
rm -f "$temp_file"
exit "$exit_code"
}
# 특정 명령 실패 허용
some_command || true
# 실패 시 대안 실행
some_command || fallback_command
# 에러 위치 기록 (디버그용)
set -E
trap 'echo "Error at line $LINENO in ${FUNCNAME[0]}"' ERR
파일과 경로
# 스크립트 자신의 디렉토리
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# 임시 파일 (자동 정리)
temp_file="$(mktemp)"
trap 'rm -f "$temp_file"' EXIT
# 경로 분리 (순수 bash, dirname/basename 없이)
path="/var/log/app/error.log"
filename="${path##*/}" # error.log
extension="${filename##*.}" # log
basename_no_ext="${filename%.*}" # error
dir="${path%/*}" # /var/log/app
텍스트 처리
# grep: 패턴 검색
grep -E "^ERROR" app.log
grep -c "pattern" file # 매칭 줄 수
grep -n "error" file # 줄 번호 포함
# awk: 필드 파싱
awk '{print $1}' file # 첫 번째 필드 (공백 구분)
awk -F: '{print $1}' /etc/passwd # 콜론 구분자
# awk 집계
awk '{sum += $1} END {print sum}' numbers.txt
awk 'NR==5' file # 5번째 줄
# sed: 치환
sed 's/foo/bar/g' file # 모든 foo -> bar
sed -i 's/foo/bar/g' file # in-place 수정
sed '/^#/d' file # # 로 시작하는 줄 삭제
sed -n '10,20p' file # 10-20번째 줄 출력
# 파이프 조합: 상위 10 에러 유형
grep "ERROR" app.log \
| awk '{print $NF}' \
| sort \
| uniq -c \
| sort -rn \
| head -10
stdin/stdout 처리
cat file.txt | grep pattern | wc -l
grep pattern < file.txt
grep pattern file.txt > out.txt 2>&1
# 표준 에러만 버림
command 2>/dev/null
# 표준 에러를 표준 출력으로
command 2>&1 | tee output.log
# heredoc (멀티라인 입력)
cat <<'EOF'
line 1
line 2
EOF
# process substitution (subshell 없이 while read)
while read -r line; do
echo "$line"
done < <(some_command)
병렬 실행
# & 로 백그라운드, wait 로 완료 대기
for host in host1 host2 host3; do
ssh "$host" "uptime" &
done
wait
# 개별 pid 추적 + 실패 감지
pids=()
for item in "${items[@]}"; do
process_item "$item" &
pids+=($!)
done
for pid in "${pids[@]}"; do
wait "$pid" || echo "pid $pid failed"
done
# xargs -P (병렬 프로세스 수 지정)
find . -name "*.csv" | xargs -P 4 -I{} process.sh {}
로깅 패턴
LOG_FILE="script-$(date +%Y%m%d-%H%M%S).log"
log_info() { echo "[$(date +%T)] INFO $*" | tee -a "$LOG_FILE"; }
log_warn() { echo "[$(date +%T)] WARN $*" | tee -a "$LOG_FILE" >&2; }
log_error() { echo "[$(date +%T)] ERROR $*" | tee -a "$LOG_FILE" >&2; }
# 컬러 (터미널)
RED='\033[0;31m'
GREEN='\033[0;32m'
NC='\033[0m'
echo -e "${GREEN}Success${NC}"
echo -e "${RED}Failed${NC}"
인자 파싱
while getopts "hi:o:v" opt; do
case $opt in
h) echo "usage: script.sh [-i input] [-o output] [-v]" ; exit 0 ;;
i) input="$OPTARG" ;;
o) output="$OPTARG" ;;
v) verbose=1 ;;
*) exit 1 ;;
esac
done
shift $((OPTIND - 1))
# 나머지 positional args: "$@"
실전 패턴: 배포 스크립트
#!/usr/bin/env bash
set -euo pipefail
readonly ENV="${1:?ENV required (prod|staging)}"
readonly DEPLOY_LOG="deploy-$(date +%Y%m%d-%H%M%S).log"
temp_dir=""
log() { echo "[$(date +%T)] $*" | tee -a "$DEPLOY_LOG"; }
cleanup() { [[ -n "$temp_dir" ]] && rm -rf "$temp_dir"; }
trap cleanup EXIT
deploy() {
temp_dir="$(mktemp -d)"
log "Pulling latest image..."
docker pull "myapp:latest"
log "Stopping old container..."
docker stop myapp 2>/dev/null || true
docker rm myapp 2>/dev/null || true
log "Starting container (ENV=$ENV)..."
docker run -d \
--name myapp \
--env-file "/config/$ENV.env" \
-p 8080:8080 \
"myapp:latest"
log "Health check..."
local max=30
for i in $(seq 1 "$max"); do
if curl -sf "http://localhost:8080/health" > /dev/null; then
log "Health OK after ${i}s"
return 0
fi
sleep 1
done
log "Health check FAILED after ${max}s"
return 1
}
deploy
함정
WARNING
- quoting:
$var는 항상"$var"로 감싸야 공백 처리 안전. 배열은"${arr[@]}"(원소별 개별 인자). - [ vs [[: bash 는
[[가 안전 (short-circuit, no word splitting).[는 POSIX sh 용. - backtick vs
$():$( )가 nesting 가능, 가독성 좋음. backtick 안에서 백슬래시 규칙이 달라 혼동 유발. - subshell:
( )안에서 변경한 변수는 밖에 반영 안 됨. 파이프도 같은 문제 (while read < <(...)로 해결). set -e와 if 조건: 0이 아닌 exit code 를 반환하는 함수가 if/while 조건이나||/&&뒤에 있으면-e가 반응 안 함. 명시적 처리 필요.
CAUTION
rm -rf "$dir/" 에서 $dir 이 빈 문자열이면 루트 삭제. 반드시 [ -z "$dir" ] 빈 문자열 검사 후 실행.
관련 위키
- github-actions - CI/CD 파이프라인 자동화
- argocd - GitOps 기반 자동 배포
- prometheus - 메트릭 수집 (exporter 스크립트 연동)
- zero-downtime-deployment - Blue-Green, Canary 배포 전략
이 글의 용어 (4개)
- [CI/CD] GitHub Actions: workflow, action, runnerdevops
- 정의 GitHub Actions = GitHub 내장 CI/CD. YAML workflow + marketplace action. 2018 출시 → Travis CI 대체. 구조…
- [DevOps] 무중단 배포: Blue-Green, Canary, Rolling, Expand-Contractdevops
- 정의 무중단 배포 (Zero-Downtime Deployment) 는 서비스 가동을 멈추지 않고 새 버전을 배포하는 일련의 전략. 핵심 도전 4가지: 1. 트래픽 전환: 어떻게 …
- [GitOps] ArgoCD: Kubernetes GitOpsdevops
- 정의 ArgoCD = Kubernetes 의 GitOps 컨트롤러. Git 리포지토리의 manifest 가 source of truth → cluster 가 자동 동기화. Git…
- [Observability] Prometheus: pull 기반 메트릭, PromQLdevops
- 정의 Prometheus = pull 기반 시계열 metric 시스템. PromQL 로 쿼리. CNCF graduated. 2026 클라우드 네이티브 메트릭 표준. 아키텍처 Pu…
💬 댓글