동작하는 코드
예제 1: 첫 번째 스크립트
cat > greet.sh << 'EOF'
#!/bin/bash
echo "안녕하세요!"
echo "오늘 날짜: $(date +%Y-%m-%d)"
echo "현재 사용자: $USER"
EOF
실행 권한 부여 후 실행:
chmod +x greet.sh
./greet.sh
출력:
안녕하세요!
오늘 날짜: 2026-03-06
현재 사용자: dale
$(명령어) 형식은 명령어 치환입니다. date +%Y-%m-%d의 출력이 그 자리에 들어갑니다.
예제 2: 인자 받기
cat > say_hello.sh << 'EOF'
#!/bin/bash
echo "안녕하세요, $1님!"
echo "전달받은 인자 수: $#"
EOF
chmod +x say_hello.sh
./say_hello.sh 홍길동
출력:
안녕하세요, 홍길동님!
전달받은 인자 수: 1
특수 변수:
$0— 스크립트 이름 자체$1,$2... — 인자$#— 인자 개수$@— 모든 인자 (배열처럼)$?— 마지막 명령어 종료 코드 (0=성공, 0 외=실패)
예제 3: 조건문으로 인자 검증
cat > backup.sh << 'EOF'
#!/bin/bash
set -e # 오류 발생 시 즉시 종료
set -u # 미정의 변수 사용 시 오류
# 인자 확인
if [ "$#" -eq 0 ]; then
echo "사용법: $0 <디렉토리>"
exit 1
fi
SOURCE="$1"
BACKUP="${SOURCE}-backup-$(date +%Y%m%d)"
if [ ! -d "$SOURCE" ]; then
echo "오류: '$SOURCE' 디렉토리가 없습니다."
exit 1
fi
cp -r "$SOURCE" "$BACKUP"
echo "백업 완료: $BACKUP"
EOF
chmod +x backup.sh
./backup.sh Documents
출력:
백업 완료: Documents-backup-20260306
직접 수정하기
조건문과 반복문
조건문 구조:
if [ 조건 ]; then
# 참일 때
elif [ 다른조건 ]; then
# 두 번째 조건
else
# 모두 거짓일 때
fi
자주 쓰는 조건:
| 조건 | 의미 |
| -------------- | -------------------- |
| -f 파일 | 파일이 존재함 |
| -d 디렉토리 | 디렉토리가 존재함 |
| -z "$변수" | 변수가 비어있음 |
| -n "$변수" | 변수가 비어있지 않음 |
| "$a" = "$b" | 문자열이 같음 |
| "$a" != "$b" | 문자열이 다름 |
| $a -eq $b | 숫자가 같음 |
| $a -gt $b | a가 b보다 큼 |
| $a -lt $b | a가 b보다 작음 |
| ! 조건 | 조건 부정 |
반복문:
# 목록 반복
for item in 사과 바나나 포도; do
echo "과일: $item"
done
# 파일 목록 반복
for file in *.txt; do
echo "처리 중: $file"
done
# 숫자 범위 (seq 사용)
for i in $(seq 1 5); do
echo "번호: $i"
done
# while 반복
count=0
while [ $count -lt 3 ]; do
echo "count: $count"
count=$((count + 1))
done
$@ 로 모든 인자 처리
cat > process_files.sh << 'EOF'
#!/bin/bash
if [ "$#" -eq 0 ]; then
echo "처리할 파일을 지정하세요."
exit 1
fi
echo "총 $# 개의 파일을 처리합니다."
for file in "$@"; do
if [ -f "$file" ]; then
echo "처리: $file ($(wc -l < "$file") 줄)"
else
echo "건너뜀: $file (파일 없음)"
fi
done
EOF
chmod +x process_files.sh
./process_files.sh file1.txt file2.txt file3.txt
"$@"는 모든 인자를 각각 별도의 인자로 전달합니다. 파일명에 공백이 있어도 안전합니다.
함수 정의와 사용
cat > utils.sh << 'EOF'
#!/bin/bash
# 함수 정의
log() {
echo "[$(date +%H:%M:%S)] $1"
}
# 종료 상태 확인 함수
check_success() {
if [ "$?" -eq 0 ]; then
log "✓ 성공: $1"
else
log "✗ 실패: $1"
exit 1
fi
}
# 사용
log "스크립트 시작"
mkdir -p /tmp/test-dir
check_success "디렉토리 생성"
cp /tmp/test-dir /tmp/test-copy 2>/dev/null
check_success "디렉토리 복사"
log "완료"
EOF
함수에서 인자도 $1, $2... 로 접근합니다. 단, 함수 내부의 $1은 함수에 전달된 인자이고 스크립트 인자가 아닙니다.
로컬 변수
calculate() {
local num1="$1" # local: 함수 안에서만 유효
local num2="$2"
local result=$((num1 + num2))
echo $result
}
result=$(calculate 10 20)
echo "10 + 20 = $result"
local을 쓰지 않으면 함수 내 변수가 전역 변수가 되어 의도치 않은 부작용이 생깁니다.
"왜?" — 스크립트의 핵심 구조
shebang (#!/bin/bash)
스크립트 첫 줄. 어떤 인터프리터로 실행할지 지정합니다.
#!/bin/bash # bash로 실행
#!/bin/sh # sh로 실행 (더 호환성 높음)
#!/usr/bin/env bash # PATH에서 bash 찾기 (이식성 높음)
set 옵션으로 안전한 스크립트
#!/bin/bash
set -e # 명령어 실패 시 즉시 종료
set -u # 미정의 변수 사용 시 오류
set -o pipefail # 파이프 중 실패 시 전체 실패
# 또는 한 줄로
set -euo pipefail
이 세 줄을 스크립트 시작에 항상 추가하면 예상치 못한 오류를 조기에 발견할 수 있습니다.
종료 코드(Exit Code)
# 성공
exit 0
# 실패
exit 1
# 직전 명령어 종료 코드 확인
ls /tmp
echo $? # 0 (성공)
ls /없는경로
echo $? # 1 (실패)
종료 코드 0은 성공, 1 이상은 실패입니다. CI/CD 시스템은 이 값으로 성공/실패를 판단합니다.
흔한 실수
실수 1: 실행 권한 없이 실행
./script.sh
# zsh: permission denied: ./script.sh
# 해결
chmod +x script.sh
./script.sh
실수 2: 변수에 공백이 있을 때 따옴표 빠뜨리기
# 위험: 파일명에 공백이 있으면 두 인자로 분리됨
FILE="my file.txt"
rm $FILE # rm "my" "file.txt" 로 해석됨!
# 안전: 항상 쌍따옴표로 감싸기
rm "$FILE"
실수 3: set -e 없이 에러 무시
#!/bin/bash
# set -e 없으면 실패해도 계속 실행됨
cp /없는파일 /목적지 # 실패
rm /목적지 # 어차피 없지만 실행됨
# set -e 있으면 첫 번째 실패에서 즉시 종료
#!/bin/bash
set -e
cp /없는파일 /목적지 # 실패 → 스크립트 종료
rm /목적지 # 실행 안 됨
실수 4: 조건문에 공백 빠뜨리기
# 잘못됨 (공백 없음)
if [$# -eq 0]; then
# 올바름 ([ 뒤, ] 앞에 공백)
if [ $# -eq 0 ]; then
심화 학습
trap으로 에러/종료 시 정리 작업
#!/bin/bash
set -e
# 임시 파일 경로
TMPFILE=$(mktemp)
# 스크립트 종료 시(정상/에러/Ctrl+C 모두) 임시 파일 삭제
trap 'rm -f "$TMPFILE"' EXIT
# 에러 시 메시지 출력
trap 'echo "오류 발생 (줄 $LINENO)"' ERR
# 작업 수행
echo "작업 시작" > "$TMPFILE"
cat "$TMPFILE"
echo "완료"
trap은 특정 시그널이나 이벤트에 반응하는 정리 코드를 등록합니다.
스크립트 디버깅
# 실행되는 명령어를 모두 출력 (디버그 모드)
bash -x script.sh
# 스크립트 내부에 set -x 추가
set -x
# ... 디버그할 부분 ...
set +x
# 문법만 체크 (실행 안 함)
bash -n script.sh
예상치 못한 동작이 생기면 set -x를 추가해 어떤 명령어가 어떻게 실행되는지 추적하세요.
배열 사용하기
# 배열 정의
fruits=("사과" "바나나" "포도")
# 전체 출력
echo "${fruits[@]}"
# 인덱스로 접근 (0부터 시작)
echo "${fruits[0]}" # 사과
echo "${fruits[1]}" # 바나나
# 배열 길이
echo "${#fruits[@]}" # 3
# 배열 반복
for fruit in "${fruits[@]}"; do
echo "과일: $fruit"
done
- 인자를 하나 받아 "안녕하세요, [인자]님!"을 출력하는
hello.sh를 작성하세요. 인자가 없으면 사용법을 출력하고 종료하세요. - 1부터 10까지 숫자를 출력하되 3의 배수는 "Fizz"로 바꾸는 스크립트를 작성하세요.
set -euo pipefail을 포함한 스크립트에서 존재하지 않는 파일을 복사하려 하면 어떻게 되는지 확인해보세요.$@를 사용해 여러 파일명을 인자로 받아 각 파일의 줄 수를 출력하는 스크립트를 작성하세요.
:::quiz{answer="C" explanation=""$@"는 모든 인자를 각각 별도의 인자로 전달하여 공백이 포함된 파일명도 안전하게 처리합니다."} Q1. 쉘 스크립트에서 모든 인자를 각각 올바르게 처리하는 변수는? (파일명에 공백이 있어도 안전)
- A)
$* - B)
$1 - C)
"$@" - D)
$#:::