
시작하기
Shell(bash, zsh 등)은 사용자가 입력한 문자열을 바로 실행하지 않는다.
- 먼저 언어 처리 파이프라인을 거쳐
- 문자열을 실제 실행 인자 목록으로 변환한 뒤 실행함.
POSIX / bash에서 정의된 처리 순서는 다음과 같음:
0. Lexical analysis(어휘 분석) (quoting, backslash 처리)
1. Brace expansion(중괄호 확장)
2. Tilde expansion(틸드 확장)
3. Value expansion
* Parameter expansion(매개변수 확장)
* Command substitution(명령 치환)
* Arithmetic expansion(산술 확장)
4. Field splitting(필드 분리) (word splitting)
5. Pathname expansion(경로명 확장) (globbing)
6. Quote removal(따옴표 제거)
7. 실행
이 과정을 shell expansion이라고도 부르면 이는 다음으로 구성됨.
- brace expansion
- tilde expansion
- parameter expansion(=variable expansion)
- command substitution
- arithmetic expansion
- field splitting
- pathname expansion(=globbing)
주의:
- 위 순서는 “확장(expansion)과 단어 생성(word generation)” 관점에서 정리한 규칙임.
- 실제 실행 전에는 위에서 언급한 처리 외에 pipe/redirection 같은 연산자 파싱이 함께 일어난다.
이 문서는 문자열에서 단어가 어떻게 바뀌는가 에만 집중하여
Shell Expansion에 대해 정확한 이해를 하도록 돕는게 목적임.
0. Lexical analysis — quoting과 backslash(escape)
Lexer로도 불리는 단계에서
- shell은 입력 문자를 읽어 토큰(word)을 만들고 (tokenizer),
- 각 문자가 literal인지 아니면 expansion(확장) 대상인지를 구분함 (lexer).
- operator와 word를 구분하는 동작도 이루어지지만 이 문서는 expansion과 관련된 word에만 집중함
- 원래 컴파일러에서의 Lexer는 다음과 같음: lexer = tokenizer + token 분류 + 상태 관리
Shell에도 lexical analysis 단계가 존재하지만,
컴파일러처럼 ‘lexer’나 ‘tokenizer’라는 이름의 구성요소로 따로 분리하진 않음.
bash의 문서등에서도 lexical analysis라고만 부름.
처리되는 문법:
| 문법 | 의미 |
'...' |
완전 리터럴 구간 (... 을 모두 lieteral로) |
"..." |
부분 리터럴 구간 (일부 부분만 literal로) |
\ |
다음 문자 1개를 리터럴로(문자 수준 escape) |
- 주의할 점은 이 단계에서는 값이 바뀌지 않는다는 점임.
- 이 단계에선 오직 이후 expansion 단계가 적용될 위치만 결정됨.
lexical의 어원은 그리스어 λέξις (léxis) 이며 의미는 말, 단어, 표현, 어휘 임.
token에 대한 개념이 헷갈리면 다음을 참고:
literal에 대한 개념이 헷갈리면 다음을 참고:
0-1. Single quote '...'
Single quote 안에서는 모든 expansion과 escape가 차단 됨.
\ 백슬래시도 특별한 의미(escape)를 잃고 그냥 문자 \가 된다.
| 요소 | 동작 |
$VAR |
차단 (Variable Expansion) |
$(cmd) |
차단 (Command Substitution) |
$(( )) |
차단 (Arithmetic Expansion) |
~ |
차단 (Tilde Expansion) |
{a,b} |
차단 (Brace Expansion) |
* ? [ ] |
차단 (Globbing) |
\ |
리터럴 문자 취급 |
| 공백 분리 | 차단 |
다음의 예를 참고
echo '\$HOME \* \n $(date)'
# \$HOME \* \n $(date)
0-2. Double quote "..."
Double quote는 value expansion($ or `로 시작하는 expansion들)을 허용하고
단어 구조 변화(field spliting)와 globbing, tilde expansion, brace expansion 을 차단한다:
특수 문자들 중 $ (dollar sign), \ (backslash), `(backtick) 는 고유한 기능적 의미를 유지하나 나머진 의미를 잃음
$VAR |
허용 (Variable Expansion) |
$(cmd) |
허용 (Command Substitution) |
$(( )) |
허용 (Arithmetic Expansion) |
~ |
차단 (Tilde Expansion) |
{a,b} |
차단 (Brace Expansion) |
* ? [ ] |
차단 (Globbing) |
| 공백 분리 | 차단 |
0-2-1. Double quote 안에서의 escape 규칙(중요)
" 안에서 \는 다음 문자에 대해서만 escape로 작동.
\ 뒤 문자 |
결과 |
" |
" |
\ |
\ |
$ |
$ |
` |
` |
그 외 (n, t 등) |
\n, \t 그대로 |
예제로 확인할 것:
echo "\\ \$HOME \"test\""
# \ $HOME "test"
0-3. Escape \ (문자 수준 escape)
이 단계에서\는 quoting과 독립적인 lexical escape로,
다음에 위치하는 문자 1개를 메타문자에서 제외 한다.
(특수문자를 표현하기 위한 escape eqeuence 처리는 여기서 이루어지지 않음)
다음 예에서 앞에 놓인 backslash로 인해 asterisk 와 환경변수가 원래의 기능이 아닌 문자 그대로로 처리됨.
echo \* \$HOME
# * $HOME
참고: Escape sequence(개행/벨/백스페이스)는 어디서 처리되나?
\n, \a, \b 같은 “제어문자 변환”은 \ 만을 붙인다고 자동으로 처리되진 않음.
그 변환은 다음과 같은 특정 문맥/도구에서 처리함.
1. printf (POSIX 표준, 권장) — 제어문자 변환은 printf가 처리
printf "A\nB\n"
printf "bell:\a done\n"
printf "ab\bX\n"
2. $'...' (bash/zsh의 ANSI-C quoting) — 이 quoting이 \n 등을 실제 제어문자로 변환
printf "%s" $'A\nB\n'
printf "%s" $'\a'
printf "%s" $'ab\bX\n'
3. echo -e (비표준/셸마다 다름, 비권장) — 동작이 일관되지 않아 스크립트에선 피하는 편이 안전
- bash에서
-e옵션(enable)을 켜야 escape sequence를 처리하고, 기본으론 처리를 하지 않으나, - zsh에선 기본으로 escape sequence를 처리하고
-E옵션(disable)을 주면 처리를 안 함.
스크립트 작성 시
가급적echo -e대신printf를 사용할 것.
1. Brace expansion
문자열 패턴을 여러 개로 복제함.
다음의 예를 참고할 것:
echo file{1,2,3}.txt
# file1.txt file2.txt file3.txt
echo {a,b}{1,2}
# a1 a2 b1 b2
1-1. Range 형식의 brace expansion
숫자나 문자를 범위로도 생성할 수 있음: ASCII 에서의 순서 기반.
echo {1..7}
# 1 2 3 4 5 6 7
echo chap{1..5}
# chap1 chap2 chap3 chap4 chap5
echo {a..e}
# a b c d e
1-2. Brace expansion에서 주의할 점.
Brace expansion은 파일 시스템과 무관 한 단순한 문자열 처리임 ( globbing 과의 차이점),
그리고, globbing보다 먼저 수행된다.
brace expansion은 POSIX 필수 기능은 아니지만,
bash/zsh에서 일반적으로 제공됨.
실제 사용에선 표준처럼 널리 쓰이지만, Python의 glob 모듈에선 지원하지 않음.
그리고, Quote 안에서는 작동하지 않음.
2. Tilde expansion
Home 디렉토리 기호 ~를 실제 경로로 치환됨.
다음 예제를 참고:
echo ~
# /home/dsaint31
echo ~root
# /root
이는 Quote 안에서는 작동하지 않음.
echo "~"
# ~
3. Parameter / Command / Arithmetic expansion
이 단계에서 실제 값 계산이 일어난다.
3-1. Parameter expansion ($VAR, ${VAR})
Variable expansion 이라고도 불림.
다음의 사용예를 참고할 것.
name=kim
echo $name
# kim
echo ${HOME}/work
# /home/dsaint31/work
3-2. Command substitution ($(cmd))
예전 버전에선 backtick 으로 감싸는 방식으로 사용했으나 중첩 등이 안되므로 가급적 사용하지 말 것.
echo "Today is $(date)"
$( )를 이용할 경우, 중첩도 가능:
echo "files: $(ls "$(pwd)")"
보다 자세한 건 다음을 참고:
2023.10.01 - [shell] - [Shell] command substitution
[Shell] command substitution
command substitution (명령어치환)우리나라말로 명령어 치환 이라고 불리며,특정 명령어의 수행결과를 문자열로 입력받는 형태로 셀프로그래밍 등에서 사용됨.command substitution 사용법아래 예제는 resu
ds31x.tistory.com
3-3. Arithmetic expansion ($((...)))
정수 연산만 된다는 점을 주의할 것.
echo $((3 + 4))
# 7
a=5; b=3
echo $((a*b + 2))
# 17
4. Field splitting
앞 단계에서 확장된 문자열을 공백(IFS) 기준으로 나눔(spliting).
구조적 변화를 일으킴.
이 단계 때문에
가급적 파일과 디렉토리 이름에
공백을 넣지 않는게 좋다는 애기가 나옴.
4-1. IFS란?
- Internal Field Separator의 약자,
- shell이 field splitting(단어 분리) 을 수행할 때 사용할 구분 문자 집합을 뜻함.
- 기본값은 보통 space / tab / newline 임,
- 환경 변수
IFS에 space, tab, newline 이 할당되어 있음: echo ${IFS}
4-2. 동작
Field splitting은 unquoted 의 확장 결과에 대해서만 IFS를 기준으로 인자가 나눔.
- 이 단계 때문에
"${var}"가 권장됨
(변수의 값에 공백문자가 있는 경우 하나로 처리하려면 quoting이 필요) - IFS를 바꾸면 분리 기준도 바뀐다
다음은 IFS를 :로 바꾼 경우임.
IFS=:
set -- $PATH
printf "%s
" "$1" "$2"
다음은 일반적인 경우의 예임
x="a b"
echo $x
# a b (두 개의 인자)
echo "$x"
# a b (하나의 인자)
또 다른 예:
files="$(printf "a b\nc d\n")"
echo $files # 공백/줄바꿈으로 쪼개짐
echo "$files" # 원형 유지(하나의 인자)
5. Pathname expansion (Globbing)
와일드카드를 실제 파일 이름 목록으로 바꾼다.
보다 자세한 내용은 다음을 참고:
glob 이란?
정의glob은 파일 이름과 경로를 간단한 와일드카드 패턴으로 매칭하는 방식을 가리킴. Bash, zsh 등의 shell 과 여러 프로그래밍 언어(Python의 glob 모듈 등)에서 폴더 내 파일을 찾거나 일괄 처리할 때
ds31x.tistory.com
주의할 점은 실제 파일을 찾아서 해당 이름 목록으로 바꾼다는 것임:
- 패턴에 해당하는 파일이 없을 경우에 대한 처리가 shell마다 다르므로 주의해야 함.
- bash 에선 매칭이 없을 때 패턴 문자열이 반환되는데,
shopt -s failglob로 빈 매칭시 에러를 발생하도록 바꾸는 것이 권장됨.- zsh 에선 에러를 반환
다음은 간단한 예제임:
ls *.txt
a.txt b.txt가 있으면:
ls a.txt b.txt
Quote나 escape된 패턴은 globbing되지 않는다.
ls "*.txt"
ls \*.txt
참고: Globbing 사용할 때 “매칭이 없을 때”에 대한 처리 중요
globbing 매칭 실패 시 동작은 셸 옵션/셸 종류에 따라 달라질 수 있음.
(예: bash의 nullglob, failglob 등)
다시 한번 강조하지만 스크립트에서는 “매칭이 없을 수 있는 패턴”을 다룰 때 주의해야 함.
개인적으로는 shell scripting에선 find 등을 globbing보다 사용하는 것을 선호.
6. Quote removal
이 단계에서 이제 역할이 끝난 ' " \ 가 실제 문자열에서 제거된다.
해당 제거는 모든 expansion과 globbing이 끝난 후에 처리된다.
예제 1 — double quote가 왜 최종 출력에 안 보이는가
echo "abc"
처리 흐름:
- Lexical analysis:
"abc"하나의 토큰 - Expansion 없음
- Quote removal ->
abc - echo abc 실행
출력:
abc
참고로 Windows의 cmd.exe에선 double quote가 보인다.
예제 2 — quote가 구조를 보호하고 나중에 사라짐
echo "$HOME *.txt"
$HOME은 확장됨*.txt에 대한 globbing 은 double quoting에 의해 차단됨.- Quote removal 후 실제 인자:
/home/dsaint31 *.txt
*.txt가 그대로 출력되는 점에 유의할 것.
예제 3 — escape도 최종 인자에서 사라짐
echo "\$HOME"
- Lexical 단계:
$HOME이 literal로 마킹됨 - Expansion 안 됨
- Quote removal →
$HOME
출력:
$HOME
종합 예
종합 예 1 — Double quote
echo "~/{a,b}/$(whoami)/*.txt"
Double quote 때문에:
| 단계 | 결과 |
| brace | double quote에 의해 차단 : {a, b} |
| tilde | double quote에 의해 차단 : ~ |
| command | dsaint31 |
| field split | double quote에 의해 차단 IFS에 의해 분리되지 않음 (따옴표로 보호) |
| glob | double quote에 의해 차단 |
출력:
~/{a,b}/dsaint31/*.txt
종합 예 2 — Single quote
echo '~/{a,b}/$(whoami)/*.txt'
Single quote 때문에:
| 항목 | 결과 |
| brace / tilde / command / arithmetic | 전부 차단 |
| globbing | 차단 |
| backslash escape | 차단(백슬래시는 그냥 문자) |
출력:
~/{a,b}/$(whoami)/*.txt
요약
Shell은 단순한 명령 실행기가 아니라
문자열을 언어 규칙에 따라 계산하는 해석기다.
- Brace → 문자열 생성
- Tilde → 경로 계산
$,$(),$(( ))→ 값 계산- Field splitting → 인자 분리
- Globbing → 파일 이름 계산
- Single quote → 모든 expansion 차단 (globbing도 넓은 의미에서 shell expansion임)
- Double quote → 값 계산만 허용 + 제한적 escape(
\\,\",\$,\`) - Escape(문자 수준) → 문자 하나 보호
- Escape sequence(제어문자) →
printf또는$'...'에서 변환 - Quote removal →
"'\제거
'shell' 카테고리의 다른 글
| echo 와 printf (0) | 2026.01.12 |
|---|---|
| Shell Initialization File-dot files: .profile, .bashrc, .zprofile, .zshrc (0) | 2026.01.02 |
| grep (Global Regular Expression Print) (0) | 2025.12.31 |
| PowerShell의 초기화-profile (0) | 2025.12.31 |
| iconv, clip.exe, pbcopy, xclip, Set-Clipboard (0) | 2025.12.29 |