
1편에서 변수, 조건문, 반복문, 함수를 다뤘습니다. 2편에서는 스크립트를 실제로 쓸 만하게 만드는 것들을 정리합니다. 에러 처리, 입력 받기, 문자열 처리, 그리고 반복적으로 등장하는 실용 패턴들입니다.
에러 처리
종료 코드
모든 명령어는 실행 후 종료 코드를 남깁니다. 0은 성공, 그 외는 실패입니다. $?로 직전 명령어의 종료 코드를 확인합니다.
ls /nonexistent
echo $? # 0이 아닌 값 (보통 2)
ls /tmp
echo $? # 0
에러 시 메시지 출력하고 종료
command || { echo "command 실패"; exit 1; }
# 함수로 만들어두면 편함
die() {
echo "Error: $1" >&2 # 표준 에러로 출력
exit "${2:-1}"
}
[ -f "$config" ] || die "설정 파일이 없습니다: $config"
>&2는 표준 에러(stderr)로 출력하는 것입니다. 에러 메시지는 stdout이 아닌 stderr에 쓰는 게 맞고, 파이프로 필터링할 때도 분리됩니다.
trap — 스크립트 종료 시 정리
스크립트가 끝나거나 중단될 때 실행할 코드를 등록합니다.
tmp_file=$(mktemp)
cleanup() {
rm -f "$tmp_file"
echo "정리 완료"
}
trap cleanup EXIT # 정상 종료 시
trap cleanup INT TERM # Ctrl+C, kill 시
trap 'cleanup; exit 1' ERR # 에러 발생 시
임시 파일을 만들고 나서 스크립트가 어떤 이유로 종료되든 삭제되도록 보장할 때 씁니다.
set 옵션 복습
set -e # 명령어 실패 시 즉시 종료
set -u # 미정의 변수 사용 시 에러
set -o pipefail # 파이프 중간 실패도 에러로 처리
set -x # 실행되는 명령어 출력 (디버깅)
set +x # 디버그 모드 끄기
set -x는 스크립트 디버깅할 때 유용합니다. 어느 줄에서 어떤 값으로 실행됐는지 보입니다.
입력 받기
스크립트 인수
#!/usr/bin/env bash
# 인수 검증
if [ "$#" -lt 2 ]; then
echo "사용법: $0 <이름> <나이>"
exit 1
fi
name="$1"
age="$2"
echo "$name ($age)"
사용자 입력 받기
read -p "이름을 입력하세요: " name
echo "안녕하세요, $name"
read -sp "비밀번호: " password # -s : 입력 숨기기
echo
read -t 10 -p "10초 안에 입력: " response # -t : 타임아웃(초)
옵션 파싱 — getopts
-f, -v 같은 플래그 형태의 인수를 처리할 때 씁니다.
verbose=false
output_file=""
while getopts "vo:h" opt; do
case "$opt" in
v) verbose=true ;;
o) output_file="$OPTARG" ;;
h)
echo "사용법: $0 [-v] [-o 출력파일]"
exit 0
;;
?)
echo "알 수 없는 옵션"
exit 1
;;
esac
done
shift $((OPTIND - 1)) # 파싱된 옵션 이후 나머지 인수로 이동
getopts "vo:h"에서 : 뒤에 오는 문자(o:)는 해당 옵션이 값을 받는다는 의미입니다.
문자열 처리
bash에서 ${변수} 안에 연산자를 넣어서 문자열을 조작할 수 있습니다.
길이
str="Hello, World"
echo "${#str}" # 12
부분 문자열
str="Hello, World"
echo "${str:7}" # World (7번째부터 끝까지)
echo "${str:7:5}" # World (7번째부터 5글자)
echo "${str: -5}" # World (뒤에서 5글자)
치환
str="Hello, World"
echo "${str/World/Bash}" # Hello, Bash (첫 번째만)
echo "${str//l/L}" # HeLLo, WorLd (전체)
제거 — 접두사/접미사
file="backup_2024-01-15.tar.gz"
echo "${file#backup_}" # 2024-01-15.tar.gz (짧은 접두사 제거)
echo "${file##*.}" # gz (가장 긴 접두사 제거, 확장자만)
echo "${file%.tar.gz}" # backup_2024-01-15 (접미사 제거)
echo "${file%%.*}" # backup_2024-01-15 (가장 긴 접미사 제거)
#은 앞에서, %는 뒤에서 제거합니다. 두 개 쓰면 최대한 많이 제거합니다.
대소문자 변환
str="Hello World"
echo "${str,,}" # hello world (전체 소문자)
echo "${str^^}" # HELLO WORLD (전체 대문자)
echo "${str,}" # hello World (첫 글자만 소문자)
echo "${str^}" # Hello World (첫 글자만 대문자)
파일 처리
파일 읽기
# 한 줄씩 처리
while IFS= read -r line; do
echo "줄: $line"
done < input.txt
# 빈 줄, 주석 제외
while IFS= read -r line; do
[[ -z "$line" || "$line" == \#* ]] && continue
echo "$line"
done < config.txt
파일 쓰기
echo "내용" > file.txt # 덮어쓰기
echo "추가 내용" >> file.txt # 이어쓰기
# 여러 줄 쓰기 (heredoc)
cat > file.txt << 'EOF'
첫 번째 줄
두 번째 줄
EOF
임시 파일
tmp=$(mktemp) # 임시 파일 생성
tmp_dir=$(mktemp -d) # 임시 디렉토리 생성
trap 'rm -rf "$tmp" "$tmp_dir"' EXIT
mktemp는 유일한 이름의 임시 파일을 만들어줍니다. /tmp/tmp.XXXXXXXX 형태입니다.
자주 쓰는 실용 패턴
스크립트 위치 기준 경로
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
CONFIG_FILE="$SCRIPT_DIR/config.yaml"
$0는 스크립트를 어디서 호출했냐에 따라 달라질 수 있습니다. BASH_SOURCE[0]을 쓰면 스크립트 자신의 위치를 정확히 가리킵니다.
로그 함수
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*"
}
log_info() { echo "[INFO] $(date '+%H:%M:%S') $*"; }
log_warn() { echo "[WARN] $(date '+%H:%M:%S') $*" >&2; }
log_error() { echo "[ERROR] $(date '+%H:%M:%S') $*" >&2; }
log_info "시작합니다"
log_error "연결 실패"
명령어 존재 여부 확인
command -v docker &>/dev/null || { echo "docker가 설치되지 않았습니다"; exit 1; }
# 함수로
require_command() {
command -v "$1" &>/dev/null || die "'$1' 명령어가 필요합니다"
}
require_command docker
require_command git
require_command jq
재시도 로직
retry() {
local max_attempts=3
local delay=5
local attempt=1
while [ $attempt -le $max_attempts ]; do
"$@" && return 0
echo "실패 ($attempt/$max_attempts), ${delay}초 후 재시도..."
sleep "$delay"
((attempt++))
done
echo "최대 재시도 횟수 초과"
return 1
}
retry curl -f https://example.com/api
진행 상황 표시
spinner() {
local pid=$1
local delay=0.1
local frames=('⠋' '⠙' '⠹' '⠸' '⠼' '⠴' '⠦' '⠧' '⠇' '⠏')
local i=0
while kill -0 "$pid" 2>/dev/null; do
printf "\r${frames[$((i % ${#frames[@]}))]} 처리 중..."
sleep "$delay"
((i++))
done
printf "\r✓ 완료 \n"
}
long_running_command &
spinner $!
색상 출력
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
echo -e "${GREEN}성공${NC}"
echo -e "${RED}에러${NC}"
echo -e "${YELLOW}경고${NC}"
확인 프롬프트
confirm() {
read -rp "${1:-계속하시겠습니까?} [y/N] " response
case "$response" in
[yY][eE][sS]|[yY]) return 0 ;;
*) return 1 ;;
esac
}
confirm "데이터베이스를 초기화하시겠습니까?" || exit 0
echo "초기화 진행..."
실전 스크립트 예시
위에서 다룬 것들을 조합한 간단한 배포 스크립트입니다.
#!/usr/bin/env bash
set -euo pipefail
# ── 설정 ────────────────────────────────────────
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
APP_NAME="myapp"
DEPLOY_DIR="/var/www/${APP_NAME}"
BACKUP_DIR="/var/backup/${APP_NAME}"
# ── 함수 ────────────────────────────────────────
log() { echo "[$(date '+%H:%M:%S')] $*"; }
die() { echo "Error: $1" >&2; exit "${2:-1}"; }
confirm() {
read -rp "$1 [y/N] " r
[[ "$r" =~ ^[yY] ]]
}
require_command() {
command -v "$1" &>/dev/null || die "'$1'이 필요합니다"
}
# ── 사전 확인 ───────────────────────────────────
require_command git
require_command rsync
[ -d "$DEPLOY_DIR" ] || die "배포 경로가 없습니다: $DEPLOY_DIR"
# ── 백업 ────────────────────────────────────────
log "현재 버전 백업 중..."
timestamp=$(date +%Y%m%d_%H%M%S)
mkdir -p "$BACKUP_DIR"
rsync -a "$DEPLOY_DIR/" "$BACKUP_DIR/${timestamp}/"
# ── 배포 ────────────────────────────────────────
confirm "배포를 진행하시겠습니까?" || { log "취소됨"; exit 0; }
log "배포 시작..."
git -C "$DEPLOY_DIR" pull origin main
log "완료: $(git -C "$DEPLOY_DIR" rev-parse --short HEAD)"
정리 표
| 분류 | 문법 | 용도 |
|---|---|---|
| 에러 처리 | cmd || die "메시지" |
실패 시 종료 |
| 에러 처리 | trap cleanup EXIT |
종료 시 정리 |
| 에러 처리 | set -x |
디버그 모드 |
| 입력 | read -p "프롬프트" var |
사용자 입력 |
| 입력 | getopts "vo:" |
옵션 파싱 |
| 문자열 | ${#str} |
길이 |
| 문자열 | ${str:offset:length} |
부분 문자열 |
| 문자열 | ${str/old/new} |
치환 |
| 문자열 | ${str#prefix} ${str%suffix} |
접두사/접미사 제거 |
| 문자열 | ${str,,} ${str^^} |
소문자 / 대문자 |
| 파일 | while IFS= read -r line |
줄 단위 읽기 |
| 파일 | mktemp |
임시 파일 생성 |
| 패턴 | BASH_SOURCE[0] |
스크립트 위치 |
| 패턴 | command -v cmd |
명령어 존재 확인 |
'블로그, 컴퓨터 > Cheatsheets' 카테고리의 다른 글
| tmux 정리 (2편) — 설정, 플러그인, 실용 커스터마이징 (0) | 2026.05.29 |
|---|---|
| tmux 정리 (1편) — 세션, 윈도우, 패널 (0) | 2026.05.29 |
| Shell Script 정리 (1편) — 변수, 조건문, 반복문, 함수 (0) | 2026.05.28 |
| Docker 명령어 정리 (2편) — Dockerfile과 Docker Compose (0) | 2026.05.27 |
| Docker 명령어 정리 (1편) — 개념, 이미지, 컨테이너 (0) | 2026.05.27 |