조건 복합 명령 — if · case · [[ ]] · (( ))
이 글은 GNU Bash 매뉴얼 3.2.5.2 Conditional Constructs와 6.4 Bash Conditional Expressions를, 처음 배우는 분도 따라올 수 있게 쉬운 말로 풀어 정리합니다. “이 조건이면 A, 아니면 B”처럼 갈라지는 길 을 만드는 방법이 핵심입니다.
이 글을 읽고 나면
if / elif / else로 일반 분기를 작성할 수 있습니다.
[[ ]]로 파일·문자열·패턴을 안전하게 검사할 수 있습니다.
(( ))로 숫자 조건을, case로 여러 패턴 갈래를 다룰 수 있습니다.
명령의 종료 코드 가 곧 “참/거짓”이라는 점을 이해합니다.
어떤 조건을 쓸까
조건문도 종류가 많아 보이지만, 고르는 기준은 “무엇을 검사할지”입니다.
검사할 것 |
알맞은 구문 |
한 줄 설명 |
|---|---|---|
파일이 있는가 / 읽을 수 있는가 |
[[ -f … ]] 등 |
파일·디렉터리 상태 |
문자열이 비었는가 / 같은가 |
[[ -z … ]] / == |
문자열 |
이름이 prod*처럼 생겼는가 |
[[ $x == prod* ]] |
글롭 패턴 |
숫자가 큰가 |
(( n > 2 )) 또는 -gt |
산술 |
값이 여러 갈래 중 어디인가 |
case |
패턴별 분기 |
방금 명령이 성공했는가 |
if cmd; then |
종료 코드 |
Text
파일·문자열·패턴 ──▶ [[ ... ]] 숫자 계산 ──────────▶ (( ... )) 여러 갈래 패턴 ─────▶ case ... esac 명령 성공/실패 ─────▶ if 명령; then
이전 글에서 && / ||로 짧게 이을 수 있다고 했습니다. 분기가 길어지거나 여러 갈래가 생기면 if / case가 훨씬 읽기 쉽습니다.
조건의 진짜 의미 — 종료 코드
셸에서 “참/거짓”은 수학의 true/false가 아니라 명령이 끝난 뒤의 숫자 입니다.
종료 코드 |
조건문에서의 뜻 |
|---|---|
0 |
참 (성공) → then으로 |
0이 아님 |
거짓 (실패) → else / 다음 검사로 |
그래서 아래 두 가지는 본질적으로 같은 동작 입니다.
Bash
if grep -q 'hello' notes.txt; then echo "found" fi # grep: 파일에서 'hello' 문자열이 있는지 찾는 명령입니다. # grep이 'hello'를 찾으면(성공, 종료 코드 0) then 블록이 실행되고, # 못 찾으면(주로 종료 코드 1) then을 건너뜁니다.
if / elif / else — 기본 분기
형태는 거의 항상 같습니다.
Bash
if 조건; then # 조건이 참일 때 elif 다른조건; then # 위는 거짓이고, 여기가 참일 때 else # 위에서 모두 거짓일 때 fi
then / fi는 예약어입니다. 빠뜨리면 문법이 깨집니다.
elif는 여러 번 쓸 수 있습니다.
else는 생략해도 됩니다.
Bash
if [[ -f "$path" ]]; then echo "file exists" elif [[ -d "$path" ]]; then echo "it is a directory" else echo "not found" fi
[[ ]] — Bash에서 조건을 쓰는 기본 도구
Bash 스크립트에서는 조건을 쓸 때 [[ ... ]]를 기본 으로 쓰는 편이 안전합니다. (POSIX의 [ / test보다 공백·글로브에서 덜 깨집니다.)
[[와 ]] 사이는 “조건식”을 적는 자리입니다. 식이 참이면 종료 코드 0, 거짓이면 비0이 되어 if가 갈라집니다.
파일 검사 (자주 쓰는 것)
검사 |
뜻 |
|---|---|
-e 경로 |
존재한다 (종류 무관) |
-f 경로 |
일반 파일이다 |
-d 경로 |
디렉터리이다 |
-L 경로 |
심볼릭 링크이다 |
-r / -w / -x |
읽기 / 쓰기 / 실행 가능 |
-s 경로 |
존재하고 크기가 0보다 크다 |
Bash
if [[ -f "$demo_dir/notes.txt" && -r "$demo_dir/notes.txt" ]]; then echo "readable file" fi
[[ 안에서는 && / ||를 그대로 쓸 수 있습니다. (즉, &&는 왼쪽이 거짓일 때, ||는 왼쪽이 참일 때 각각 오른쪽 조건은 평가하지 않고 건너뜁니다. 이런 방식 덕분에 불필요한 비교나 에러 발생을 막을 수 있습니다.)
문자열 검사
검사 |
뜻 |
|---|---|
-z "$s" |
비어 있다 (길이 0) |
-n "$s" |
비어 있지 않다 |
"$a" == "$b" |
문자열이 같다 |
"$a" != "$b" |
문자열이 다르다 |
Bash
if [[ -z "${empty:-}" ]]; then echo "empty is empty" fi if [[ "$name" == "Ada" ]]; then echo "name is Ada" fi
패턴 매칭 (== 오른쪽)
[[에서 ==(또는 =)의 오른쪽을 따옴표로 감싸지 않으면, 글롭 패턴으로 비교합니다. (prod*, *.txt 등)
Bash
mode="release" if [[ "$mode" == rel* ]]; then echo "mode starts with rel" fi
쓰고 싶은 것 |
쓰는 법 |
|---|---|
패턴으로 비교 |
[[ $x == prod* ]] (오른쪽 따옴표 X) |
글자 그대로 비교 |
[[ $x == "$y" ]] (오른쪽 따옴표 O) |
정규식까지 쓰고 싶다면 =~가 있습니다. ([[ $line =~ ^[0-9]+$ ]]) 처음에는 글롭 패턴만으로도 충분한 경우가 많습니다.
정수 비교 ([[ 안)
연산 |
뜻 |
|---|---|
-eq |
같다 |
-ne |
다르다 |
-lt / -le |
작다 / 작거나 같다 |
-gt / -ge |
크다 / 크거나 같다 |
Bash
if [[ "$count" -ge 3 ]]; then echo "count >= 3" fi
문자열 비교(==)와 숫자 비교(-eq)를 헷갈리지 마세요. "10" == "9"는 문자열이 다르고, [[ 10 -gt 9 ]]는 숫자로 참입니다.
(( )) — 숫자 계산이 조건일 때
산술식이 0이 아니면 참(종료 코드 0), 0이면 거짓 입니다.
Bash
count=3 if (( count > 2 )); then echo "count is greater than 2" fi
(( 안에서는 $ 없이도 변수를 쓸 수 있어, 숫자 반복·카운터와 잘 맞습니다. (이전 글의 for ((i=0; i<3; i++))와 같은 계열입니다.)
case — 여러 갈래 패턴
값이 “이 목록 중 어디에 해당하는가”를 나눌 때 if를 여러 번 쓰기보다 case가 읽기 쉽습니다.
Bash
case "$mode" in debug) echo "build=debug" ;; release) echo "build=release" ;; *) echo "build=unknown" ;; esac
동작을 쉽게 말하면:
"$mode" 값을 위에서부터 패턴과 비교합니다.
처음 맞는 갈래 만 실행합니다.
각 갈래는 ;;로 끝냅니다.
*)는 “위에 아무것도 안 맞으면” — 기본값으로 자주 씁니다.
한 갈래에 여러 패턴은 |로 이을 수 있습니다. (debug|test))
Text
mode="release" │ ▼ debug? → 아니오 release? → 예 ──▶ echo build=release ──▶ 끝 *) → (도달 안 함)
[와 [[ — 짧게만
[ / test |
[[ ]] (Bash) |
|
|---|---|---|
이식성 |
POSIX에 가깝다 |
Bash 전용에 가깝다 |
공백·글로브 |
따옴표 실수가 잦다 |
상대적으로 덜 깨진다 |
&& 안에서 |
불편 |
자연스럽다 |
패턴 == |
없음(문자열만) |
지원 |
Bash로 스크립트를 쓴다면 [[ ]]를 기본 으로 두고, 꼭 sh 호환이 필요할 때만 [를 쓰는 전략이 실무에서 흔합니다.
실행 예제
아래 스크립트는 if / [[ ]] / (( )) / case / “명령을 조건으로”를 한 화면에서 비교해 보여 줍니다.
Bash
#!/usr/bin/env bash # if / case / [[ ]] / (( )) 조건 분기를 한 화면에서 비교합니다. set -eu demo_dir="$(mktemp -d "${TMPDIR:-/tmp}/b05.XXXXXX")" cleanup() { rm -rf -- "$demo_dir"; } trap cleanup EXIT printf 'hello\n' >"$demo_dir/notes.txt" name="Ada" count=3 mode="release" echo "=== 1) if / elif / else ===" if [[ -f "$demo_dir/notes.txt" ]]; then # (1) 일반 파일이면 참 echo "file exists" elif [[ -d "$demo_dir/notes.txt" ]]; then # (2) 아니면 디렉터리인지 검사 echo "it is a directory" else echo "not found" fi echo echo "=== 2) [[ ]] 문자열 검사 ===" if [[ -z "${empty:-}" ]]; then # (3) 비어 있거나 없으면 참 echo "empty is empty" fi if [[ -n "$name" ]]; then # (4) 길이가 0보다 크면 참 echo "name=$name" fi if [[ "$name" == "Ada" ]]; then # (5) 문자열이 같은지 echo "name is Ada" fi echo echo "=== 3) [[ ]] 패턴 매칭 ===" if [[ "$mode" == rel* ]]; then # (6) 오른쪽은 글롭 패턴 (quote 안 함) echo "mode starts with rel" fi echo echo "=== 4) [[ ]] 파일·숫자·조합 ===" if [[ -f "$demo_dir/notes.txt" && -r "$demo_dir/notes.txt" ]]; then # (7) 둘 다 참일 때 echo "readable file" fi if [[ "$count" -ge 3 ]]; then # (8) 정수 비교 echo "count >= 3" fi echo echo "=== 5) (( )) 산술 조건 ===" if (( count > 2 )); then # (9) 산술식이 0이 아니면 참 echo "count is greater than 2" fi echo echo "=== 6) case: 여러 갈래 패턴 ===" case "$mode" in # (10) 위에서부터 첫 일치만 실행 debug) echo "build=debug" ;; release) echo "build=release" ;; *) echo "build=unknown" ;; esac echo echo "=== 7) 명령 자체를 조건으로 ===" if grep -q 'hello' "$demo_dir/notes.txt"; then # (11) 종료 코드 0이면 then echo "grep found hello" fi echo echo "ok — 조건은 '무엇을 검사할지'에 따라 골라 씁니다."
주요 라인별 설명
1–2: if / elif — 파일인지 먼저 보고, 아니면 디렉터리인지 봅니다.
3–5: -z(비었음), -n(비지 않음), ==(문자열 같음)을 검사합니다.
6: == 오른쪽 rel*는 패턴입니다. 따옴표를 치면 글자 그대로가 됩니다.
7–8: 파일 권한과 정수 비교(-ge)를 조합합니다.
9: (( ))는 숫자 계산 결과가 0이 아니면 참입니다.
10: case는 위에서부터 첫 패턴만 실행하고, *)는 기본값입니다.
11: grep -q의 성공/실패(종료 코드)가 곧 if의 조건이 됩니다.
실행하면 대략 이런 결과가 나옵니다.
Text
=== 1) if / elif / else === file exists === 2) [[ ]] 문자열 검사 === empty is empty name=Ada name is Ada === 3) [[ ]] 패턴 매칭 === mode starts with rel === 4) [[ ]] 파일·숫자·조합 === readable file count >= 3 === 5) (( )) 산술 조건 === count is greater than 2 === 6) case: 여러 갈래 패턴 === build=release === 7) 명령 자체를 조건으로 === grep found hello
헷갈리기 쉬운 부분
[ $a = $b ]처럼 따옴표 없이 — 값이 비어 있으면 문법이 깨집니다. Bash라면 [[ "$a" == "$b" ]]를 쓰세요.
패턴을 리터럴로 비교하고 싶은데 따옴표를 안 침 — [[ $x == "$y" ]]처럼 오른쪽을 따옴표로 감싸야 글자 그대로입니다.
==와 -eq 혼동 — 문자열은 ==, 정수는 -eq/-gt 또는 (( )).
case에서 ;; 빠뜨림 — 각 갈래 끝에 ;;가 필요합니다.
명령 조건을 [[ grep ... ]]처럼 감싸기 — 명령을 조건으로 쓸 때는 if grep ...; then처럼 명령 자체를 if 뒤에 둡니다.
한 줄 요약
조건의 본질은 종료 코드(0=참)이고, 파일·문자열·패턴은 [[ ]], 숫자는 (( )), 여러 갈래는 case, 긴 분기는 if로 씁니다.