
터미널 명령어를 익히고 나면 자연스럽게 "이 명령어들을 묶어서 자동으로 실행할 수 없을까"라는 생각이 생깁니다. 그게 Shell Script입니다.
배포 자동화, 반복 작업, 서버 세팅 같은 걸 스크립트 하나로 처리할 수 있게 됩니다. 처음엔 문법이 좀 독특해서 어색한데, 패턴이 정해져 있어서 익숙해지면 읽는 것도 쓰는 것도 어렵지 않습니다.
1편에서는 스크립트 기본 구조, 변수, 조건문, 반복문, 함수를 다룹니다.
기본 구조
#!/bin/bash
echo "Hello, World!"
첫 줄의 #!/bin/bash는 shebang이라고 부릅니다. 이 스크립트를 어떤 인터프리터로 실행할지 지정합니다. bash를 쓸 땐 #!/bin/bash, 더 호환성 높게 쓰려면 #!/usr/bin/env bash를 씁니다.
파일을 만든 뒤 실행 권한을 주고 실행합니다.
chmod +x script.sh
./script.sh
또는 권한 없이 bash script.sh로 직접 실행할 수도 있습니다.
첫 줄에 꼭 넣는 것들
#!/usr/bin/env bash
set -euo pipefail
set -euo pipefail은 스크립트를 더 안전하게 만드는 옵션 조합입니다.
-e: 명령어가 실패하면(종료 코드가 0이 아니면) 즉시 스크립트 종료-u: 정의되지 않은 변수를 쓰면 에러-o pipefail: 파이프 중간 명령어가 실패해도 에러로 처리
이게 없으면 중간에 명령어가 실패해도 스크립트가 계속 실행됩니다. 배포 스크립트 같은 데서 이게 없으면 위험합니다.
변수
선언과 사용
name="Alice"
age=30
greeting="Hello, $name"
echo $name
echo "$name" # 따옴표 안에서도 변수 치환됨
echo "${name}!" # 변수 이름 경계를 명확히 할 때
= 양쪽에 공백이 없어야 합니다. name = "Alice"는 에러입니다.
변수를 쓸 때는 $name이나 ${name} 둘 다 되는데, ${name} 형태가 더 안전합니다. ${name}_backup처럼 변수 이름과 다른 문자가 붙을 때 $name_backup으로 쓰면 name_backup이라는 변수로 해석되기 때문입니다.
따옴표 차이
name="World"
echo "Hello $name" # Hello World (변수 치환 O)
echo 'Hello $name' # Hello $name (변수 치환 X)
큰따옴표는 변수를 치환하고, 작은따옴표는 모든 것을 문자 그대로 씁니다.
환경 변수와 export
export PATH="$PATH:/usr/local/bin" # 자식 프로세스에도 전달
export DB_HOST="localhost"
export를 붙이면 해당 스크립트에서 실행하는 하위 프로세스에도 변수가 전달됩니다.
특수 변수
$0 # 스크립트 이름
$1, $2 # 첫 번째, 두 번째 인수
$@ # 모든 인수 (각각 따로)
$* # 모든 인수 (하나의 문자열로)
$# # 인수 개수
$? # 직전 명령어 종료 코드 (0 = 성공)
$$ # 현재 스크립트의 PID
$! # 마지막 백그라운드 프로세스 PID
#!/usr/bin/env bash
echo "스크립트 이름: $0"
echo "첫 번째 인수: $1"
echo "전체 인수 수: $#"
변수 기본값
name="${1:-기본값}" # $1이 없거나 비어있으면 기본값 사용
name="${NAME:=default}" # 변수가 없으면 기본값으로 설정까지 함
echo "${name:-unknown}" # 출력할 때만 기본값 적용
명령어 결과를 변수에 저장
current_date=$(date +%Y-%m-%d)
files=$(ls *.txt)
line_count=$(wc -l < file.txt)
echo "오늘 날짜: $current_date"
조건문
기본 if
if [ 조건 ]; then
실행할 명령어
elif [ 다른조건 ]; then
실행할 명령어
else
실행할 명령어
fi
[ 와 ] 안쪽에 공백이 있어야 합니다. [$name]은 에러입니다.
문자열 비교
if [ "$name" = "Alice" ]; then # 같음
if [ "$name" != "Alice" ]; then # 다름
if [ -z "$name" ]; then # 비어있음
if [ -n "$name" ]; then # 비어있지 않음
문자열 비교할 때 변수를 반드시 따옴표로 감쌉니다. [ $name = "Alice" ]처럼 쓰면 name이 비어있을 때 [ = "Alice" ]가 되어 에러가 납니다.
숫자 비교
if [ "$count" -eq 0 ]; then # equal
if [ "$count" -ne 0 ]; then # not equal
if [ "$count" -gt 10 ]; then # greater than
if [ "$count" -lt 10 ]; then # less than
if [ "$count" -ge 10 ]; then # greater or equal
if [ "$count" -le 10 ]; then # less or equal
파일/디렉토리 확인
if [ -f "$file" ]; then # 파일이 존재하고 일반 파일
if [ -d "$dir" ]; then # 디렉토리가 존재
if [ -e "$path" ]; then # 존재 (파일, 디렉토리 모두)
if [ -r "$file" ]; then # 읽기 가능
if [ -w "$file" ]; then # 쓰기 가능
if [ -x "$file" ]; then # 실행 가능
if [ -s "$file" ]; then # 비어있지 않음 (크기 > 0)
AND / OR
if [ "$a" -gt 0 ] && [ "$b" -gt 0 ]; then # 둘 다 참
if [ "$a" -eq 0 ] || [ "$b" -eq 0 ]; then # 하나라도 참
[[ ]] (이중 대괄호)를 쓰면 &&, ||를 안에서 쓸 수 있고, 따옴표 없이도 더 안전하게 동작합니다. bash 전용이지만 더 편합니다.
if [[ "$name" == "Alice" && "$age" -gt 20 ]]; then
if [[ "$name" =~ ^A ]]; then # 정규식 매칭 (=~ 연산자)
반복문
for — 목록 순회
for item in apple banana cherry; do
echo "$item"
done
# 배열 순회
fruits=("apple" "banana" "cherry")
for fruit in "${fruits[@]}"; do
echo "$fruit"
done
# 파일 목록 순회
for file in *.txt; do
echo "처리 중: $file"
done
# C 스타일 for
for ((i=0; i<5; i++)); do
echo "$i"
done
while
count=0
while [ "$count" -lt 5 ]; do
echo "$count"
((count++))
done
# 파일 한 줄씩 읽기
while IFS= read -r line; do
echo "$line"
done < file.txt
파일을 한 줄씩 처리할 때 while read 패턴을 자주 씁니다. IFS=는 줄 앞뒤 공백을 보존하기 위해, -r은 백슬래시를 이스케이프로 처리하지 않기 위해 붙입니다.
until
until [ "$count" -ge 5 ]; do
echo "$count"
((count++))
done
while의 반대입니다. 조건이 참이 될 때까지 반복합니다.
break / continue
for i in 1 2 3 4 5; do
if [ "$i" -eq 3 ]; then
continue # 이번 회차 건너뜀
fi
if [ "$i" -eq 5 ]; then
break # 반복문 종료
fi
echo "$i"
done
함수
function greet() {
echo "Hello, $1!"
}
# 또는 function 키워드 없이
greet() {
echo "Hello, $1!"
}
greet "Alice" # 호출
함수 안의 $1, $2는 함수 인수입니다. 스크립트 전체의 $1과는 다릅니다.
반환값
bash 함수는 숫자(0~255)만 return으로 반환할 수 있습니다. 0은 성공, 그 외는 실패를 의미합니다. 문자열은 echo로 출력하고 명령어 치환으로 받습니다.
get_date() {
echo "$(date +%Y-%m-%d)"
}
today=$(get_date)
echo "오늘: $today"
지역 변수
함수 안에서 local을 붙이면 함수 밖에 영향을 주지 않습니다.
my_func() {
local temp="함수 안에서만"
echo "$temp"
}
my_func
echo "${temp:-비어있음}" # 비어있음
배열
arr=("apple" "banana" "cherry")
echo "${arr[0]}" # 첫 번째 요소
echo "${arr[@]}" # 전체 요소
echo "${#arr[@]}" # 배열 길이
arr+=("date") # 요소 추가
unset arr[1] # 특정 요소 삭제
# 인덱스 목록
echo "${!arr[@]}"
산술 연산
a=10
b=3
echo $((a + b)) # 13
echo $((a - b)) # 7
echo $((a * b)) # 30
echo $((a / b)) # 3 (정수 나눗셈)
echo $((a % b)) # 1 (나머지)
echo $((a ** b)) # 1000 (거듭제곱)
((a++)) # 증가
((a += 5)) # 더하기 대입
소수점 연산이 필요하면 bc를 씁니다.
echo "scale=2; 10 / 3" | bc # 3.33
정리 표
| 분류 | 문법 | 용도 |
|---|---|---|
| 변수 | name="value" |
변수 선언 |
| 변수 | "${name:-default}" |
기본값 지정 |
| 변수 | result=$(command) |
명령어 결과 저장 |
| 특수 변수 | $1 $@ $# $? |
인수 / 전체 / 개수 / 종료코드 |
| 조건 | [ -f ] [ -d ] [ -z ] |
파일 / 디렉토리 / 빈 문자열 |
| 조건 | -eq -ne -gt -lt |
숫자 비교 |
| 조건 | [[ =~ ]] |
정규식 매칭 |
| 반복 | for item in list |
목록 순회 |
| 반복 | while IFS= read -r line |
파일 한 줄씩 |
| 함수 | func() { ... } |
함수 선언 |
| 함수 | local var |
지역 변수 |
| 산술 | $((a + b)) |
정수 연산 |
2편에서는 입력 처리, 에러 핸들링, 문자열 처리, 그리고 자주 쓰는 실용 패턴들을 정리합니다.
'블로그, 컴퓨터 > Cheatsheets' 카테고리의 다른 글
| tmux 정리 (1편) — 세션, 윈도우, 패널 (0) | 2026.05.29 |
|---|---|
| Shell Script 정리 (2편) — 에러 처리, 문자열, 실용 패턴 (0) | 2026.05.28 |
| Docker 명령어 정리 (2편) — Dockerfile과 Docker Compose (0) | 2026.05.27 |
| Docker 명령어 정리 (1편) — 개념, 이미지, 컨테이너 (0) | 2026.05.27 |
| Git 명령어 정리 (2편) — 브랜치, 원격, 되돌리기, stash (0) | 2026.05.26 |