본문 바로가기
목차
shell

Shell Expansion: Brace Expansion, Tilde Expansion, Variable Expansion, Command Substitution, Arithmetic Expansion, Filed Splitting, Globbing

by ds31x 2026. 1. 11.
728x90
반응형

시작하기

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)

와일드카드를 실제 파일 이름 목록으로 바꾼다.

보다 자세한 내용은 다음을 참고:

2025.10.04 - [CE] - glob 이란?

 

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"

처리 흐름:

  1. Lexical analysis: "abc" 하나의 토큰
  2. Expansion 없음
  3. Quote removal -> abc
  4. echo abc 실행

출력:

abc

참고로 Windows의 cmd.exe에선 double quote가 보인다.


예제 2 — quote가 구조를 보호하고 나중에 사라짐

echo "$HOME *.txt"
  1. $HOME은 확장됨
  2. *.txt에 대한 globbing 은 double quoting에 의해 차단됨.
  3. Quote removal 후 실제 인자:
/home/dsaint31 *.txt
  • *.txt가 그대로 출력되는 점에 유의할 것.

예제 3 — escape도 최종 인자에서 사라짐

echo "\$HOME"
  1. Lexical 단계: $HOME이 literal로 마킹됨
  2. Expansion 안 됨
  3. 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 → "'\ 제거
728x90