cv.cm

코딩 · 프로그래밍 언어

입문자를 위한 Bash 스크립트 기초 강좌

10분 분량

핵심 답변

Bash는 Linux 서버, macOS 터미널, CI 파이프라인에서 쓰는 유닉스 명령줄 셸이자 스크립트 언어입니다. Bash 스크립트는 셸 명령에 변수, 조건문, 반복문, 함수, 파이프를 결합한 텍스트 파일입니다. 이 페이지에서는 스크립트를 작성하고 실행하는 방법, 변수를 안전하게 따옴표로 감싸는 법, 제어 흐름, 배열, 출력 리다이렉션, set -euo pipefail을 이용한 오류 처리를 보여 줍니다.

chmod 계산기 열기

Bash란 무엇이고 어디에 쓰는가

Bash(Bourne Again SHell)는 GNU 프로젝트의 명령줄 셸이자 스크립트 언어입니다. 입력한 명령을 읽고, 프로그램을 실행하며, 파이프와 리다이렉션으로 프로그램들을 연결합니다. Bash 스크립트는 이 명령들을 담은 텍스트 파일이므로, 터미널에서 손으로 하던 일은 무엇이든 자동화할 수 있습니다.

Bash는 대부분의 Linux 배포판에서 기본 대화형 셸이며, macOS(지금은 기본 셸이 zsh이지만 Bash도 포함됨)와 WSL 또는 Git Bash를 통한 Windows에서도 쓸 수 있습니다. 개발자, 시스템 관리자, DevOps 엔지니어는 빌드 스크립트, 배포, CI 작업, 서버 유지 보수, 그리고 grep, sed, awk, sort 같은 도구를 이용한 빠른 데이터 처리에 Bash를 사용합니다.

스크립트 작성과 Hello, world 실행

스크립트는 어떤 인터프리터를 쓸지 시스템에 알려 주는 셔뱅(shebang) 줄로 시작합니다. chmod로 파일을 실행 가능하게 만든 뒤 실행하세요.

#!/usr/bin/env bash
# hello.sh
echo "Hello, world!"
chmod +x hello.sh
./hello.sh
# Hello, world!
bash hello.sh       # also works without chmod
bash --version      # check which version you have

#!/usr/bin/env bash는 PATH에서 Bash를 찾으므로 /bin/bash를 직접 적는 것보다 이식성이 좋습니다. 스크립트가 POSIX 기능만 쓴다면 #!/bin/sh를 쓸 수 있지만, 그러면 [[ ]]나 배열 같은 Bash 전용 문법은 사용할 수 없습니다.

변수, 따옴표, 확장

name=value로 할당하고 $name 또는 ${name}으로 읽습니다. 모든 변수는 문자열이며, Bash는 $(( )) 안에서만 정수 연산을 합니다. 공백과 특수 문자가 유지되도록 확장 부분은 큰따옴표로 감싸세요.

name="Ada"                 # no spaces around =
count=3
echo "Hello, $name"        # Hello, Ada
echo "Next: $((count + 1))"  # Next: 4  (integer arithmetic)
today=$(date +%Y-%m-%d)    # command substitution
echo "Today is $today"
file="my report.txt"
echo "${file%.txt}.pdf"    # my report.pdf  (strip suffix)
echo "${#name}"            # 3  (string length)
echo "${missing:-default}" # default  (fallback value)
문법의미
"$var"변수의 값. 하나의 단어로 유지됨
'text'문자 그대로의 텍스트. 확장이 전혀 일어나지 않음
$(command)명령의 출력
$((a + b))정수 산술
$1, $2, $@, $#스크립트 또는 함수의 인자, 모든 인자, 인자 개수
$?마지막 명령의 종료 상태(0은 성공)

조건문과 case

if는 명령을 실행하고 그 종료 상태를 검사합니다. [[ ]] 테스트 명령은 문자열을 비교하고 파일을 검사하며, (( ))는 숫자를 비교합니다. case는 값을 글롭 패턴과 대조합니다.

#!/usr/bin/env bash
file="${1:-notes.txt}"

if [[ -f "$file" ]]; then
  echo "$file exists"
