DaleSchool

쉘 스크립트 기초

입문30분

학습 목표

  • 기본 쉘 스크립트 구조를 이해하고 실행할 수 있다
  • 변수, 조건문, 반복문을 스크립트에서 사용할 수 있다
  • $@, $#, $?로 인자와 상태를 처리할 수 있다
  • set -e, set -u로 안전한 스크립트를 작성할 수 있다

동작하는 코드

예제 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
  1. 인자를 하나 받아 "안녕하세요, [인자]님!"을 출력하는 hello.sh를 작성하세요. 인자가 없으면 사용법을 출력하고 종료하세요.
  2. 1부터 10까지 숫자를 출력하되 3의 배수는 "Fizz"로 바꾸는 스크립트를 작성하세요.
  3. set -euo pipefail을 포함한 스크립트에서 존재하지 않는 파일을 복사하려 하면 어떻게 되는지 확인해보세요.
  4. $@를 사용해 여러 파일명을 인자로 받아 각 파일의 줄 수를 출력하는 스크립트를 작성하세요.

:::quiz{answer="C" explanation=""$@"는 모든 인자를 각각 별도의 인자로 전달하여 공백이 포함된 파일명도 안전하게 처리합니다."} Q1. 쉘 스크립트에서 모든 인자를 각각 올바르게 처리하는 변수는? (파일명에 공백이 있어도 안전)

  • A) $*
  • B) $1
  • C) "$@"
  • D) $# :::