들어가며 — 까만 창의 정체
매일 쓰는 iTerm, Windows Terminal, VS Code terminal. 거기에 ls를 치면 파일 목록이 나오고, Ctrl+C로 실행 중인 프로세스를 멈추고, vim을 실행하면 전체 화면을 점유한 에디터가 뜬다. 자연스러워 보이지만, 이 모든 게 1970년대 VT100 텔레타이프 단말기의 추상화를 현대 OS가 여전히 유지한 결과다.
터미널 에뮬레이터가 진짜 뭘 할까?
- GUI 창 하나를 열어서 문자를 렌더링한다 (폰트, 커서, 스크롤백)
- 사용자의 키보드 입력을 받아 **가상 터미널(PTY)**의 "master" 쪽에 쓴다
- "slave" 쪽에서 프로세스(bash, vim 등)가 stdin/stdout으로 읽고 쓴다
- 프로세스가 쓴 바이트 중 ANSI 이스케이프 시퀀스를 해석해 색/커서 이동/화면 클리어 수행
그리고 bash 같은 셸은:
- PTY에서 한 줄 입력을 읽고 파싱
- 파이프/리다이렉션 처리
- fork + exec로 자식 프로세스 실행
Ctrl+C,Ctrl+Z를 받아 프로세스 그룹에 signal 전달- 프롬프트(
$,%)를 그리고 history 관리
이 모든 게 합쳐져서 "터미널"이 된다. 이 글에서는 각 층을 하나씩 벗겨보자. 글을 끝까지 읽으면 tmux/screen이 왜 필요한지, ssh가 어떻게 원격 터미널을 흉내내는지, stty -echo가 비밀번호 입력에 쓰이는 이유, ANSI 컬러 코드가 단순 숫자 나열인 이유를 단순히 "그렇게 된다"가 아니라 "왜 그렇게 설계됐는지" 설명할 수 있게 된다.
1. 역사 — 텔레타이프에서 가상 터미널까지
1.1 1869년 — Teletype
Edward Calahan이 설계한 전신 기계. 키보드 + 프린터. 상대 기계로 전신 신호를 보내면 그쪽 프린터가 글자를 찍음.
1.2 1960년대 — Computer Terminal
컴퓨터와 사람을 연결하는 단말기. 초기엔 프린터 기반(Teletype Model 33, "TTY"). 한 번 프린트한 줄은 수정 불가. **\r (carriage return)**은 프린트 헤드를 줄 처음으로, **\n (line feed)**은 종이를 한 줄 올림. Windows가 \r\n을 쓰는 이유의 역사적 기원.
1.3 1970년대 — Video Display Terminal
CRT 화면 + 키보드. 대표 모델: DEC VT100 (1978). 80x24 칸, 이스케이프 시퀀스로 커서 제어. 오늘날 "terminal"의 사실상 표준이 됨.
1.4 1980년대 이후 — Terminal Emulator
개인용 컴퓨터 보급으로 실제 단말기 장치 대신 소프트웨어 에뮬레이터가 주류. xterm(X Window), DEC VT320 호환. 이후 등장한 iTerm2, Windows Terminal, Alacritty, WezTerm 등 모두 VT100의 후손.
중요한 관찰: 2025년에도 우리는 VT100 시절 설계된 프로토콜 위에 구축된 도구로 일한다. 그 프로토콜이 50년 동안 교체되지 않은 이유는, 단순하고 바이트 스트림 기반이어서 네트워크/파이프/파일 어디든 동작하기 때문이다.
2. PTY — 가상 터미널의 정체
2.1 실제 단말기와의 차이
초창기 Unix는 실제 터미널 장치 /dev/tty0, /dev/tty1...을 통해 사용자와 통신. 요즘은 터미널 에뮬레이터(GUI 프로그램)가 실제 하드웨어가 아니니, 커널은 가상의 터미널 한 쌍을 만들어준다. 이게 pseudo-terminal (PTY).
2.2 Master-Slave 구조
┌─────────────────────┐ ┌──────────┐ ┌─────────────────┐
│ Terminal Emulator │◀──▶│ PTY pair │◀──▶│ Shell (bash,...) │
│ (iTerm, xterm, ...) │ │ │ │ │
└─────────────────────┘ └──────────┘ └─────────────────┘
master side slave side
(터미널이 읽고 씀) (프로세스가 읽고 씀)
- Master (
/dev/ptmx,/dev/pts/0의 반대편): 터미널 에뮬레이터가 보유. 여기로 쓰면 slave 쪽에서 stdin으로 읽힘. - Slave (
/dev/pts/0,/dev/pts/1, ...): shell이 stdin/stdout/stderr로 사용.
비유: 과거에는 컴퓨터와 터미널이 시리얼 케이블로 연결됐다. PTY는 그 케이블의 소프트웨어 에뮬레이션. 양 끝은 서로를 "파일"로 본다.
2.3 만드는 법
#include <pty.h>
int master_fd;
pid_t pid = forkpty(&master_fd, NULL, NULL, NULL);
if (pid == 0) {
// child: slave 쪽이 stdin/stdout/stderr가 됨
execlp("bash", "bash", NULL);
} else {
// parent: master_fd에 쓰고 읽기
}
터미널 에뮬레이터, SSH 서버, tmux, Docker의 -t, VS Code의 integrated terminal 모두 이 forkpty로 PTY를 만든다.
2.4 왜 필요한가
많은 프로그램이 "stdin이 터미널인지" 체크한다(isatty(0)). 터미널이면:
- 버퍼링 방식 변경 (
stdoutline-buffered) - 컬러 출력 활성화
- 프로그레스 바 표시
- 비밀번호 입력 시 echo 끄기
단순히 stdout을 파일로 리디렉션하면 isatty가 false를 반환하고 이런 기능이 꺼진다. 반대로 tmux/screen이 이런 프로그램을 감쌀 수 있는 건, PTY를 중간에 끼우기 때문.
3. termios — 터미널의 설정
3.1 무엇을 제어하는가
termios 구조체는 PTY의 입력/출력/라인 처리 규칙을 정의한다. 주요 설정:
- Echo on/off: 타이핑한 문자를 다시 출력할지
- Canonical mode on/off: 줄 단위로 모을지, 한 글자씩 바로 보낼지
- Signal generation:
Ctrl+C→ SIGINT 자동 변환 여부 - Flow control:
Ctrl+S/Ctrl+Q로 멈추고 재개
3.2 기본 모드 — Canonical (Cooked)
- 사용자가 입력한 문자들을 버퍼에 모음
Enter를 치면 한 줄 전체를 프로세스에 전달Backspace로 수정 가능 (커널이 처리)- Echo on → 입력이 화면에 표시
Ctrl+C/Ctrl+Z→ 시그널 발생
bash 프롬프트에서 명령을 입력하는 평범한 상태.
3.3 Raw 모드
- 문자 단위로 즉시 전달
- Echo off
- 시그널 생성 꺼짐 (프로그램이 직접
Ctrl+C바이트 0x03을 받음)
vim, htop, less 같은 TUI 앱이 사용. 이들은 방향키, Ctrl+X 같은 조합을 직접 해석해야 하기 때문.
3.4 실전 명령
# 비밀번호 입력 시 echo off
$ stty -echo; read password; stty echo
# 현재 설정 보기
$ stty -a
# Canonical 끄고 한 글자씩 받기
$ stty raw -echo; read -n 1 key; stty sane
# 리셋
$ stty sane
3.5 Python 예시
import termios, tty, sys
fd = sys.stdin.fileno()
old = termios.tcgetattr(fd)
try:
tty.setraw(fd)
ch = sys.stdin.read(1) # 한 글자 즉시
finally:
termios.tcsetattr(fd, termios.TCSADRAIN, old)
3.6 stty 흔한 용도
- 비밀번호 입력:
stty -echo - 터미널 크기 쿼리:
stty size→24 80 - cron 스크립트에서 에러: "stty: standard input: Inappropriate ioctl for device" → PTY 없는 환경에서 터미널 설정 시도
4. ANSI Escape Code — 제어 문자의 세계
4.1 기본 구조
모든 이스케이프 시퀀스는 **ESC (0x1B, \x1b, \033)**로 시작.
- CSI (Control Sequence Introducer):
ESC [(흔히\x1b[) - OSC (Operating System Command):
ESC ] - DCS (Device Control String):
ESC P
4.2 색상
$ echo -e "\x1b[31mRed Text\x1b[0m normal"
\x1b[31m: 전경색 빨강\x1b[0m: 리셋
기본 8색:
| code | 색 |
|---|---|
| 30 | Black |
| 31 | Red |
| 32 | Green |
| 33 | Yellow |
| 34 | Blue |
| 35 | Magenta |
| 36 | Cyan |
| 37 | White |
배경색은 4047. 9097/100~107은 bright 변형.
256색:
\x1b[38;5;196m ← 전경, 팔레트 196번 (빨강 계열)
\x1b[48;5;20m ← 배경, 팔레트 20번
Truecolor (16.7M):
\x1b[38;2;255;128;0m ← RGB (255, 128, 0) 오렌지
대부분의 현대 터미널이 truecolor 지원. $COLORTERM=truecolor 환경변수로 확인.
4.3 커서 이동
\x1b[H또는\x1b[1;1H: 홈 (1,1)\x1b[<n>;<m>H: (n, m)으로 이동\x1b[A(up),[B(down),[C(right),[D(left): n칸 이동 (숫자 생략 시 1)\x1b[s/\x1b[u: 커서 위치 저장/복원
4.4 화면 제어
\x1b[2J: 전체 화면 지우기\x1b[K: 현재 줄의 커서 이후 지우기\x1b[?1049h: Alternate screen buffer 진입 (vim/less가 사용 — 종료 시 원래 화면 복구)\x1b[?1049l: Alternate screen 종료
4.5 스타일
\x1b[1m: Bold\x1b[3m: Italic\x1b[4m: Underline\x1b[5m: Blink (요즘은 대부분 무시 — "접근성 문제")\x1b[7m: Reverse video\x1b[9m: Strikethrough\x1b[0m: 전부 리셋
4.6 OSC — 터미널 "대화"
\x1b]0;My Title\x07: 창 제목 설정 (BEL =\x07로 종료)\x1b]8;;https://example.com\x1b\\Link Text\x1b]8;;\x1b\\: Hyperlink (2017~)\x1b]52;c;<base64>\x07: 클립보드에 복사 (tmux, iTerm2 지원)
4.7 Bracketed paste mode
\x1b[?2004h ← 활성화
(붙여넣기 시)
\x1b[200~ ...내용... \x1b[201~
\x1b[?2004l ← 비활성화
셸이 "이건 붙여넣은 내용이야"라고 인지해 자동 실행되지 않게 한다. Zsh/Bash 최근 버전에서 기본 활성화.
5. Signal과 Job Control
5.1 시그널 개요
kill -l로 전체 목록 확인. 터미널 관련 주요:
| Signal | 번호 | 기본 동작 | 트리거 |
|---|---|---|---|
| SIGINT | 2 | 종료 | Ctrl+C |
| SIGQUIT | 3 | core dump | Ctrl+\ |
| SIGTSTP | 20 | 정지 | Ctrl+Z |
| SIGTTIN | 21 | 정지 | background에서 stdin 읽음 |
| SIGTTOU | 22 | 정지 | background에서 stdout 씀 (tostop 옵션 시) |
| SIGHUP | 1 | 종료 | 터미널 연결 끊김 (SSH 단절, 창 닫음) |
| SIGWINCH | 28 | 무시 | 터미널 크기 변경 |
5.2 프로세스 그룹과 세션
Session (login 단위)
├── Session leader (보통 shell)
└── Process groups
├── Foreground group (현재 PTY를 제어)
│ └── e.g., "grep foo | less" 파이프라인
└── Background groups
├── "long-running &"로 실행된 그룹
└── ...
setsid: 새 세션 만듦 (daemon 생성)setpgid: 프로세스를 특정 그룹으로 옮김tcsetpgrp(fd, pgid): 특정 그룹을 foreground로 지정
5.3 Ctrl+C의 여정
- 사용자가
Ctrl+C누름 → 터미널 에뮬레이터가 바이트0x03전송 - PTY master 쪽에서 받음 → Line discipline(N_TTY)이 canonical 모드 +
isig활성 확인 - Line discipline이 foreground 프로세스 그룹 전체에 SIGINT 발송
- 파이프라인 전체가 종료 (또는 handler 등록된 프로세스는 자체 처리)
왜 파이프라인 전체에? grep foo huge.log | less 중 Ctrl+C로 grep만 종료하면 less가 EOF 받고 끝나야 정상. 그룹 단위 시그널이 이걸 가능케 한다.
5.4 Job Control
bash/zsh에서:
foo &: background 실행Ctrl+Z: 현재 foreground 작업 정지 → SIGTSTPjobs: 작업 목록fg %1: 1번 작업 foreground로bg %1: 정지된 1번을 background에서 계속disown %1: shell의 job table에서 제거 (SIGHUP 안 받음)nohup cmd &: SIGHUP 무시하고 실행
5.5 SIGHUP와 nohup / tmux
SSH 세션이 끊기면 shell이 SIGHUP를 받고, 셸이 자식들에게 HUP 전파 → 모두 종료. 이걸 피하려면:
nohup: HUP 무시disown: job table에서 빼서 전파 대상 제외tmux/screen: 별도 세션으로 분리 (shell이 죽어도 tmux 서버가 살아있음)
5.6 SIGWINCH
창 크기 바꾸면 커널이 foreground 프로세스에 SIGWINCH 발송. vim/less/tmux는 이걸 handler로 받아 화면 다시 그림. 안 받으면 깨진 레이아웃.
6. Shell 내부 — bash/zsh가 하는 일
6.1 REPL 흐름
while True:
1. 프롬프트 출력 (PS1)
2. readline으로 한 줄 입력 받음
3. parse: 토큰화, 파이프/리다이렉션/expansion
4. 실행: builtin이면 직접, 아니면 fork+exec
5. wait (foreground인 경우)
6.2 Builtin vs External
- Builtin: shell이 직접 구현 —
cd,export,echo,read,source,alias- cd가 external이면 자식 프로세스의 chdir이라 부모에 반영 안 됨 → 반드시 builtin
- External: 별도 바이너리 실행 —
ls,grep,cat,node
type cd, type ls로 확인. command prefix로 builtin 우회.
6.3 Expansion (순서 중요)
- Brace expansion:
{a,b,c}→a b c,{1..5}→1 2 3 4 5 - Tilde:
~→$HOME - Parameter:
$VAR,${VAR:-default} - Command substitution:
$(cmd)또는`cmd` - Arithmetic:
$((1 + 2)) - Process substitution:
<(cmd),>(cmd) - Word splitting: IFS로 split
- Pathname expansion (globbing):
*.txt→ 파일 목록 - Quote removal
실행 전 이 순서대로 전개된다. echo "$VAR"에서 쿼트가 있으면 word splitting을 건너뛰는 등 세부 규칙이 복잡.
6.4 Pipe와 Redirection
$ a | b | c
- Shell이 3번 fork + exec, 각각 pipe() 생성
- a의 stdout → pipe 1 → b의 stdin
- b의 stdout → pipe 2 → c의 stdin
- 모두 같은 프로세스 그룹 (foreground)
- Shell은 c만 wait (마지막 exit code가
$?) — pipefail 옵션 있으면 중간도 체크
6.5 Glob 방식 — shell vs 프로그램
Unix 철학: ls *.txt는 shell이 *.txt를 파일 목록으로 펼쳐 ls file1.txt file2.txt로 exec. ls는 아무것도 몰라도 됨. Windows cmd/PowerShell이 개별 프로그램에 글로브 해석을 맡기는 것과 대조.
부작용: rm *를 빈 디렉터리에서 실행하면 shell이 "* 그대로" 넘김 (nullglob 옵션 없으면) → rm이 * 이름 파일을 못 찾아 에러. shopt -s nullglob로 다르게 설정 가능.
6.6 Exit code
- 0: 성공
- 1~255: 실패. 관례상 128+N은 시그널 N으로 종료 (SIGINT=2 → exit 130)
$?: 최근 명령의 exitset -e: 실패 시 즉시 스크립트 종료 (pipeline의 마지막만 체크하는 건 함정)set -o pipefail: pipeline 중간 실패도 감지
6.7 zsh vs bash
- Zsh: 2020년 macOS 기본. 더 풍부한 completion, globbing(
**/recursive,(.)modifier), theme (oh-my-zsh, starship) - Bash: Linux 기본. 호환성 최고, POSIX 스크립트에 적합
- Fish: non-POSIX, UX 우선 (자동 제안, 타입별 컬러). 스크립트 이식성 나쁨
6.8 프롬프트 파싱
PS1='\u@\h:\w\$ '
# \u: 사용자, \h: 호스트, \w: pwd, \$: #(root) or $
Zsh:
PROMPT='%n@%m:%~%# '
oh-my-zsh의 Git 상태 표시는 매 프롬프트마다 git status를 실행해서 체감 느릴 수 있음. starship / powerlevel10k는 async/cache로 개선.
7. Readline — 한 줄 편집의 기반
Bash(및 대부분 REPL)가 사용하는 GNU Readline 라이브러리.
7.1 주요 바인딩 (emacs 모드 기본)
Ctrl+A/Ctrl+E: 줄 처음/끝Ctrl+K: 커서 이후 잘라내기 (kill)Ctrl+U: 커서 앞 잘라내기Ctrl+Y: yank (붙여넣기)Ctrl+R: history 역검색Ctrl+W: 단어 단위 삭제Alt+.: 이전 명령의 마지막 인자 (매우 유용!)Ctrl+L: 화면 clear
7.2 Vi mode
$ set -o vi # bash
$ bindkey -v # zsh
Esc로 normal mode, i로 insert. 명령줄에서 vim 매크로급 편집 가능.
7.3 History
- 기본:
~/.bash_history또는~/.zsh_history - 세션 종료 시 저장 (bash) vs 즉시 append (zsh,
setopt inc_append_history) history -c: 메모리에서만 삭제HISTIGNORE="ls:pwd": 저장 안 할 패턴HISTCONTROL=ignoreboth: 중복/공백 시작 무시
7.4 inputrc
~/.inputrc로 readline 커스터마이즈:
set completion-ignore-case on
set show-all-if-ambiguous on
"\e[A": history-search-backward # 방향키 위 → prefix 매치 검색
8. SSH — 원격에서 터미널 에뮬레이션
8.1 세션 흐름
- TCP 연결 + SSH 프로토콜 핸드셰이크 (키 교환, 인증)
- 클라이언트가 "pty 요청" 채널 open (
-t옵션 또는 interactive) - 서버가 PTY 한 쌍 생성
- 서버의 slave 쪽에 login shell 실행
- 클라이언트 ↔ 서버 사이 양방향 바이트 스트림 (암호화)
로컬 키보드 입력 → SSH 암호화 → 서버 PTY master → shell stdin. 출력은 역방향.
8.2 SIGHUP과 SSH 단절
SSH 연결 끊김 → 서버의 PTY master close → slave 쪽 프로세스들이 SIGHUP 받음 → 종료. tmux/screen을 쓰는 이유.
8.3 SSH agent forwarding
ssh -A user@host: 로컬 SSH agent를 서버에서 접근 가능하게. 편하지만 서버가 해킹되면 내 SSH 키가 모든 다른 서버에 접근 가능 → 보안 위험. ForwardAgent no 기본 권장, 필요한 호스트만 config로 허용.
8.4 X11/포트 포워딩
-X: X11 forwarding (GUI 앱)-L: 로컬 포트를 원격으로-R: 원격 포트를 로컬로-D: 동적 SOCKS proxy
9. tmux / screen — Terminal Multiplexer
9.1 왜 필요한가
- 한 SSH 세션에서 여러 "가상 창"
- SSH 끊겨도 서버 쪽 세션 유지 (attach/detach)
- 창 분할(split), 창 전환(window)
- 복사/붙여넣기 버퍼 공유
9.2 구조
tmux server (daemon, 호스트당 1개)
└── sessions
├── session "main"
│ ├── window 0 (several panes)
│ ├── window 1
│ └── ...
└── session "work"
└── ...
클라이언트는 attach로 session에 붙고, detach로 떨어짐.
서버는 클라이언트 없어도 계속 실행.
9.3 tmux가 PTY를 쓰는 방식
- 각 pane이 별도 PTY
- tmux는 bytepile forwarder + terminal emulator 일부 기능을 구현
- pane 안 프로그램이 보는 건 tmux가 만든 PTY → 실제 터미널 크기와 무관하게 관리 가능
9.4 단축키 (기본 prefix Ctrl+B)
prefix c: 새 windowprefix %: 좌우 splitprefix ": 상하 splitprefix d: detachtmux ls: session 목록tmux attach -t main: 재접속
10. 터미널의 복사/붙여넣기
10.1 왜 Ctrl+C가 복사가 아닌가
Ctrl+C는 오래전부터 SIGINT 의미로 고정. 터미널 복사는 관례적으로:
- Linux xterm: 자동 primary selection (드래그)
- macOS Terminal/iTerm:
Cmd+C - Windows Terminal:
Ctrl+Shift+C
10.2 tmux copy mode
prefix [로 copy mode 진입 → vi/emacs 키로 선택 → Enter로 버퍼에 저장. prefix ]로 붙여넣기.
시스템 클립보드와 연동하려면 OSC 52 (clipboard paste)를 사용. tmux 3.2+에서 set -g set-clipboard on.
10.3 원격 SSH에서 클립보드
일반적으로 서버가 클라이언트 클립보드에 접근 불가. 해결:
- OSC 52 지원 (iTerm2, Windows Terminal, WezTerm 일부 버전)
- X11 forwarding +
xclip - mosh + clipboard script
11. 현대 터미널 에뮬레이터 비교
11.1 주요 선수
- iTerm2 (macOS): 가장 풍부한 기능, tabs, splits, triggers, shell integration
- Alacritty: Rust, GPU 가속, 설정 최소화, 매우 빠름
- WezTerm: Rust, 최고 수준 기능 + GPU + scripting
- Kitty: Python 설정, GPU, graphics 프로토콜 (이미지 인라인)
- Windows Terminal: MS 공식, WSL 친화
- Warp: Rust + AI 기능. 2025년 급부상
- Ghostty: 2024년 Mitchell Hashimoto 발표, zig 기반
11.2 성능 차이
단순 문자 렌더링은 차이 없어 보이지만:
cat huge-log-file처럼 초당 수십만 줄 출력 시 → Alacritty/Kitty/WezTerm이 Terminal.app보다 10배+ 빠름tmux내 세션 전환 → GPU 가속 있는 쪽이 부드러움- RAM 사용: Electron 기반 터미널(Hyper 등)이 가장 많이 먹음
11.3 Graphics 프로토콜
- Kitty image protocol: 인라인 이미지 표시
- Sixel: 오래된 pixel 프로토콜, iTerm2/WezTerm/Kitty 지원 확대
- iTerm2 inline images: iTerm2 고유
icat, kitty +kitten icat, timg 같은 툴로 터미널에서 이미지 보기 가능.
12. 색과 테마
12.1 $TERM 환경변수
$ echo $TERM
xterm-256color
terminfo 데이터베이스(/usr/share/terminfo/)의 엔트리 이름. 각 터미널이 자기 capability를 선언. tput 명령으로 쿼리:
$ tput colors # 256
$ tput cols # 120
$ tput setaf 1; echo Red; tput sgr0
12.2 $COLORTERM
$ echo $COLORTERM
truecolor
truecolor 지원 여부. 프로그램(vim, tmux)이 이 값을 보고 24비트 컬러 활성화.
12.3 테마 도구
- base16: 16색 팔레트 표준, 200+ 테마
- Dracula, Solarized, Nord, Gruvbox: 인기 팔레트
- starship / powerlevel10k: 프롬프트 테마
12.4 스크립트에서 색
# 직접 이스케이프
$ echo -e "\033[31mRed\033[0m"
# tput으로 이식성 있게
$ echo "$(tput setaf 1)Red$(tput sgr0)"
# terminfo 상수
$ RED=$(tput setaf 1); NC=$(tput sgr0)
$ echo "${RED}error${NC}"
파이프 감지:
if [ -t 1 ]; then
echo -e "\033[31mRed\033[0m"
else
echo "Red" # 파일로 리디렉션 시 색 제거
fi
ls --color=auto가 정확히 이 로직.
13. Shell 스크립트의 숨은 함정
13.1 Quoting
# 거의 항상 쌍따옴표로 감싸라
foo="$bar" # ✅
foo=$bar # ❌ word splitting
foo='$bar' # 리터럴 (expansion 안 함)
13.2 파일 이름에 공백
for f in $(ls); do echo $f; done
# "my file.txt"가 두 개 인자로 쪼개짐
# ✅
for f in *; do echo "$f"; done
# 또는
find . -type f -print0 | while IFS= read -r -d '' f; do ...; done
13.3 [ vs [[
[: POSIX, 실제로 별도 바이너리/usr/bin/[[[: bash/zsh 확장,&&/||사용, 단어 분리 안 함
[[ -f "$file" && -r "$file" ]] # bash OK
[ -f "$file" -a -r "$file" ] # POSIX
13.4 $* vs $@
"$@": 각 인자 separately (거의 항상 원하는 것)"$*": 한 문자열 결합$@(쿼트 없음): 위험. 공백 있는 인자 쪼개짐
13.5 errexit의 함정
set -e
cmd || fallback # errexit 건너뜀 (설계대로)
cmd && echo ok # cmd 실패 시 스크립트 종료 안 함 (&&의 좌측은 체크 안 함)
생각대로 안 돌 때가 많아서 set -e 대신 명시적 에러 체크를 선호하는 학파도 있다.
13.6 Subshell
(cd /tmp; some_cmd) # 현재 디렉터리 안 바뀜
cd /tmp; some_cmd # 현재 디렉터리 바뀜
|도 일부 경우 subshell 생성. 변수 할당이 "사라지는" 이유.
14. 현대 대안 — fish, nushell, xonsh
14.1 fish
- 2005 첫 릴리스, 2023 Rust 재구현
- non-POSIX (스크립트 이식성 낮음)
- Out-of-the-box 자동 제안, 히스토리 기반 completion
- 신규 사용자 UX 최고
14.2 nushell
- 2019, Rust
- 구조화된 데이터가 first-class (JSON/CSV/YAML을 테이블로)
- SQL-like 파이프라인:
ls | where size > 1MB | sort-by modified - Unix philosophy에 대한 재해석 — "all text"가 아니라 "structured records"
14.3 xonsh
- Python + shell.
ls | [x.upper() for x in _]같은 혼합 - 데이터 분석가 친화
14.4 oil shell (osh/ysh)
- POSIX 호환이면서 안전한 syntax 옵션
- 한 번에 옮기기 쉬운 "next-gen bash"
현실: 2025년 생산성 터미널은 **starship 프롬프트 + fish 또는 zsh(oh-my-zsh/p10k)**가 주류. nushell은 특수 워크로드에 실험적으로 사용.
15. Terminal의 미래
15.1 AI 통합
- Warp: AI command suggestion, 자연어 → shell command
- Fig/Amazon Q: 자동완성 확장
- GitHub Copilot CLI:
gh copilot explain,suggest
15.2 GPU/rich rendering
- Kitty graphics, Sixel 확산
- Ghostty 같은 zig 구현으로 성능 극한 추구
- 터미널에서 차트/이미지 인라인 표시 늘어남
15.3 Shell 세션의 공유
- tmate: tmux fork, 세션 URL로 공유
- Zellij: Rust multiplexer + 멀티유저
- upterm:
upterm session create로 페어 프로그래밍
15.4 Web-based terminals
- xterm.js: VS Code, JupyterLab, Grafana 등에 내장
- SSH 없이도 브라우저에서 컨테이너 터미널 접속
16. 실전 팁
디버깅
ps -ef/ps aux: 프로세스 목록ps -ejH또는pstree: 트리 구조 (프로세스 그룹/세션 구분)ps -o pid,ppid,pgid,sid,cmd: PID/PPID/PGID/SID 보기lsof -p <pid>: 프로세스의 열린 파일/fdstrace -p <pid>: 시스템 콜 추적tty: 현재 터미널 경로 (/dev/pts/0등)
생산성
alt+.로 이전 명령 마지막 인자 가져오기Ctrl+Rhistory 역검색 (fzf 연동하면 더 강력)!!: 직전 명령!$: 직전 명령 마지막 인자^foo^bar: 직전 명령의 foo를 bar로 바꿔 재실행sudo !!: 직전 명령 sudo로 재실행
생존
tmux new -s main→ 항상 tmux에서 작업ls --color=auto | cat은 파이프에서 색 안 나옴 (정상)script typescript.log cmd: 터미널 전체 세션 녹화
마무리 — 50년 된 추상화의 힘
VT100(1978) 프로토콜이 2025년에도 살아있다는 건 놀라운 일이다. 그 이유는 단순하다: 바이트 스트림 + 이스케이프 시퀀스라는 단순한 인터페이스가 "텍스트 I/O의 보편 언어"가 됐기 때문. 그 위에 tmux, ssh, Docker exec, VS Code terminal, 그리고 Warp 같은 AI 터미널까지 모두 똑같은 계층을 재활용한다.
개발자가 터미널을 "제대로" 이해하면:
- SSH 끊기면 작업 날아가는 문제를 tmux로 해결
- Ctrl+C가 안 먹는 프로그램은 raw mode라는 걸 앎
stty: Inappropriate ioctl에러는 PTY 없는 환경 → cron/CI에서isatty체크- vim이 종료 후 화면이 지워지는 이유는 alternate screen buffer
- color가 파이프에서 사라지는 이유는
isatty(1)체크 때문
이 글의 교훈 5가지:
- 터미널은 에뮬레이터 + PTY + shell + readline의 협연이지 단일 프로그램이 아니다
- Raw vs Canonical 모드 차이가 bash 입력과 vim 입력의 본질적 차이
- 시그널은 프로세스 그룹 단위 — 그래서 파이프라인 전체가
Ctrl+C로 죽는다 - ANSI 이스케이프 시퀀스는 1978년 스펙이고 여전히 표준이다
tmux는 "SSH 끊김 보험"이 아니라 멀티플렉서 — 기본 도구로 써라
다음 글에서는 Linux 커널 내부 — 시스템 콜의 여정, VFS, 프로세스 스케줄러, 메모리 관리, I/O 스택을 파고든다. 터미널에서 친 ls가 화면에 파일 목록으로 나오기까지, 키 입력 → PTY → shell → fork+exec → ls → open/read/write → VFS → filesystem driver → block device → 화면 경로 중 이 글은 PTY~shell까지만 다뤘다. 다음 글은 그 아래 레이어로 내려간다.
터미널은 단순한 "까만 창"이 아니라, 50년의 소프트웨어 진화가 누적된 살아있는 박물관이다. 그 박물관의 구조를 이해하는 순간, 매일 하는 작업이 훨씬 덜 신비롭고 훨씬 더 흥미로워진다.
현재 단락 (1/377)
매일 쓰는 iTerm, Windows Terminal, VS Code terminal. 거기에 `ls`를 치면 파일 목록이 나오고, `Ctrl+C`로 실행 중인 프로세스를 멈추고,...