Bash 조건문 비교: test, [ ], [[ ]] 사용 시기
test, 단일 대괄호, 이중 대괄호를 비교하여 Bash 조건문을 이식 가능하고 안전하며 읽기 쉽게 유지하는 방법을 알아보세요.
Bash 조건문 비교: test, [ ], [[ ]] 사용 시기
Bash 조건문이 이상하게 동작할 때, 문제는 종종 선택한 구조에 있습니다. test, [ ], [[ ]]는 비슷해 보이지만, 따옴표 처리, 패턴, 정규식, 이식성에서 차이가 있습니다.
이 가이드는 세 가지 형식을 비교하여 스크립트가 실제로 실행되는 셸에 적합한 안전하고 읽기 쉬운 조건문을 작성할 수 있도록 도와줍니다.
test 명령어: 기본
test 명령어는 셸 스크립트에서 조건을 평가하는 가장 오래되고 기본적인 방법 중 하나입니다. 대부분의 현대 셸에 내장된 명령어이며 POSIX 표준의 일부로, 이식성이 뛰어납니다. test는 표현식을 평가하고 종료 상태로 0(참) 또는 1(거짓)을 반환합니다.
기본 사용법
test 명령어는 하나 이상의 인수를 받아 평가할 표현식을 구성합니다. 파일 속성, 문자열 비교, 정수 비교를 확인합니다.
# 파일이 존재하는지 확인
if test -f "myfile.txt"; then
echo "myfile.txt가 존재하며 일반 파일입니다."
fi
# 두 문자열이 같은지 확인
NAME="Alice"
if test "$NAME" = "Alice"; then
echo "이름이 Alice입니다."
fi
# 한 숫자가 다른 숫자보다 큰지 확인
COUNT=10
if test "$COUNT" -gt 5; then
echo "Count가 5보다 큽니다."
fi
일반적인 test 연산자
- 파일 연산자:
-f(일반 파일),-d(디렉토리),-e(존재),-s(비어 있지 않음),-r(읽기 가능),-w(쓰기 가능),-x(실행 가능). - 문자열 연산자:
=(같음),!=(같지 않음),-z(문자열이 비어 있음),-n(문자열이 비어 있지 않음). - 정수 연산자:
-eq(같음),-ne(같지 않음),-gt(큼),-ge(크거나 같음),-lt(작음),-le(작거나 같음).
팁: 변수 값에 공백이나 글로브 문자가 포함된 경우 단어 분리 및 경로명 확장 문제를 방지하려면 test와 함께 사용할 변수를 항상 따옴표로 묶으세요(예: "$NAME").
단일 대괄호 [ ]: test 형식
단일 대괄호 [ ] 구조는 test 명령어의 대체 구문입니다. 많은 셸에서 [는 셸 내장 명령어이며, 시스템은 종종 외부 /usr/bin/[도 제공합니다. 주요 차이점은 [가 마지막 인수로 닫는 ]를 필요로 한다는 것입니다. test와 마찬가지로 POSIX를 준수합니다.
구문 및 의미
# test -f "myfile.txt"와 동일
if [ -f "myfile.txt" ]; then
echo "myfile.txt가 존재하며 [ ]를 사용한 일반 파일입니다."
fi
# test "$NAME" = "Alice"와 동일
NAME="Bob"
if [ "$NAME" != "Alice" ]; then
echo "이름이 Alice가 아닙니다."
fi
[ 뒤와 ] 앞에 필수 공백이 있음에 주의하세요. 이들은 [ 명령어에 대한 별도의 인수로 처리됩니다.
변수 따옴표: 중요한 세부 사항
[ ]는 기본적으로 test 명령어이므로 단어 분리 및 경로명 확장에 관한 동일한 동작을 상속합니다. 즉, 따옴표로 묶지 않은 변수는 예기치 않은 동작이나 보안 취약점을 초래할 수 있습니다.
다음 예를 고려하세요:
#!/bin/bash
INPUT="file with spaces.txt"
# 위험: 따옴표로 묶지 않은 변수는 INPUT에 공백이 포함된 경우 문제를 일으킵니다.
# 셸이 단어 분리를 수행하여 "file"과 "with spaces.txt"를 별도의 인수로 처리,
# 구문 오류나 잘못된 평가를 초래합니다.
# if [ -f $INPUT ]; then echo "Found"; else echo "Not found"; fi
# 올바름: 변수를 따옴표로 묶어 단일 인수로 처리
if [ -f "$INPUT" ]; then
echo "'file with spaces.txt'가 존재합니다."
else
echo "'file with spaces.txt'가 존재하지 않거나 일반 파일이 아닙니다."
fi
따옴표가 없으면 $INPUT은 file with spaces.txt로 확장되고, [ -f file with spaces.txt ]는 -f가 하나의 피연산자만 기대하므로 [ 명령어에 의해 구문 오류로 해석됩니다. 따옴표를 사용하면 $INPUT이 단일 인수 "file with spaces.txt"로 전달됩니다.
단어 분리 및 경로명 확장 위험
test와 [ 모두 셸의 기본 동작인 단어 분리 및 경로명 확장(글로빙)의 영향을 받습니다. 변수에 공백이나 글로브 문자(*, ?, [ ])가 포함되어 있고 따옴표로 묶지 않은 경우, 셸은 test나 [가 인수를 보기 전에 이를 확장합니다. 이는 글로브 문자가 기존 파일과 일치할 때 구문 오류나 잘못된 비교를 초래할 수 있습니다.
이중 대괄호 [[ ]]: 현대 Bash 키워드
이중 대괄호 [[ ]] 구조는 Bash 키워드(Ksh 및 Zsh에서도 지원)이며, 외부 명령어나 별칭이 아닙니다. 이 차이는 중요하며, [[ ]]가 test나 [ ]와 다르게 동작하고 향상된 기능과 개선된 안전성을 제공할 수 있게 합니다.
향상된 기능
[[ ]]는 test나 [에서 사용할 수 없는 몇 가지 강력한 기능을 도입합니다:
단어 분리 또는 경로명 확장 없음:
[[ ]]내의 변수는 일반적으로 따옴표로 묶을 필요가 없습니다(명확성을 위해 묶는 것이 좋은 습관이지만). 셸은[[ ]]의 내용을 단일 단위로 처리하여 단어 분리 및 경로명 확장을 방지합니다. 이는 일반적인 스크립팅 오류와 보안 위험을 크게 줄입니다.# 변수를 따옴표로 묶을 필요 없음(그래도 안전함) INPUT="file with spaces.txt" if [[ -f $INPUT ]]; then # $INPUT은 여기서 단일 문자열로 처리됨 echo "'$INPUT'이 존재합니다." fi문자열 비교를 위한 글로빙:
==및!=연산자는[[ ]]내에서 사용될 때 엄격한 문자열 동등성 대신 패턴 매칭(글로빙)을 수행합니다. 즉,*,?,[]를 와일드카드로 사용할 수 있습니다.FILE_NAME="my_document.txt" if [[ "$FILE_NAME" == *".txt" ]]; then # FILE_NAME이 .txt로 끝나는지 확인 echo "텍스트 파일입니다!" fi # 참고: 글로빙 없이 엄격한 문자열 동등성을 원하면 `test` 또는 `[ ]`와 `=`를 사용하거나 # `[[ ]]`의 `==` 오른쪽에 글로브 문자가 없는지 확인하세요. # (또는 리터럴 글로브 문자를 문자 그대로 일치시키려면 오른쪽을 따옴표로 묶으세요).정규식 매칭:
=~연산자를 사용하면 정규식 매칭을 수행할 수 있습니다.
IP_ADDRESS="192.168.1.100"
if [[ "$IP_ADDRESS" =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]]; then
echo "유효한 IP 형식입니다."
fi
# 중요: =~의 오른쪽에 있는 정규식 패턴은 일반적으로 따옴표로 묶지 않아야 합니다.
# 패턴에 글로브 패턴으로 처리될 수 있는 문자가 포함된 경우.
# 정규식이 변수에 있는 경우에도 따옴표로 묶지 않아야 합니다.
# 패턴 예: ^[A-Za-z]+$
```
4. **논리 연산자 `&&` 및 `||`**: `[[ ]]`는 여러 조건을 결합하기 위해 더 직관적인 C 스타일 논리 연산자 `&&`(AND) 및 `||`(OR)를 `!`(부정)와 함께 지원합니다. 이러한 연산자는 `test`의 `-a` 및 `-o`와 달리 적절한 단락 평가와 우선순위를 가집니다.
```bash
AGE=25
if [[ "$NAME" == "Alice" && "$AGE" -ge 18 ]]; then
echo "Alice는 성인입니다."
fi
if [[ "$USER" == "root" || -w /etc/fstab ]]; then
echo "root이거나 fstab에 쓸 수 있습니다."
fi
```
### Bash 특화 특성
`[[ ]]`는 상당한 이점을 제공하지만, 주요 단점은 Bash/Ksh/Zsh 확장이며 POSIX 표준의 일부가 아니라는 점입니다. 즉, `[[ ]]`에 의존하는 스크립트는 `sh`, `dash` 또는 오래된/최소한의 Unix 계열 시스템으로 이식되지 않을 수 있습니다.
## 나란히 비교: `test` vs. `[` vs. `[[`
다음 표는 주요 차이점을 요약합니다:
| 기능 | `test` | `[ ]` | `[[ ]]` |
| :------------------------- | :------------------------------- | :---------------------------------- | :---------------------------------------- |
| **유형** | 내장 명령어(또는 외부) | 내장 명령어(`test`의 별칭) | 셸 키워드(Bash, Ksh, Zsh) |
| **POSIX 준수** | 예 | 예 | 아니요 |
| **닫는 `]` 필요** | 아니요 | 예 (마지막 인수로) | 예 (키워드의 일부로) |
| **단어 분리** | 예, 따옴표 없는 변수 | 예, 따옴표 없는 변수 | 아니요, 변수는 단일 문자열로 처리됨 |
| **경로명 확장** | 예, 따옴표 없는 변수 | 예, 따옴표 없는 변수 | 아니요 |
| **글로빙 패턴 매칭** | 문자열 동등성에 대해 아니요 | 문자열 동등성에 대해 아니요 | 예, `==` 또는 `!=`의 따옴표 없는 오른쪽 |
| **정규식** | 아니요 | 아니요 | 예, `=~` 사용 |
| **논리 AND/OR** | `-a`, `-o`가 존재하지만 오해하기 쉬움 | `-a`, `-o`가 존재하지만 오해하기 쉬움 | `&&`, `||` 일반적인 단락 평가 동작 |
| **복합 명령어** | 별도의 `test` 호출 필요 | 별도의 `[` 호출 필요 | 표현식을 직접 결합 가능 (`&&`/`||`)|
| **변수 따옴표** | **필수** (안전을 위해) | **필수** (안전을 위해) | 일반적으로 필요하지 않지만 좋은 습관 |
## 언제 무엇을 사용할지
올바른 조건부 구조를 선택하는 것은 주로 이식성 요구 사항과 조건부 논리의 복잡성에 따라 달라집니다.
### POSIX 준수 vs. 현대 Bash 기능
- **다음 경우 `test` 또는 `[ ]`를 사용하세요...**
- **이식성이 가장 중요한 경우**: 스크립트가 모든 POSIX 호환 셸(`sh`, `dash`, 오래된 시스템 등)에서 실행되어야 하는 경우 `test` 또는 `[ ]`가 유일한 신뢰할 수 있는 옵션입니다.
- 조건이 간단한 경우(파일 확인, 기본 문자열/정수 비교).
- 모든 변수를 신중하게 따옴표로 묶고 복합 논리가 필요할 때 대괄호 외부에서 셸 수준의 `&&`/`||`를 사용하는 데 익숙한 경우.
- **다음 경우 `[[ ]]`를 사용하세요...**
- **Bash 전용으로 작성하는 경우**(또는 Ksh/Zsh) POSIX 이식성이 필요하지 않은 경우.
- 글로빙 패턴 매칭, 정규식 매칭 또는 C 스타일 `&&`/`||` 논리 연산자와 같은 고급 기능이 필요한 경우.
- 단어 분리 및 경로명 확장을 방지하여 더 강력하고 오류 가능성이 적은 코드를 제공하는 향상된 안전 기능을 원하는 경우.
- `test -a`/`-o`로는 다루기 힘든 복잡한 논리가 포함된 조건이 있는 경우.
### 모범 사례 및 권장 사항
1. **Bash 스크립트의 경우 `[[ ]]`를 우선시하세요**: 스크립트가 Bash용인 경우, `[[ ]]`는 향상된 안전성, 확장된 기능, 복잡한 조건에 대한 더 직관적인 구문으로 인해 일반적으로 선호되는 선택입니다. 따옴표 및 특수 문자와 관련된 일반적인 스크립팅 오류를 크게 줄여줍니다.
2. **`test` 및 `[ ]`에서는 항상 따옴표를 사용하세요**: POSIX 준수를 위해 `test` 또는 `[ ]`를 *반드시* 사용해야 하는 경우, 단어 분리 및 경로명 확장으로 인한 예기치 않은 동작을 방지하기 위해 변수를 **항상 따옴표로 묶는** 습관을 들이세요.
```bash
# [ ] 및 test에 대한 좋은 습관
VAR="a string with spaces"
if [ -n "$VAR" ]; then echo "비어 있지 않음"; fi
```
3. **패턴 매칭에 주의하세요**: `test` 및 `[ ]`에서 `=`는 문자열 동등성에 사용됩니다. `[[ ]]`에서 `=`와 `==`는 오른쪽이 따옴표로 묶이지 않은 경우 패턴 매칭을 수행할 수 있습니다. 리터럴 문자열 비교를 원할 때는 오른쪽을 따옴표로 묶으세요.
4. **`=~`를 사용한 정규식**: `[[ ]]`에서 `=~`를 사용할 때, 오른쪽은 일반적으로 따옴표로 묶지 않아야 셸이 이를 리터럴 문자열이 아닌 정규식 패턴으로 해석할 수 있습니다.
```bash
# [[ ]]에서 =~에 대한 따옴표 없는 정규식 패턴이 올바름
if [[ "$LINE" =~ ^Error: ]]; then echo "오류 발견"; fi
```
## 요점
스크립트가 POSIX `sh`에서 실행되어야 하는 경우 `[ ]` 또는 `test`를 사용하세요. 쉬뱅이 Bash이고 더 안전한 변수 처리, 글로브 매칭, 정규식 매칭, 더 깔끔한 복합 조건을 원하는 경우 `[[ ]]`를 사용하세요. 주요 습관은 간단합니다: 조건부 구문을 셸에 맞추고 의도적으로 따옴표를 사용하세요.