elif [[ -d "$file" ]]; then
  echo "$file is a directory"
else
  echo "$file not found"
fi

n=7
if (( n % 2 == 1 )); then echo "$n is odd"; fi

case "$file" in
  *.txt) echo "text file" ;;
  *.sh)  echo "shell script" ;;
  *)     echo "something else" ;;
esac
테스트참이 되는 경우
[[ -f path ]]경로가 일반 파일임
[[ -d path ]]경로가 디렉터리임
[[ -z "$s" ]]문자열이 비어 있음
[[ "$a" == "$b" ]]문자열이 같음
(( a > b ))숫자 a가 b보다 큼
[[ $a -lt $b ]]숫자 a가 b보다 작음(구식 표기)

반복문

Bash에는 단어 목록을 순회하는 for, C 스타일 for (( )), while, until이 있습니다. 파일을 한 줄씩 읽을 때는 while read를 사용합니다.

for fruit in apple banana cherry; do
  echo "fruit: $fruit"
done

for (( i = 1; i <= 3; i++ )); do
  echo "i = $i"
done

for f in *.log; do
  [[ -e "$f" ]] || continue   # skip if no .log files match
  echo "log file: $f"
done

while IFS= read -r line; do
  echo "line: $line"
done < input.txt

IFS= read -r은 각 줄을 쓰인 그대로 읽습니다. IFS=는 앞쪽 공백을 유지하고 -r은 백슬래시를 유지합니다.

함수

name() { ... }로 함수를 정의하고 명령처럼 호출합니다. 인자는 $1, $2 등으로 전달됩니다. 함수는 return으로 종료 상태(0~255)를 반환하며, 데이터를 반환하려면 출력한 뒤 $( )로 그 출력을 받아 오세요.

greet() {
  local name="${1:-world}"   # local variable, default value
  echo "Hello, $name!"
}

is_even() {
  (( $1 % 2 == 0 ))          # exit status 0 means true
}

greet                         # Hello, world!
greet "Ann"                   # Hello, Ann!
msg=$(greet "Bo")             # capture output
echo "Got: $msg"              # Got: Hello, Bo!

if is_even 4; then echo "4 is even"; fi

배열과 연관 배열

인덱스 배열은 문자열 목록을 담습니다. 연관 배열은 문자열 키를 값에 대응시키며 Bash 4 이상이 필요하므로, macOS에 들어 있는 오래된 Bash 3.2에서는 사용할 수 없습니다.

langs=("bash" "python" "go")
langs+=("rust")
echo "${langs[0]}"           # bash
echo "${#langs[@]}"          # 4
for l in "${langs[@]}"; do echo "- $l"; done

# associative array (Bash 4 or later)
declare -A port
port[http]=80
port[https]=443
echo "${port[https]}"        # 443
for key in "${!port[@]}"; do
  echo "$key -> ${port[$key]}"
done

요소를 순회할 때는 항상 따옴표를 붙여 "${array[@]}"라고 쓰세요. 요소에 공백이 있어도 각 요소가 하나의 단어로 유지됩니다.

파이프와 리다이렉션

Bash의 대표 기능은 작은 프로그램을 조합하는 것입니다. 파이프 |는 한 명령의 출력을 다음 명령의 입력으로 보냅니다. 리다이렉션은 출력을 파일로 보내거나 파일에서 입력을 읽습니다. 파일 디스크립터 1은 표준 출력, 2는 표준 오류입니다.

ls -l > files.txt            # write stdout to a file (overwrite)
echo "more" >> files.txt     # append
grep error app.log 2> /dev/null   # discard error messages
make > build.log 2>&1        # stdout and stderr to the same file

# count the 3 most common words in a file
tr -cs '[:alpha:]' '\n' < book.txt | tr '[:upper:]' '[:lower:]' |
  sort | uniq -c | sort -rn | head -n 3

오류 처리와 안전한 스크립트

