쉘 스크립팅

쉘 스크립팅

1. 쉘 스크립트 기초

쉘 스크립트는 명령어들을 파일에 모아 자동화하는 프로그램입니다.

첫 번째 스크립트

#!/bin/bash
# 이것은 주석입니다
echo "Hello, World!"

shebang (#!)

스크립트의 첫 줄은 인터프리터를 지정합니다.

#!/bin/bash        # bash 쉘
#!/bin/sh          # 표준 쉘
#!/usr/bin/env bash  # 환경에서 bash 찾기 (이식성 좋음)

스크립트 실행

# 1. 실행 권한 부여 후 실행
chmod +x script.sh
./script.sh

# 2. bash로 직접 실행
bash script.sh

# 3. source로 실행 (현재 쉘에서)
source script.sh
. script.sh

2. 변수

변수 선언과 사용

#!/bin/bash

# 변수 선언 (= 양쪽에 공백 없음!)
name="John"
age=25
readonly PI=3.14159    # 상수

# 변수 사용
echo $name
echo ${name}           # 권장 (명확함)
echo "Hello, ${name}!"
echo "Age: $age"

# 변수 삭제
unset name

특수 변수

변수 설명
$0 스크립트 이름
$1` ~ `$9 위치 매개변수
$# 매개변수 개수
$@ 모든 매개변수 (개별)
$* 모든 매개변수 (문자열)
$? 직전 명령 종료 상태
$$ 현재 프로세스 PID
$! 마지막 백그라운드 PID
#!/bin/bash
echo "스크립트: $0"
echo "첫 번째 인자: $1"
echo "두 번째 인자: $2"
echo "인자 개수: $#"
echo "모든 인자: $@"
echo "PID: $$"

환경 변수

# 환경 변수 보기
env
printenv

# 주요 환경 변수
echo $HOME       # 홈 디렉토리
echo $USER       # 사용자명
echo $PATH       # 실행 경로
echo $PWD        # 현재 디렉토리
echo $SHELL      # 현재 쉘

# 환경 변수 설정
export MY_VAR="value"

# 스크립트 내에서 설정
#!/bin/bash
export PATH="$PATH:/opt/myapp/bin"

변수 기본값

# 변수가 없으면 기본값 사용
name=${name:-"default"}

# 변수가 없으면 기본값 할당
name=${name:="default"}

# 변수가 없으면 에러
name=${name:?"변수가 설정되지 않음"}

3. 입력 받기

read 명령어

#!/bin/bash

# 기본 입력
echo -n "이름을 입력하세요: "
read name
echo "안녕하세요, $name님!"

# 프롬프트와 함께
read -p "나이를 입력하세요: " age
echo "당신은 ${age}살입니다."

# 비밀번호 입력 (표시 안 함)
read -sp "비밀번호: " password
echo
echo "비밀번호가 설정되었습니다."

# 시간 제한
read -t 5 -p "5초 내 입력하세요: " input

# 여러 변수에 입력
read -p "이름과 나이: " name age
echo "$name, $age"

명령줄 인자

#!/bin/bash
# 사용: ./script.sh arg1 arg2

if [ $# -lt 2 ]; then
    echo "사용법: $0 <이름> <나이>"
    exit 1
fi

name=$1
age=$2
echo "$name님은 ${age}살입니다."

4. 조건문

if 문

#!/bin/bash

# 기본 형식
if [ 조건 ]; then
    명령어
fi

# if-else
if [ 조건 ]; then
    명령어1
else
    명령어2
fi

# if-elif-else
if [ 조건1 ]; then
    명령어1
elif [ 조건2 ]; then
    명령어2
else
    명령어3
fi

비교 연산자

숫자 비교

연산자 설명
-eq 같다 (equal)
-ne 다르다 (not equal)
-gt 크다 (greater than)
-ge 크거나 같다
-lt 작다 (less than)
-le 작거나 같다
#!/bin/bash
num=10

if [ $num -gt 5 ]; then
    echo "$num은 5보다 큽니다."
fi

if [ $num -eq 10 ]; then
    echo "$num은 10입니다."
fi

문자열 비교

연산자 설명
= 같다
!= 다르다
-z 빈 문자열
-n 비어있지 않음
#!/bin/bash
str="hello"

if [ "$str" = "hello" ]; then
    echo "문자열이 hello입니다."
fi

if [ -z "$empty" ]; then
    echo "변수가 비어있습니다."
fi

if [ -n "$str" ]; then
    echo "변수에 값이 있습니다."
fi

파일 테스트

연산자 설명
-e 파일 존재
-f 일반 파일
-d 디렉토리
-r 읽기 가능
-w 쓰기 가능
-x 실행 가능
-s 파일 크기 > 0
#!/bin/bash
file="/etc/passwd"

if [ -e "$file" ]; then
    echo "파일이 존재합니다."
fi

if [ -f "$file" ]; then
    echo "일반 파일입니다."
fi

if [ -d "/home" ]; then
    echo "디렉토리입니다."
fi

if [ -r "$file" ]; then
    echo "읽을 수 있습니다."
fi

논리 연산자

#!/bin/bash

# AND
if [ $a -gt 0 ] && [ $b -gt 0 ]; then
    echo "둘 다 양수"
fi

# OR
if [ $a -gt 0 ] || [ $b -gt 0 ]; then
    echo "하나 이상 양수"
fi

# NOT
if [ ! -f "file.txt" ]; then
    echo "파일이 없습니다."
fi

# [[ ]]에서 사용 (권장)
if [[ $a -gt 0 && $b -gt 0 ]]; then
    echo "둘 다 양수"
fi

case 문

#!/bin/bash
read -p "과일을 선택하세요 (apple/banana/orange): " fruit

case $fruit in
    apple)
        echo "사과를 선택했습니다."
        ;;
    banana)
        echo "바나나를 선택했습니다."
        ;;
    orange)
        echo "오렌지를 선택했습니다."
        ;;
    *)
        echo "알 수 없는 과일입니다."
        ;;
