1971년 톰슨 셸부터 1977년 본 셸(sh), 1989년 Bash까지 — 셸은 리눅스를 쓰는 사람이 가장 먼저, 가장 자주 마주치는 프로그램이다. 1급에서 셸은 단순한 "명령 입력기"가 아니라 하나의 인터프리터 언어이자 사용자 환경으로 다뤄진다.
리눅스 시스템은 크게 커널(kernel)과 셸(shell)로 나뉜다.
커널은 하드웨어를 직접 제어하는 핵심이고, 셸은 사용자 입력을 커널이 이해할 형태로 전달하는 명령어 해석기(command interpreter)다.
셸이라는 이름 자체가 "껍데기"라는 뜻 — 커널을 둘러싸 사용자와 맞닿는 가장 바깥층이기 때문이다.
1. 프롬프트 출력 → 사용자 입력 대기2. 입력 라인 읽기 (read)3. 토큰 분리 + 메타문자/와일드카드 확장 (parsing & expansion)4. 변수 치환, 명령 치환 수행5. 빌트인 명령인지 외부 명령인지 판별6. 외부 명령이면 PATH에서 실행 파일 검색7. fork() → exec()로 자식 프로세스 생성·실행8. wait()로 종료 대기 후 종료 코드 회수9. 다시 프롬프트 출력
이 한 사이클(REPL: Read-Eval-Print Loop)을 셸은 로그아웃할 때까지 반복한다.
셸은 "대화형 셸(interactive shell)"과 "비대화형 셸(non-interactive shell)"로 나뉜다. 터미널에서 직접 타이핑하면 대화형, 스크립트 파일을 실행하면 비대화형이다. 둘은 읽는 초기화 파일이 다르다 — 이 차이가 오늘 마지막 주제의 핵심이다.
cat /etc/shells # 설치된 셸 목록echo $SHELL # 현재 로그인 셸 경로echo $0 # 현재 실행 중인 셸 이름grep "^$(whoami):" /etc/passwd # 내 계정의 로그인 셸 확인chsh -s /bin/zsh # 기본 로그인 셸을 zsh로 변경 (change shell)
⚠️ 함정: $SHELL은 "로그인 시 지정된 기본 셸"을 가리키지, "지금 실행 중인 셸"이 아니다. bash로 로그인한 뒤 zsh를 직접 실행해도 $SHELL은 여전히 /bin/bash를 보여준다. 현재 실행 중인 셸은 echo $0 또는 ps -p $$로 확인해야 한다.
셸 변수(local variable / shell variable): 현재 셸 안에서만 유효하다. 자식 프로세스에게 상속되지 않는다.
환경 변수(environment variable): export로 표시(mark)되어 자식 프로세스에게 상속된다.
# 셸 변수 선언 — 현재 셸에서만 유효NAME="linux"echo $NAME # linux# 환경 변수로 승격 — 자식에게 상속됨export NAME# 또는 한 줄로 선언과 동시에 exportexport PROJECT="cert"
이 차이를 직접 확인하는 것이 이해의 핵심이다.
# 직접 쳐보기: 셸 변수는 자식에게 상속되지 않는다LOCAL_VAR="only-here"export GLOBAL_VAR="passed-down"bash # 자식 셸 진입echo "$LOCAL_VAR" # (빈 줄) — 상속 안 됨echo "$GLOBAL_VAR" # passed-down — 상속됨exit # 부모 셸로 복귀
변수의 상속은 부모 → 자식 한 방향으로만 일어난다. 자식 셸에서 환경 변수를 바꿔도 부모 셸에는 절대 반영되지 않는다. 자식은 부모 환경의 "복사본"을 받아 시작하기 때문이다. 그래서 스크립트 안에서 export로 바꾼 변수가 스크립트를 호출한 부모 셸에 남지 않는다.
set | head # 모든 변수(셸+환경+함수) — 가장 많이 나옴env # 환경 변수만export -p # export된 변수 목록unset NAME # 변수 삭제declare -p NAME # 변수 속성과 값 확인 (bash 빌트인)
env는 출력만이 아니라 임시로 환경을 바꿔 명령을 실행하는 데도 쓴다.
# LANG을 영어로 임시 설정해 date 실행 (현재 셸 환경은 그대로)env LANG=C date# 환경 변수를 모두 비우고 깨끗한 환경에서 실행env -i bash
set은 변수 출력 외에 셸 옵션을 켜고 끄는 데도 쓰인다. set -x는 실행되는 명령을 추적 출력(디버깅), set -e는 에러 발생 시 즉시 중단, set -u는 미정의 변수 사용 시 에러를 낸다. 스크립트 상단의 set -euo pipefail이 바로 이 옵션 조합으로, 안전한 스크립트의 관용구다.
echo $PATH# /usr/local/bin:/usr/bin:/bin:/usr/local/sbin:/usr/sbin# PATH에 디렉터리 추가 (앞에 붙이면 우선 검색됨)export PATH=/opt/myapp/bin:$PATH
⚠️ 함정: PATH를 설정할 때 기존 값을 덮어쓰지 말아야 한다. export PATH=/opt/bin처럼 쓰면 ls, cat 같은 기본 명령조차 찾지 못하게 된다. 반드시 export PATH=/opt/bin:$PATH처럼 기존 $PATH를 포함시켜야 한다. 또한 PATH는 앞에서부터 검색하므로, 같은 이름의 명령이 여러 곳에 있으면 먼저 나오는 디렉터리의 것이 실행된다.
1. /etc/profile (전역 — 모든 사용자) └─ /etc/profile.d/*.sh (전역 설정 모음)2. 사용자 홈에서 다음 중 "처음 발견되는 하나"만 실행: ~/.bash_profile → ~/.bash_login → ~/.profile3. 로그아웃 시: ~/.bash_logout
핵심은 2단계에서 세 파일을 다 읽는 게 아니라 처음 존재하는 하나만 읽는다는 점이다.
그래서 대부분의 배포판은 ~/.bash_profile 안에서 ~/.bashrc를 명시적으로 불러오도록 작성해 둔다.
# ~/.bash_profile 의 전형적인 내용if [ -f ~/.bashrc ]; then . ~/.bashrc # .bashrc를 불러옴 ( . 은 source의 줄임)fi
정리하면 — .bash_profile은 로그인할 때 한 번, .bashrc는 셸을 새로 띄울 때마다 읽힌다. 그래서 alias나 함수처럼 "모든 셸에서 매번 필요한 설정"은 .bashrc에, 환경 변수처럼 "로그인 시 한 번 설정하면 자식에게 상속되는 설정"은 .bash_profile에 두는 것이 관례다.
파일
범위
실행 시점
/etc/profile
전역
로그인 셸 시작 시 (전체 사용자)
/etc/bashrc
전역
대화형 셸 시작 시 (전체 사용자)
~/.bash_profile
개인
로그인 셸 시작 시
~/.bashrc
개인
대화형 셸 시작 시
~/.bash_logout
개인
로그아웃 시
~/.profile
개인
.bash_profile 없을 때 대체 (sh 호환)
~/.profile은 본래 bash 전용이 아니라 본 셸 호환 파일이다. 데비안/우분투는 전통적으로 ~/.bash_profile 대신 ~/.profile을 기본 제공한다. bash는 .bash_profile을 먼저 찾고 없으면 .bash_login, 그것도 없으면 .profile을 읽으므로, 우분투에서는 .profile이 실질적인 로그인 스크립트가 된다.
설정 파일을 수정한 뒤 로그아웃·재로그인하지 않고 즉시 반영하려면 source 또는 .(점)을 쓴다.
source ~/.bashrc # 현재 셸에 .bashrc 내용을 다시 읽어들임. ~/.bashrc # 위와 완전히 동일 ( . 은 source의 단축)
bash ~/.bashrc처럼 실행하면 안 된다. 이는 새 자식 셸을 띄워 그 안에서만 적용한 뒤 끝나버리므로 현재 셸에는 아무 변화가 없다. 반드시 source(또는 .)를 써서 현재 셸 프로세스 안에서 파일을 읽어야 변경이 유지된다. 이것이 바로 셸 변수 상속(부모↔자식)과 직결되는 개념이다.
source 명령은 원래 C 셸에서 처음 등장했고, 본 셸 계열에서는 .(점) 하나로 같은 일을 했다. bash는 두 표기를 모두 지원한다. 시험에서 ". file과 같은 역할을 하는 명령은?"이라는 문제가 나오면 답은 source다.