기본적으로 Bash는 명령이 실패해도 계속 진행합니다. 많은 스크립트가 set -euo pipefail로 시작합니다. -e는 처리되지 않은 오류에서 종료하고, -u는 설정되지 않은 변수를 오류로 취급하며, -o pipefail은 파이프라인 안의 명령 중 하나라도 실패하면 파이프라인을 실패로 만듭니다. trap은 스크립트가 종료될 때 정리 코드를 실행합니다.

#!/usr/bin/env bash
set -euo pipefail            # stop on errors, unset vars, pipe failures

tmp=$(mktemp)
trap 'rm -f "$tmp"' EXIT     # clean up when the script exits

if ! grep -q "ready" status.txt; then
  echo "status is not ready" >&2   # errors go to stderr
  exit 1
fi

cp config.txt "$tmp" || { echo "copy failed" >&2; exit 2; }
echo "ok"

한 줄로 실패를 처리하려면 ||, 앞 명령이 성공했을 때만 실행하려면 &&를 사용하세요. set -e에는 예외가 있으므로(예: if 조건 안의 명령은 이를 발동시키지 않음) 중요한 명령은 명시적으로 확인하세요.

작은 완성 스크립트: 날짜별 백업

이 스크립트는 디렉터리를 타임스탬프가 붙은 .tar.gz 파일로 보관하고 최신 아카이브 다섯 개만 남깁니다. 인자, 기본값, 테스트, 명령 치환, 파이프를 사용합니다.

#!/usr/bin/env bash
# backup.sh: copy a directory into a dated .tar.gz archive
set -euo pipefail

usage() { echo "usage: $0 SOURCE_DIR [DEST_DIR]" >&2; exit 1; }

[[ $# -ge 1 ]] || usage
src="$1"
dest="${2:-$HOME/backups}"
[[ -d "$src" ]] || { echo "not a directory: $src" >&2; exit 1; }

mkdir -p "$dest"
stamp=$(date +%Y%m%d-%H%M%S)
archive="$dest/$(basename "$src")-$stamp.tar.gz"

tar -czf "$archive" -C "$(dirname "$src")" "$(basename "$src")"
size=$(du -h "$archive" | cut -f1)
echo "Created $archive ($size)"

# keep only the 5 newest archives for this source
ls -1t "$dest"/"$(basename "$src")"-*.tar.gz | tail -n +6 | while read -r old; do
  rm -f -- "$old"
  echo "Removed $old"
done
./backup.sh ~/projects/site
# Created /home/ann/backups/site-20261031-093000.tar.gz (2.1M)

다음 단계

  • 공식 문서인 GNU Bash Reference Manual.
  • 터미널에서 man bash와 help(예: help read).
  • 흔한 스크립트 버그를 설명해 주는 정적 분석 도구 ShellCheck.
  • sh, dash, BusyBox에서도 실행되는 스크립트가 필요하다면 POSIX 셸 명세.

자주 묻는 질문

Bash와 sh의 차이는 무엇인가요?

sh는 POSIX 셸 표준이며 dash 같은 프로그램이 구현합니다. Bash는 POSIX sh와 호환되면서 [[ ]], 배열, (( )), 중괄호 확장 같은 기능을 추가합니다.

Bash와 zsh의 차이는 무엇인가요?

일상적인 명령 대부분은 둘 다 호환됩니다. zsh는 대화형 기능이 더 많고 macOS의 기본 셸입니다. bash 셔뱅으로 작성한 스크립트는 여전히 Bash로 실행됩니다.

스크립트가 permission denied라고 나오는 이유는 무엇인가요?

파일이 실행 가능하지 않기 때문입니다. chmod +x script.sh를 실행하거나 bash script.sh로 시작하세요.

Bash 스크립트에 인자는 어떻게 전달하나요?

./script.sh one two처럼 스크립트 이름 뒤에 씁니다. 스크립트 안에서는 $1, $2로 접근하고, $@는 전체 인자, $#는 인자 개수입니다.

큰 프로그램도 Bash로 작성해야 하나요?

Bash는 명령을 이어 붙이는 용도에 가장 적합합니다. 스크립트가 복잡해지거나 자료구조, 세심한 오류 처리가 필요해지면 파이썬 같은 언어가 보통 유지 보수하기 더 쉽습니다.

코딩 튜토리얼 전체