esac

5. 반복문

for 문

#!/bin/bash

# 리스트 반복
for name in Alice Bob Charlie; do
    echo "Hello, $name!"
done

# 범위 반복
for i in {1..5}; do
    echo "Number: $i"
done

# 증가값
for i in {0..10..2}; do
    echo "Even: $i"
done

# C 스타일
for ((i=0; i<5; i++)); do
    echo "Index: $i"
done

# 파일 목록
for file in *.txt; do
    echo "Processing: $file"
done

# 명령어 출력
for user in $(cat /etc/passwd | cut -d: -f1); do
    echo "User: $user"
done

while 문

#!/bin/bash

# 기본 while
count=1
while [ $count -le 5 ]; do
    echo "Count: $count"
    ((count++))
done

# 파일 읽기
while read line; do
    echo "Line: $line"
done < file.txt

# 무한 루프
while true; do
    echo "실행 중... (Ctrl+C로 종료)"
    sleep 1
done

until 문

#!/bin/bash

# 조건이 참이 될 때까지 반복
count=1
until [ $count -gt 5 ]; do
    echo "Count: $count"
    ((count++))
done

break와 continue

#!/bin/bash

# break - 루프 종료
for i in {1..10}; do
    if [ $i -eq 5 ]; then
        break
    fi
    echo $i
done

# continue - 다음 반복으로
for i in {1..5}; do
    if [ $i -eq 3 ]; then
        continue
    fi
    echo $i
done

6. 함수

함수 정의

#!/bin/bash

# 방법 1
function greet() {
    echo "Hello, $1!"
}

# 방법 2
say_bye() {
    echo "Goodbye, $1!"
}

# 호출
greet "World"
say_bye "World"

함수 매개변수

#!/bin/bash

print_info() {
    echo "이름: $1"
    echo "나이: $2"
    echo "인자 수: $#"
}

print_info "John" 25

반환값

#!/bin/bash

# return으로 종료 상태 반환 (0-255)
is_even() {
    if [ $(($1 % 2)) -eq 0 ]; then
        return 0    # true
    else
        return 1    # false
    fi
}

if is_even 4; then
    echo "짝수입니다."
fi

# echo로 값 반환
add() {
    echo $(($1 + $2))
}

result=$(add 5 3)
echo "5 + 3 = $result"

지역 변수

#!/bin/bash

my_func() {
    local local_var="나는 지역 변수"
    global_var="나는 전역 변수"
    echo "$local_var"
}

my_func
echo "$global_var"     # 출력됨
echo "$local_var"      # 출력 안 됨

7. 배열

#!/bin/bash

# 배열 선언
fruits=("apple" "banana" "orange")

# 인덱스로 접근
echo ${fruits[0]}     # apple
echo ${fruits[1]}     # banana

# 모든 요소
echo ${fruits[@]}

