
지난 편에 이어 Bash의 심화 기능을 다루는 글이다.
잘 알아 두면 매우 편리한 확장과 명령어 치환,
리다이렉션과 파이프라인에 대한 내용이다.
중요하지 않은 부분도 있고 많이 쓰는 부분도 있어서
익숙한 거 위주로 잘 보면 된다.
확장(Expansion)
셸이 명령어를 실행하기 전에 변수, 표현식 등을 실제 값으로 변환하는 자동 치환 과정
# 확장 실행 순
1. 중괄호 확장 {a,b}
2. 틸데 확장 ~
3. 변수 치환 $VAR
4. 명령어 치환 $(cmd), `cmd`
5. 산술 치환 $(( ))
6. 와일드카드 *? 확장
7. 공백 분리 (word splitting)
중괄호 확장(Brace Expansion)
중괄호 {}를 사용해 문자열 시퀀스나 조합을 자동 생성하는 것
문자열은 쉼표로 구분하고 사이에 공백이나 쿼팅이 있으면 안 됨
아스키 값을 토대로 확장하는 증분을 사용할 수 있음
# 중괄호 확장 사용
{item1,item2,item3} # 쉼표 구분 리스트
{start..end} # 범위 (숫자/문자)
{start..end..step} # 단계 포함 범위
prefix{list}suffix # 접두사/접미사
# 기초 확장
echo {a..z}
a b c d e f g h i j k l m n o p q r s t u v w x y z
# for문에서 활용
for i in {1..3}; do echo "i is $i"; done
i is 1
i is 2
i is 3
# 파일명 일괄 생성
touch backup-{2025{01..12},2024{01..12}}.tar.gz
# 복합 패턴
echo file-{1..3}-{A..C}.txt
# file-1-A.txt file-1-B.txt file-1-C.txt ... file-3-C.txt
# 중첩 확장
echo {{A,B},{X,Y}}Z
# AZ BZ XZ YZ
틸데 확장(Tilde Expansion)
~를 홈 디렉토리로 자동 변환하는 기능
~ → 현재 사용자 홈 ($HOME)
/home/username
~username → 특정 사용자 홈
~otheruser → /home/otheruser
~+ → 현재 디렉토리 ($PWD)
~- → 이전 디렉토리 ($OLDPWD)
명령어 치환(Command Substitution)
명령어를 실행하고 그 표준 출력(stdout)을 문자열로 만들어 현재 명령어나 변수에 사용하는 기능
$(명령어) 또는 명령어 → 명령 실행 → 결과 문자열 반환
$VAR → 변수 치환 (값 가져오기)
$(CMD) → 명령어 치환 (명령 실행 → 결과 문자열)
# 현대 표준
DATE=$(date)
# 백틱 사용(구식)
DATE=`date`
2025. 12. 01. (월) 09:35:58 KST # 결과 동일
명령어 치환과 반환값의 구분
| 항목 | 명령어 치환 $( ) | 반환값 return |
|---|---|---|
| 저장 방법 | VAR=$(함수) | STATUS=$? |
| 반환 내용 | stdout 출력 | 종료 코드 0-255 |
| 용도 | 계산 결과, 파일 목록 등 | 성공/실패 여부 |
| 용량 제한 | 메모리 제한 없음 | 0~255 바이트 |
get_files() {
# 함수를 명령어 치환으로 호출하여 표준 출력(stdout)으로 출력한 내용
ls *.txt
# 반환값은 별도로 처리
return 0
}
FILES=$(get_files) # file1.txt file2.txt
STATUS=$? # 0
산술 확장(Arithmetic Expansion)
정수로 된 수학 표현식을 연산하고 결과를 반환하는 기능
$((표현식)) → 값 반환 (명령어 치환처럼 사용)
((표현식)) → 조건문/반복문 조건으로 사용 (값 반환 X)
산술 확장을 사용하면 변수 앞 $ 생략 가능 (예: a+1 = $a+1)
식 내부의 공백 허용 (예: (( a + b )) = ((a+b)))
결과를 저장하려면 표현식 안에서 변수의 값을 저장해야 함
정수형 변수
declare -i 로 선언한 변수
정수형 변수는 산술 연산을 바로 적용할 수 있고, 정수가 아닌 값을 저장하면 0으로 처리
정수형 → 일반변수: 정수값만 저장 (속성 사라짐)
일반변수 → 정수형: 자동 변환 (연산 적용)
declare -i num1=10
declare -i num2="abc" # "abc" → 0으로 변환
echo $num1 $num2 # 10 0
num1=$((num1 + num2)) # 자동 산술 연산
echo $num1 # 10
산술 연산자
| 연산자 | 의미 | 예시 |
|---|---|---|
| + | 덧셈 | $((5 + 3))→8 |
| - | 뺄셈 | $((5 - 3))→2 |
| * | 곱셈 | $((5 * 3))→15 |
| / | 나눗셈 (몫) | $((7 / 2))→3 |
| % | 나머지 | $((7 % 2))→1 |
| ** | 거듭제곱 | $((2 ** 3))→8 |
| 연산자 | 의미 | 예시 |
|---|---|---|
| var++ | 후증가 | i=5; echo $((i++))→5(i=6) |
| ++var | 전증가 | i=5; echo $((++i))→6(i=6) |
| var-- | 후감소 | i=5; echo $((i--))→5(i=4) |
| --var | 전감소 | i=5; echo $((--i))→4(i=4) |
#!/bin/bash
point="$1"
if ((point >= 90)); then
grade="A"
elif ((point >= 80)); then
grade="B"
elif ((point >= 70)); then
grade="C"
elif ((point >= 60)); then
grade="D"
else
grade="F"
fi
echo "Your grade is ${grade}"
# bash grade.sh 100 # Your grade is A
# bash grade.sh 80 # Your grade is B
비교 연산자
| 연산자 | 의미 |
|---|---|
| == | 같음 |
| != | 다름 |
| < | 작음 |
| > | 큼 |
| <= | 작거나 같음 |
| >= | 크거나 같음 |
논리 연산자
| 연산자 | 의미 |
|---|---|
| && | 논리 AND |
| ! | 논리 NOT |
비트 연산자
10진수를 2진수로 변환한 뒤 각 비트에 대해 연산을 수행
| 연산자 | 의미 | 예시 |
|---|---|---|
| 비트 OR | ||
| & | 비트 AND | $((5 \& 12))→4 |
| ^ | 비트 XOR | $((5 ^ 12))→9 |
| ~ | 비트 반전 | $((~5))→-6 |
| << | 왼쪽 시프트 | $((5 << 1))→10 |
| >> | 오른쪽 시프트 | $((12 >> 1))→6 |
a=5
b=12
echo $((a | b)) # 13
echo $((a & b)) # 4
echo $((~a & b)) # 8
echo $((a << 1)) # 10
PERMISSIONS=644 # rw-r--r--
EXECUTE=1 # ---x
((PERMISSIONS |= EXECUTE << 3)) # rwxr--r--
echo "decimal: $PERMISSIONS" # 755
echo "octal: $((8#${PERMISSIONS}))" # 755
할당 연산자
변수에 값을 저장하는 연산자
| 연산자 | 의미 | 예시 |
|---|---|---|
| = | 대입 | a=5 |
| += | 덧셈 대입 | ((a += 3)) |
| -= | 뺄셈 대입 | ((a -= 2)) |
| *= | 곱셈 대입 | ((a *= 4)) |
| /= | 나눗셈 대입 | ((a /= 2)) |
서브스트링 확장(Substring expansion)
변수에 저장된 문자열의 특정 부분을 추출하는 Bash 파라미터 확장 기능
오프셋 : 양수는 문자열 시작(0)부터, 음수는 문자열 끝(-1)부터
음수 오프셋은 숫자 앞 공백 필수 (${VAR: -3})
${변수:오프셋} # 오프셋부터 문자열 끝까지 추출
${변수:오프셋:길이} # 오프셋부터 지정 길이만큼 추
# 원본 문자열
FILENAME="backup-2025-12-01.tar.gz"
VERSION="v1.2.3"
# 오프셋만 지정
echo ${FILENAME:7} # 2025-12-01.tar.gz
# 오프셋과 길이 지정
echo ${FILENAME:0:6} # backup
echo ${FILENAME:7:10} # 2025-12-01
# 음수 오프셋 (끝에서 시작)
echo ${FILENAME: -7} # tar.gz (끝 7자)
echo ${VERSION: -2} # 3 (끝 2자)
패턴 찾아 바꾸기
변수에 저장된 문자열 내에서 특정 패턴을 찾아 다른 문자열로 변경하는 기능
| 형식 | 설명 | 예시 (foofoobar) | 결과 |
|---|---|---|---|
| ${변수/패턴/문자열} | 처음 일치하는 패턴만 한 번 바꿈 | ${str/foo/bar} | barfoobar(첫 번째foo만bar로) |
| ${변수//패턴/문자열} | 모든 일치하는 패턴 바꿈 | ${str//foo/bar} | barbarbar(모든foobar로) |
| ${변수/#패턴/문자열} | 문자열 시작 부분이 패턴과 일치하면 바꿈 | ${str/#foo/bar} | barfoobar(foo로 시작하면 변경) |
| ${변수/%패턴/문자열} | 문자열 끝 부분이 패턴과 일치하면 바꿈 | ${str/%bar/baz} | foobar baz(bar로 끝나면 변경) |
변수 값에 따른 확장
변수의 설정 여부와 내용에 따라 다른 값을 반환하거나 에러를 발생시키는 Bash 파라미터 확장 기능
| 형식 | 동작 | 예시 (unset VAR) | 예시 (VAR="") | 예시 (VAR="set") |
|---|---|---|---|---|
| ${VAR:-기본값} | 빈 값일 때 기본값 사용(변수 미설정) | 기본값 | 기본값 | set |
| ${VAR:=기본값} | 빈 값일 때 기본값 사용+ 변수에 저장 | 기본값 (VAR=기본값) | 기본값 (VAR=기본값) | set |
| ${VAR:?에러메시지} | 빈 값일 때 에러 발생 | 에러 종료 | 에러 종료 | set |
생략
셸 옵션
Bash의 동작 방식을 제어하는 설정값으로, set 명령어를 통해 변경 가능
- : 옵션 활성화
+ : 옵션 비활성화
주요 셸 옵션 목록
| 옵션 | 플래그 | 설명 |
|---|---|---|
errexit | -E | 함수 내 trap 상속 |
pipefail | -o pipefail | 파이프라인에서 첫 실패 반환 |
nounset | -u | 미설정 변수 참조 시 에러 |
xtrace | -x | 명령 실행 전 추적 출력 |
set
set -o 옵션명 # 활성화
set -플래그 # 활성화 (예: set -x)
set +o 옵션명 # 비활성화
set +플래그 # 비활성화 (예: set +x)
리다이렉션(Redirection)
표준 입출력 스트림의 방향을 변경하여 다른 스트림으로 입출력을 지정하는 기능
| 스트림 번호 | 이름 | 기본 위치 | 용도 |
|---|---|---|---|
| 0 | stdin | 키보드 | 입력 |
| 1 | stdout | 터미널 | 일반 출력 |
| 2 | stderr | 터미널 | 에러 출력 |
출력 리다이렉션
프로세스의 출력 스트림(표준 출력, 표준 에러)을 파일로 출력하는 것
스트림 번호를 생략하면 표준 출력(1)이 자동으로 설정되고, 표준 에러는 2로 표기
명령 [스트림_번호] > 파일 # 저장할 내용을 파일에 덮어쓰기
명령 [스트림_번호] >> 파일 # 기존의 파일 내용에 이어 쓰기
# 에러 리다이렉션
username@host:~$ cc # 담을 내용 (에러)
cc: fatal error: no input files
compilation terminated.
username@host:~$ cc 2> err # stderr만 파일로
username@host:~$ cat err
cc: fatal error: no input files
compilation terminated.
username@host:~$ cc 2> /dev/stdout # stderr → stdout으로 리다이렉트
cc: fatal error: no input files
compilation terminated.
표준 출력과 표준 에러 한번에 리다이렉션
명령 [스트림_번호] > 파일 [n]>&[m] : 스트림 n을 스트림 m으로 리다이렉션
명령어의 실행 성공 여부를 알 수 없지만 한 파일에 리다이렉션하고 싶을 때 활용
Bash는 표준 출력과 표준 에러를 동시에 리다이렉션하는 >& 기능을 제공
username@host:~$ ping -c www.google.com > err 2>&1
username@host:~$ ping -c www.google.com &> err # 동일하게 동작
username@host:~$ cat err
ping: invalid argument: 'www.google.com'
입력 리다이렉션
표준 입력을 키보드 대신 다른 파일 등의 스트림으로부터 받도록 하는 기능
username@host:~$ cat < input.txt # 파일 내용 전체 출력
username@host:~$ sort < names.txt # 정렬
username@host:~$ wc -l < log.txt # 단어 수 세기
Here documents
임시 파일을 만들지 않고 여러 줄의 텍스트를 명령어의 표준 입력으로 전달하는 입력 리다이렉션
구분자는 영문 한 단어로, 어떤 값이든 사용할 수 있으나 대부분 EOF/END를 사용
파일 생성과 삭제가 불필요하고 권한 문제가 없으며, 파이프라인과 호환성 좋음
명령어 [스트림_번호]<< 구분자
[여러 줄 텍스트]
구분자(동일한 문자열, 들여쓰기 없음)
# 입력한 내용 바로 파일에 저장 가능
cat << EOF > config.txt
SERVER=localhost
PORT=8080
DEBUG=true
DATABASE=/var/db/app.db
EOF
Here strings
Here documents와 유사하지만 텍스트 한 줄을 입력하는 기능
명령어 [스트림_번호]<<< 문자열
sort <<< "cherry
banana
apple" # 정렬
wc -w <<< "Hello World Bash" # 단어 수 세기
sed 's/Bash/Shell/g' <<< "Hello World Bash" # sed 치환
파이프라인(Pipeline)
여러 프로세스의 입출력 스트림을 파일 없이 직접 연결하여 데이터를 전달하는 방식
한 명령어의 출력을 다음 명령어의 입력으로 바로 전달하기 때문에 연속적인 데이터 처리 가능
# 파이프라인 사용법
command1 | command2 | command3
# 표준 오류를 포함하여 전달하는 법
command1 |& command2 |& command3
xargs
표준 입력으로 받은 내용을 명령어의 인수로 변환하여 실행
username@host:~$ touch err1 err2 err3
username@host:~$ ls | grep err | xargs rm # grep으로 걸러진 파일명을 xargs가 rm에 인수로 넘김
username@host:~$ ls | grep err # err로 시작하는 파일 없음
파이프라인의 프로세스 종료 코드
기본적으로 파이프라인 명령어의 종료 상태는 마지막 명령의 프로세스 종료 코드만 반영
중간 명령어가 실패해도 알기 어려움
중간에 실패하는 경우 종료 코드에 반영하기
pipefail 옵션을 설정하면 파이프라인에 포함된 명령어 중 실패하는 명령이 있을 때 전체 실패 처리
set -o pipefail
command1 | command2 | command3
처음에는 변수 확장과 명령어 치환이 조금 헷갈렸는데
쓰다 보니 편리하고 좋은 기능이라는 생각이 들었다.
확장은 워낙 방대한 부분이라 다 알 필요까지는 없고
필요할 때 찾아서 활용해 보면 좋겠다.