# 요소 개수
echo ${#fruits[@]}

# 배열 추가
fruits+=("grape")

# 배열 반복
for fruit in "${fruits[@]}"; do
    echo $fruit
done

# 인덱스와 함께
for i in "${!fruits[@]}"; do
    echo "$i: ${fruits[$i]}"
done

8. 실무 스크립트 예제

백업 스크립트

#!/bin/bash
# backup.sh - 디렉토리 백업 스크립트

SOURCE_DIR="${1:-/var/www}"
BACKUP_DIR="${2:-/backup}"
DATE=$(date +%Y%m%d_%H%M%S)
BACKUP_FILE="backup_${DATE}.tar.gz"

# 백업 디렉토리 확인
if [ ! -d "$BACKUP_DIR" ]; then
    mkdir -p "$BACKUP_DIR"
fi

# 소스 디렉토리 확인
if [ ! -d "$SOURCE_DIR" ]; then
    echo "오류: $SOURCE_DIR 디렉토리가 없습니다."
    exit 1
fi

# 백업 실행
echo "백업 시작: $SOURCE_DIR -> $BACKUP_DIR/$BACKUP_FILE"
tar -czvf "$BACKUP_DIR/$BACKUP_FILE" -C "$(dirname $SOURCE_DIR)" "$(basename $SOURCE_DIR)"

if [ $? -eq 0 ]; then
    echo "백업 완료: $BACKUP_DIR/$BACKUP_FILE"

    # 30일 이상 된 백업 삭제
    find "$BACKUP_DIR" -name "backup_*.tar.gz" -mtime +30 -delete
    echo "오래된 백업 정리 완료"
else
    echo "백업 실패!"
    exit 1
fi

서버 상태 체크 스크립트

#!/bin/bash
# health_check.sh - 서버 상태 체크

LOG_FILE="/var/log/health_check.log"

log_message() {
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
}

check_disk() {
    local usage=$(df -h / | awk 'NR==2 {print $5}' | tr -d '%')
    if [ "$usage" -gt 80 ]; then
        log_message "경고: 디스크 사용량 ${usage}%"
        return 1
    fi
    log_message "디스크: ${usage}% 사용 중"
    return 0
}

check_memory() {
    local usage=$(free | awk '/Mem:/ {printf("%.0f", $3/$2 * 100)}')
    if [ "$usage" -gt 80 ]; then
        log_message "경고: 메모리 사용량 ${usage}%"
        return 1
    fi
    log_message "메모리: ${usage}% 사용 중"
    return 0
}

check_service() {
    local service=$1
    if systemctl is-active --quiet "$service"; then
        log_message "서비스 $service: 실행 중"
        return 0
    else
        log_message "경고: 서비스 $service 중지됨"
        return 1
    fi
}

# 메인
log_message "===== 상태 체크 시작 ====="
check_disk
check_memory
check_service "sshd"
check_service "nginx" 2>/dev/null || true
log_message "===== 상태 체크 완료 ====="

로그 분석 스크립트

#!/bin/bash
# log_analyzer.sh - 로그 분석

LOG_FILE="${1:-/var/log/syslog}"

if [ ! -f "$LOG_FILE" ]; then
    echo "파일을 찾을 수 없습니다: $LOG_FILE"
    exit 1
fi

echo "=== 로그 분석: $LOG_FILE ==="
echo

echo "=== 에러 수 ==="
grep -ci "error" "$LOG_FILE"

echo
echo "=== 최근 에러 10개 ==="
grep -i "error" "$LOG_FILE" | tail -10

echo
echo "=== 시간대별 로그 수 ==="
awk '{print $3}' "$LOG_FILE" | cut -d: -f1 | sort | uniq -c | sort -k2n

사용자 생성 스크립트

#!/bin/bash
# create_users.sh - 파일에서 사용자 일괄 생성
# 사용: sudo ./create_users.sh users.txt
# users.txt 형식: username:password:groupname

USER_FILE="$1"

if [ -z "$USER_FILE" ] || [ ! -f "$USER_FILE" ]; then
    echo "사용법: $0 <사용자파일>"
    exit 1
fi

while IFS=: read -r username password groupname; do
    # 빈 줄 또는 주석 건너뛰기
    [[ -z "$username" || "$username" =~ ^# ]] && continue

    # 사용자 존재 확인
    if id "$username" &>/dev/null; then
        echo "사용자 존재: $username (건너뜀)"
        continue
    fi

    # 그룹 생성 (없으면)
    if ! getent group "$groupname" &>/dev/null; then
        groupadd "$groupname"
        echo "그룹 생성: $groupname"
    fi

    # 사용자 생성
    useradd -m -s /bin/bash -g "$groupname" "$username"
    echo "$username:$password" | chpasswd
    echo "사용자 생성: $username (그룹: $groupname)"

done < "$USER_FILE"

echo "완료!"

9. 디버깅

디버그 모드

# 스크립트 실행 추적
bash -x script.sh

# 스크립트 내에서 활성화
#!/bin/bash
set -x    # 디버그 시작
# 명령어들...
set +x    # 디버그 종료

# 에러 발생 시 종료
set -e

# 미정의 변수 사용 시 에러
set -u

# 파이프 에러 감지
set -o pipefail

# 모범 사례 (스크립트 시작)
#!/bin/bash
set -euo pipefail

10. 실습 예제

실습 1: 간단한 계산기

#!/bin/bash
# calculator.sh

read -p "첫 번째 숫자: " num1
read -p "연산자 (+, -, *, /): " op
read -p "두 번째 숫자: " num2

case $op in
    +) result=$((num1 + num2)) ;;
    -) result=$((num1 - num2)) ;;
    \*) result=$((num1 * num2)) ;;
    /) result=$((num1 / num2)) ;;
    *) echo "잘못된 연산자"; exit 1 ;;
esac

echo "결과: $num1 $op $num2 = $result"

실습 2: 파일 정리 스크립트

#!/bin/bash
# organize.sh - 파일 확장자별 정리

DIR="${1:-.}"

for file in "$DIR"/*; do
    [ -f "$file" ] || continue

    ext="${file##*.}"
    mkdir -p "$DIR/$ext"
    mv "$file" "$DIR/$ext/"
    echo "이동: $file -> $DIR/$ext/"
done

다음 단계

10_Network_Basics.md에서 네트워크 관리를 배워봅시다!

to navigate between lessons